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