go-chip8/main.go
main.goRaw
1package main
2
3import (
4 "flag"
5 "fmt"
6 "io/ioutil"
7 "math/rand"
8 "os"
9
10 "github.com/veandco/go-sdl2/sdl"
11)
12
13const w = 64
14const h = 32
15
16type chip8 struct {
17 opcode uint16
18 memory [4096]byte
19
20 // register
21 v [16]byte
22
23 index uint16
24 pc uint16
25
26 gfx [w * h]byte
27
28 delayTimer byte
29 soundTimer byte
30
31 stack [16]uint16
32 sp int
33
34 key [16]byte
35
36 drawFlag bool
37
38 // graphics
39 renderer *sdl.Renderer
40 zoom int32
41}
42
43// Initialize registers and memory once
44func (c *chip8) initialize() {
45 c.pc = 0x200
46 c.opcode = 0
47 c.index = 0
48 c.sp = 0
49 c.zoom = 8
50
51 for i := 0; i < len(fontSet); i++ {
52 c.memory[i] = fontSet[i]
53 }
54}
55
56func (c *chip8) emulateCycle() {
57 // Fetch Opcode
58 c.opcode = uint16(c.memory[c.pc])<<8 | uint16(c.memory[c.pc+1])
59 x := byte((c.opcode & 0x0F00) >> 8)
60 y := byte((c.opcode & 0x00F0) >> 4)
61 nn := byte(c.opcode & 0x00FF)
62 nnn := uint16(c.opcode & 0x0FFF)
63 // fmt.Printf("%X\n", c.opcode)
64
65 // Decode Opcode
66 // https://en.wikipedia.org/wiki/CHIP-8#Opcode_table
67 // http://www.multigesture.net/wp-content/uploads/mirror/goldroad/chip8_instruction_set.shtml
68 switch c.opcode & 0xF000 {
69 case 0x0000:
70 switch c.opcode & 0x000F {
71 case 0x0000:
72 // 00E0 Clear screen
73 for i := 0; i < len(c.gfx); i++ {
74 c.gfx[i] = 0
75 }
76 c.pc += 2
77 c.drawFlag = true
78 break
79 case 0x000E:
80 // 00EE Returns from a subroutine
81 c.sp--
82 c.pc = c.stack[c.sp]
83 c.pc += 2
84 break
85 default:
86 fmt.Printf("Unknown opcode [0x0000]: 0x%X\n", c.opcode)
87 }
88 case 0x1000:
89 // 1NNN Jumps to address NNN
90 // c.pc = c.opcode & 0x0FFF
91 c.pc = nnn
92 break
93 case 0x2000:
94 // 2NNN Calls subroutine at NNN
95 c.stack[c.sp] = c.pc
96 c.sp++
97 // c.pc = c.opcode & 0x0FFF
98 c.pc = nnn
99 break
100 case 0x3000:
101 // 3XNN Skips the next instruction if VX equals NN. (Usually
102 // the next instruction is a jump to skip a code block)
103 if c.v[x] == nn {
104 c.pc += 2
105 }
106 c.pc += 2
107 break
108 case 0x4000:
109 // 4XNN Skips the next instruction if VX doesn't equal NN.
110 // (Usually the next instruction is a jump to skip a code
111 // block)
112 if c.v[x] != nn {
113 c.pc += 2
114 }
115 c.pc += 2
116 break
117 case 0x5000:
118 // 5XY0 Skips the next instruction if VX equals VY. (Usually
119 // the next instruction is a jump to skip a code block)
120 if c.v[x] != c.v[y] {
121 c.pc += 2
122 }
123 c.pc += 2
124 break
125 case 0x6000:
126 // 6XNN Sets VX to NN
127 c.v[x] = nn
128 c.pc += 2
129 break
130 case 0x7000:
131 // 7XNN Adds NN to VX. (Carry flag is not changed)
132 // c.v[c.opcode&0x0F00>>8] += byte(c.opcode)
133 c.v[x] += nn
134 c.pc += 2
135 break
136 case 0x8000:
137 switch c.opcode & 0x000F {
138 case 0x0000:
139 // 0x8XY0 Sets VX to the value of VY
140 c.v[x] = c.v[y]
141 c.pc += 2
142 case 0x0001:
143 // 0x8XY1 Sets VX to VX or VY. (Bitwise OR operation)
144 c.v[x] = (c.v[x] | c.v[y])
145 c.pc += 2
146 case 0x0002:
147 // 0x8XY2 Sets VX to VX and VY. (Bitwise AND operation)
148 c.v[x] = (c.v[x] & c.v[y])
149 c.pc += 2
150 case 0x0003:
151 // 0x8XY3 Sets VX to VX xor VY
152 c.v[x] = (c.v[x] ^ c.v[y])
153 c.pc += 2
154 case 0x0004:
155 // 0x8XY4 Adds VY to VX. VF is set to 1 when there's a
156 // carry, and to 0 when there isn't
157 if c.v[y] > (0xFF - c.v[x]) {
158 c.v[0xF] = 1
159 } else {
160 c.v[0xF] = 0
161 }
162 c.v[x] += c.v[y]
163 c.pc += 2
164 break
165 case 0x0005:
166 // 0x8XY5 VY is subtracted from VX. VF is set to 0 when
167 // there's a borrow, and 1 when there isn't
168 if c.v[x] > c.v[y] {
169 c.v[0xF] = 0x1
170 } else {
171 c.v[0xF] = 0x0
172 }
173 c.v[x] -= c.v[y]
174 c.pc += 2
175 case 0x0006:
176 // 0x8XY6 Stores the least significant bit of VX in VF
177 // and then shifts VX to the right by 1
178 if c.opcode&0x1 >= 1 {
179 c.v[0xF] = 1
180 } else {
181 c.v[0xF] = 0
182 }
183 c.v[x] = c.v[x] >> 1
184 c.pc += 2
185 case 0x0007:
186 // 0x8XY7 Sets VX to VY minus VX. VF is set to 0 when
187 // there's a borrow, and 1 when there isn't
188 if c.v[y] > c.v[x] {
189 c.v[0xF] = 0x1
190 } else {
191 c.v[0xF] = 0x0
192 }
193 c.v[x] = c.v[y] - c.v[x]
194 c.pc += 2
195 case 0x000E:
196 // 0x8XYE Stores the most significant bit of VX in VF
197 // and then shifts VX to the left by 1
198 if c.opcode&0x80 == 0x80 {
199 c.v[0xF] = 1
200 } else {
201 c.v[0xF] = 0
202 }
203 c.v[x] = c.v[x] << 1
204 c.pc += 2
205 default:
206 fmt.Printf("Unknown opcode [0x8000]: 0x%X\n", c.opcode)
207 }
208 case 0x9000:
209 // 9XY0 Skips the next instruction if VX doesn't equal VY.
210 // (Usually the next instruction is a jump to skip a code
211 // block)
212 if c.v[x] != c.v[y] {
213 c.pc += 2
214 }
215 c.pc += 2
216 case 0xA000:
217 // ANNN set index to NNN position
218 c.index = nnn
219 c.pc += 2
220 break
221 case 0xB000:
222 // BNNN Jumps to the address NNN plus V0
223 c.pc = nnn + uint16(c.v[0])
224 case 0xC000:
225 // CXNN Sets VX to the result of a bitwise and operation on a
226 // random number (Typically: 0 to 255) and NN
227 r := byte(rand.Intn(255))
228 c.v[x] = r & nn
229 c.pc += 2
230 case 0xD000:
231 // DXYN Draws a sprite at coordinate (VX, VY) that has a width
232 // of 8 pixels and a height of N+1 pixels. Each row of 8 pixels
233 // is read as bit-coded starting from memory location I; I
234 // value doesn’t change after the execution of this
235 // instruction. As described above, VF is set to 1 if any
236 // screen pixels are flipped from set to unset when the sprite
237 // is drawn, and to 0 if that doesn’t happen
238
239 height := c.opcode & 0x000F
240
241 var pixel byte
242 c.v[0xF] = 0
243
244 for yline := uint16(0); yline < height; yline++ {
245 pixel = c.memory[c.index+yline]
246 for xline := uint16(0); xline < 8; xline++ {
247 if (pixel & (0x80 >> xline)) != 0 {
248 pos := (uint16(c.v[x]) + xline) + ((uint16(c.v[y]) + yline) * w)
249 if pos >= 2048 {
250 break
251 }
252 if c.gfx[pos] == 1 {
253 c.v[0xF] = 1
254 }
255 c.gfx[pos] ^= 1
256 }
257 }
258
259 }
260
261 c.drawFlag = true
262 c.pc += 2
263 break
264 case 0xE000:
265 switch c.opcode & 0x00FF {
266 case 0x009E:
267 // EX9E Skips the next instruction if the key stored in
268 // VX is pressed. (Usually the next instruction is a
269 // jump to skip a code block)
270 if c.key[c.v[x]] != 0 {
271 c.pc += 2
272 }
273 c.pc += 2
274 break
275 case 0x00A1:
276 // EXA1 Skips the next instruction if the key stored in
277 // VX isn't pressed. (Usually the next instruction is a
278 // jump to skip a code block)
279 if c.key[c.v[x]] != 1 {
280 c.pc += 2
281 }
282 c.pc += 2
283 default:
284 fmt.Printf("Unknown opcode [0xE000]: 0x%X\n", c.opcode)
285 }
286 break
287 case 0xF000:
288 switch c.opcode & 0x00FF {
289 case 0x0007:
290 // FX07 Sets VX to the value of the delay timer
291 c.v[x] = c.delayTimer
292 c.pc += 2
293 case 0x000A:
294 // FX0A A key press is awaited, and then stored in VX.
295 // (Blocking Operation. All instruction halted until
296 // next key event)
297 pressed := false
298 for i := 0; i < 16; i++ {
299 if c.key[i] == 1 {
300 c.v[x] = byte(i)
301 pressed = true
302 }
303 }
304 if !pressed {
305 return
306 }
307 c.pc += 2
308 case 0x0015:
309 // FX15 Sets the delay timer to VX
310 c.delayTimer = c.v[x]
311 c.pc += 2
312 case 0x0018:
313 // FX18 Sets the sound timer to VX
314 c.soundTimer = c.v[x]
315 c.pc += 2
316 case 0x001E:
317 // FX1E Adds VX to I. VF is not affected
318 c.index += uint16(c.v[x])
319 c.pc += 2
320 case 0x0029:
321 // FX29 Sets I to the location of the sprite for the
322 // character in VX. Characters 0-F (in hexadecimal) are
323 // represented by a 4x5 font
324 c.index = uint16(c.v[x]) * 5
325 c.pc += 2
326 break
327 case 0x0033:
328 c.memory[c.index] = c.v[x] / 100
329 c.memory[c.index+1] = (c.v[x] / 10) % 10
330 c.memory[c.index+2] = (c.v[x] / 100) % 10
331 c.pc += 2
332 break
333 case 0x0055:
334 // FX55 Stores V0 to VX (including VX) in memory
335 // starting at address I. The offset from I is
336 // increased by 1 for each value written, but I itself
337 // is left unmodified
338 for i := uint16(0); i <= uint16(x); i++ {
339 c.memory[c.index+i] = c.v[i]
340 }
341 c.pc += 2
342 case 0x0065:
343 // 0xFX65 Fills V0 to VX (including VX) with values
344 // from memory starting at address I. The offset from I
345 // is increased by 1 for each value written, but I
346 // itself is left unmodified
347 for i := uint16(0); i < uint16(x)+1; i++ {
348 c.v[i] = c.memory[c.index+i]
349 }
350 c.pc += 2
351 break
352 default:
353 fmt.Printf("Unknown opcode [0xF000]: 0x%X\n", c.opcode)
354 }
355 break
356 default:
357 fmt.Printf("Unknown opcode: 0x%X\n", c.opcode)
358 }
359
360 // Update timers
361 if c.delayTimer > 0 {
362 c.delayTimer--
363 }
364 if c.soundTimer > 0 {
365 if c.soundTimer == 1 {
366 fmt.Printf("Beep!\n")
367 }
368 c.soundTimer--
369 }
370}
371
372func (c *chip8) loadGame(filepath string) error {
373 buffer, err := ioutil.ReadFile(filepath)
374 if err != nil {
375 return err
376 }
377
378 for i := 0; i < len(buffer); i++ {
379 // 0x200 == 512
380 c.memory[512+i] = buffer[i]
381 }
382 return nil
383}
384
385var fontSet = [80]byte{
386 0xF0, 0x90, 0x90, 0x90, 0xF0, // 0
387 0x20, 0x60, 0x20, 0x20, 0x70, // 1
388 0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2
389 0xF0, 0x10, 0xF0, 0x10, 0xF0, // 3
390 0x90, 0x90, 0xF0, 0x10, 0x10, // 4
391 0xF0, 0x80, 0xF0, 0x10, 0xF0, // 5
392 0xF0, 0x80, 0xF0, 0x90, 0xF0, // 6
393 0xF0, 0x10, 0x20, 0x40, 0x40, // 7
394 0xF0, 0x90, 0xF0, 0x90, 0xF0, // 8
395 0xF0, 0x90, 0xF0, 0x10, 0xF0, // 9
396 0xF0, 0x90, 0xF0, 0x90, 0x90, // A
397 0xE0, 0x90, 0xE0, 0x90, 0xE0, // B
398 0xF0, 0x80, 0x80, 0x80, 0xF0, // C
399 0xE0, 0x90, 0x90, 0x90, 0xE0, // D
400 0xF0, 0x80, 0xF0, 0x80, 0xF0, // E
401 0xF0, 0x80, 0xF0, 0x80, 0x80, // F
402}
403
404func (c *chip8) setupInput() {
405}
406
407func (c *chip8) setupGraphics() {
408 fmt.Println(c.zoom)
409 window, err := sdl.CreateWindow("go-chip8", sdl.WINDOWPOS_UNDEFINED,
410 sdl.WINDOWPOS_UNDEFINED, w*c.zoom, h*c.zoom, sdl.WINDOW_SHOWN)
411 if err != nil {
412 panic(err)
413 }
414 c.renderer, err = sdl.CreateRenderer(window, -1, 0)
415 if err != nil {
416 panic(err)
417 }
418}
419
420func (c *chip8) drawGraphics() {
421 // x := 0
422 // y := 0
423 // for i := 0; i < len(c.gfx); i++ {
424 // if i%w == 0 {
425 // x = 0
426 // y++
427 // fmt.Println("")
428 // }
429 // if c.gfx[i]^1 == 0 {
430 // c.renderer.DrawPoint(int32(x), int32(y))
431 // fmt.Print("x")
432 // } else {
433 // fmt.Print(" ")
434 // }
435 // x++
436 // }
437
438 // for y := 0; y < h; y++ {
439 // for x := 0; x < w; x++ {
440 // pixel := c.gfx[y*w+x]
441 // if pixel != 0 {
442 // c.renderer.DrawPoint(int32(x), int32(y))
443 // }
444 // }
445 // }
446
447 c.renderer.SetDrawColor(0, 0, 0, 1)
448 c.renderer.Clear()
449 c.renderer.SetDrawColor(255, 255, 255, 1)
450 for y := 0; y < h; y++ {
451 for x := 0; x < w; x++ {
452 pixel := c.gfx[y*w+x]
453 if pixel != 0 {
454 c.renderer.FillRect(&sdl.Rect{
455 X: int32(x) * c.zoom,
456 Y: int32(y) * c.zoom,
457 W: c.zoom,
458 H: c.zoom,
459 })
460 }
461 }
462 }
463
464 c.renderer.Present()
465 c.drawFlag = false
466}
467
468func (c *chip8) setKeys() {
469 for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() {
470 switch t := event.(type) {
471 case *sdl.QuitEvent:
472 println("Quit")
473 os.Exit(0)
474 // running = false
475 // break
476 case *sdl.KeyboardEvent:
477 switch t.Type {
478 case sdl.KEYDOWN:
479 if keyHex, ok := validKeys[t.Keysym.Sym]; ok {
480 fmt.Println("down", t.Keysym.Sym)
481 c.key[keyHex] = 1
482 fmt.Println(keyHex, c.key[keyHex])
483 }
484 case sdl.KEYUP:
485 if keyHex, ok := validKeys[t.Keysym.Sym]; ok {
486 fmt.Println("up", t.Keysym.Sym)
487 c.key[keyHex] = 0
488 fmt.Println(keyHex, c.key[keyHex])
489 }
490 if t.Keysym.Sym == sdl.K_ESCAPE {
491 fmt.Println("EXIT")
492 os.Exit(0)
493 }
494 }
495 }
496 }
497
498}
499
500/*
501 Key pad: Keyboard keys:
502 1 2 3 c 1 2 3 4
503 4 5 6 d q w e r
504 7 8 9 e a s d f
505 a 0 b f z x c v
506*/
507var validKeys = map[sdl.Keycode]byte{
508 sdl.K_1: 0x01,
509 sdl.K_2: 0x02,
510 sdl.K_3: 0x03,
511 sdl.K_4: 0x0c,
512 sdl.K_q: 0x04,
513 sdl.K_w: 0x05,
514 sdl.K_e: 0x06,
515 sdl.K_r: 0x0d,
516 sdl.K_a: 0x07,
517 sdl.K_s: 0x08,
518 sdl.K_d: 0x09,
519 sdl.K_f: 0x0e,
520 sdl.K_z: 0x0a,
521 sdl.K_x: 0x00,
522 sdl.K_c: 0x0b,
523 sdl.K_v: 0x0f,
524}
525
526func main() {
527 filepath := flag.String("file", "file-path", "file path of the input file")
528
529 flag.Parse()
530
531 var c chip8
532 c.initialize()
533 c.setupGraphics()
534 c.setupInput()
535
536 err := c.loadGame(*filepath)
537 if err != nil {
538 panic(err)
539 }
540
541 for {
542 c.emulateCycle()
543 if c.drawFlag {
544 c.drawGraphics()
545 }
546 c.setKeys()
547 sdl.Delay(100 / 60)
548 }
549}