go-chip8/main.go
main.goRaw
1package main
2
3import (
4 "flag"
5 "fmt"
6 "io/ioutil"
7 "math/rand"
8
9 "github.com/veandco/go-sdl2/sdl"
10)
11
12const w = 64
13const h = 32
14
15type chip8 struct {
16 opcode uint16
17 memory [4096]byte
18
19 // register
20 v [16]byte
21
22 index uint16
23 pc uint16
24
25 gfx [w * h]byte
26
27 delayTimer byte
28 soundTimer byte
29
30 stack [16]uint16
31 sp int
32
33 key [16]byte
34
35 drawFlag bool
36
37 // graphics
38 renderer *sdl.Renderer
39}
40
41// Initialize registers and memory once
42func (c *chip8) initialize() {
43 c.pc = 0x200
44 c.opcode = 0
45 c.index = 0
46 c.sp = 0
47}
48
49func (c *chip8) emulateCycle() {
50 // Fetch Opcode
51 c.opcode = uint16(c.memory[c.pc])<<8 | uint16(c.memory[c.pc+1])
52 x := byte((c.opcode & 0x0F00) >> 8)
53 y := byte((c.opcode & 0x00F0) >> 4)
54 nn := byte(c.opcode & 0x00FF)
55 nnn := uint16(c.opcode & 0x0FFF)
56 fmt.Printf("%X\n", c.opcode)
57
58 // Decode Opcode
59 // https://en.wikipedia.org/wiki/CHIP-8#Opcode_table
60 // http://www.multigesture.net/wp-content/uploads/mirror/goldroad/chip8_instruction_set.shtml
61 switch c.opcode & 0xF000 {
62 case 0x0000:
63 switch c.opcode & 0x000F {
64 case 0x0000:
65 // 00E0 Clear screen
66 for i := 0; i < len(c.gfx); i++ {
67 c.gfx[i] = 0
68 }
69 c.pc += 2
70 c.drawFlag = true
71 break
72 case 0x000E:
73 // 00EE Returns from a subroutine
74 c.sp--
75 c.pc = c.stack[c.sp]
76 break
77 default:
78 fmt.Printf("Unknown opcode [0x0000]: 0x%X\n", c.opcode)
79 }
80 case 0x1000:
81 // 1NNN Jumps to address NNN
82 // c.pc = c.opcode & 0x0FFF
83 c.pc = nnn
84 break
85 case 0x2000:
86 // 2NNN Calls subroutine at NNN
87 c.stack[c.sp] = c.pc
88 c.sp++
89 // c.pc = c.opcode & 0x0FFF
90 c.pc = nnn
91 break
92 case 0x3000:
93 // 3XNN Skips the next instruction if VX equals NN. (Usually
94 // the next instruction is a jump to skip a code block)
95 fmt.Println(c.v[x], nn)
96 if c.v[x] == nn {
97 c.pc += 2
98 }
99 c.pc += 2
100 break
101 case 0x4000:
102 // 4XNN Skips the next instruction if VX doesn't equal NN.
103 // (Usually the next instruction is a jump to skip a code
104 // block)
105 if c.v[x] != nn {
106 c.pc += 2
107 }
108 c.pc += 2
109 break
110 case 0x5000:
111 // 5XY0 Skips the next instruction if VX equals VY. (Usually
112 // the next instruction is a jump to skip a code block)
113 if c.v[x] != c.v[y] {
114 c.pc += 2
115 }
116 c.pc += 2
117 break
118 case 0x6000:
119 // 6XNN Sets VX to NN
120 c.v[x] = nn
121 c.pc += 2
122 break
123 case 0x7000:
124 // 7XNN Adds NN to VX. (Carry flag is not changed)
125 // c.v[c.opcode&0x0F00>>8] += byte(c.opcode)
126 c.v[x] += nn
127 c.pc += 2
128 break
129 case 0x8000:
130 switch c.opcode & 0x000F {
131 case 0x0000:
132 // 0x8XY0 Sets VX to the value of VY
133 c.v[x] = c.v[y]
134 c.pc += 2
135 case 0x0001:
136 // 0x8XY1 Sets VX to VX or VY. (Bitwise OR operation)
137 c.v[x] = (c.v[x] | c.v[y])
138 c.pc += 2
139 case 0x0002:
140 // 0x8XY2 Sets VX to VX and VY. (Bitwise AND operation)
141 c.v[x] = (c.v[x] & c.v[y])
142 c.pc += 2
143 case 0x0003:
144 // 0x8XY3 Sets VX to VX xor VY
145 c.v[x] = (c.v[x] ^ c.v[y])
146 c.pc += 2
147 case 0x0004:
148 // 0x8XY4 Adds VY to VX. VF is set to 1 when there's a
149 // carry, and to 0 when there isn't
150 if c.v[(c.opcode&0x00F0)>>4] > (0xFF - c.v[c.opcode&0x0F00]) {
151 c.v[0xF] = 1
152 } else {
153 c.v[0xF] = 0
154 }
155 c.v[(c.opcode&0x0F00)>>8] += c.v[(c.opcode&0x00F0)>>4]
156 c.pc += 2
157 break
158 case 0x0005:
159 // 0x8XY5 VY is subtracted from VX. VF is set to 0 when
160 // there's a borrow, and 1 when there isn't
161 if c.v[x] > c.v[y] {
162 c.v[0xF] = 0x1
163 } else {
164 c.v[0xF] = 0x0
165 }
166 c.v[x] -= c.v[y]
167 c.pc += 2
168 case 0x0006:
169 // 0x8XY6 Stores the least significant bit of VX in VF
170 // and then shifts VX to the right by 1
171 if c.opcode&0x1 >= 1 {
172 c.v[0xF] = 1
173 } else {
174 c.v[0xF] = 0
175 }
176 c.v[x] = c.v[x] >> 1
177 c.pc += 2
178 case 0x0007:
179 // 0x8XY7 Sets VX to VY minus VX. VF is set to 0 when
180 // there's a borrow, and 1 when there isn't
181 if c.v[y] > c.v[x] {
182 c.v[0xF] = 0x1
183 } else {
184 c.v[0xF] = 0x0
185 }
186 c.v[x] = c.v[y] - c.v[x]
187 c.pc += 2
188 case 0x000E:
189 // 0x8XYE Stores the most significant bit of VX in VF
190 // and then shifts VX to the left by 1
191 if c.opcode&0x80 == 0x80 {
192 c.v[0xF] = 1
193 } else {
194 c.v[0xF] = 0
195 }
196 c.v[x] = c.v[x] << 1
197 c.pc += 2
198 default:
199 fmt.Printf("Unknown opcode [0x8000]: 0x%X\n", c.opcode)
200 }
201 case 0x9000:
202 // 9XY0 Skips the next instruction if VX doesn't equal VY.
203 // (Usually the next instruction is a jump to skip a code
204 // block)
205 if c.v[x] != c.v[y] {
206 c.pc += 2
207 }
208 c.pc += 2
209 case 0xA000:
210 // ANNN set index to NNN position
211 c.index = c.opcode & 0x0FFF
212 c.pc += 2
213 break
214 case 0xB000:
215 // BNNN Jumps to the address NNN plus V0
216 c.pc = nnn + uint16(c.v[0])
217 case 0xC000:
218 // CXNN Sets VX to the result of a bitwise and operation on a
219 // random number (Typically: 0 to 255) and NN
220 r := byte(rand.Intn(255))
221 c.v[x] = r & nn
222 c.pc += 2
223 case 0xD000:
224 // DXYN Draws a sprite at coordinate (VX, VY) that has a width
225 // of 8 pixels and a height of N+1 pixels. Each row of 8 pixels
226 // is read as bit-coded starting from memory location I; I
227 // value doesn’t change after the execution of this
228 // instruction. As described above, VF is set to 1 if any
229 // screen pixels are flipped from set to unset when the sprite
230 // is drawn, and to 0 if that doesn’t happen
231 fmt.Printf("D: %X\n", c.opcode)
232 height := c.opcode & 0x000F
233
234 var pixel byte
235 c.v[0xF] = 0
236 for yline := uint16(0); yline < height; yline++ {
237 pixel = c.memory[c.index+yline]
238 for xline := uint16(0); xline < 8; xline++ {
239 if (pixel & (0x80 >> xline)) != 0 {
240 if c.gfx[(w*(uint16(y)+yline)+(uint16(x)+xline))] == 1 {
241 c.v[0xF] = 1
242 }
243 c.gfx[w*(uint16(y)+yline)+(uint16(x)+xline)] ^= 1
244 }
245 }
246 }
247 // for yline := uint16(0); yline < height; yline++ {
248 // pixel = c.memory[c.index+yline]
249 // for xline := uint16(0); xline < 8; xline++ {
250 // if (pixel & (0x80 >> xline)) != 0 {
251 // if c.gfx[(uint16(x)+xline+((uint16(y)+yline)*64))] == 1 {
252 // c.v[0xF] = 1
253 // }
254 // c.gfx[uint16(x)+xline+((uint16(y)+yline)*64)] ^= 1
255 // }
256 // }
257 //
258 // }
259 c.drawFlag = true
260 c.pc += 2
261 break
262 case 0xE000:
263 switch c.opcode & 0x00FF {
264 case 0x009E:
265 // EX9E Skips the next instruction if the key stored in
266 // VX is pressed. (Usually the next instruction is a
267 // jump to skip a code block)
268 if c.key[c.v[(c.opcode&0x0F00)>>8]] != 0 {
269 c.pc += 4
270 } else {
271 c.pc += 2
272 }
273 break
274 case 0x00A1:
275 // EXA1 Skips the next instruction if the key stored in
276 // VX isn't pressed. (Usually the next instruction is a
277 // jump to skip a code block)
278 // TODO
279 c.pc += 2
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 // TODO
293 c.pc += 2
294 case 0x0015:
295 // FX15 Sets the delay timer to VX
296 c.delayTimer = c.v[x]
297 c.pc += 2
298 case 0x0018:
299 // FX18 Sets the sound timer to VX
300 c.soundTimer = c.v[x]
301 c.pc += 2
302 case 0x001E:
303 // FX1E Adds VX to I. VF is not affected
304 c.index += uint16(c.v[x])
305 c.pc += 2
306 case 0x0029:
307 // 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
308 c.index = uint16(c.v[x]) * 5
309 c.pc += 2
310 break
311 case 0x0033:
312 c.memory[c.index] = c.v[x] / 100
313 c.memory[c.index+1] = (c.v[x] / 10) % 10
314 c.memory[c.index+2] = (c.v[x] / 100) % 10
315 c.pc += 2
316 break
317 case 0x0055:
318 // FX55 Stores V0 to VX (including VX) in memory
319 // starting at address I. The offset from I is
320 // increased by 1 for each value written, but I itself
321 // is left unmodified
322 for i := uint16(0); i <= uint16(x); i++ {
323 c.memory[c.index+i] = c.v[i]
324 }
325 c.pc += 2
326 case 0x0065:
327 // 0xFX65 Fills V0 to VX (including VX) with values
328 // from memory starting at address I. The offset from I
329 // is increased by 1 for each value written, but I
330 // itself is left unmodified
331 for i := uint16(0); i < uint16(x)+1; i++ {
332 c.v[i] = c.memory[c.index+i]
333 }
334 c.pc += 2
335 break
336 }
337 break
338 default:
339 fmt.Printf("Unknown opcode: 0x%X\n", c.opcode)
340 }
341
342 // Update timers
343 if c.delayTimer > 0 {
344 c.delayTimer--
345 }
346 if c.soundTimer > 0 {
347 if c.soundTimer == 1 {
348 fmt.Printf("Beep!\n")
349 }
350 c.soundTimer--
351 }
352}
353
354func (c *chip8) loadGame(filepath string) error {
355 buffer, err := ioutil.ReadFile(filepath)
356 if err != nil {
357 return err
358 }
359
360 for i := 0; i < len(buffer); i++ {
361 // 0x200 == 512
362 c.memory[512+i] = buffer[i]
363 }
364 return nil
365}
366
367var fontSet = [80]byte{
368 0xF0, 0x90, 0x90, 0x90, 0xF0, // 0
369 0x20, 0x60, 0x20, 0x20, 0x70, // 1
370 0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2
371 0xF0, 0x10, 0xF0, 0x10, 0xF0, // 3
372 0x90, 0x90, 0xF0, 0x10, 0x10, // 4
373 0xF0, 0x80, 0xF0, 0x10, 0xF0, // 5
374 0xF0, 0x80, 0xF0, 0x90, 0xF0, // 6
375 0xF0, 0x10, 0x20, 0x40, 0x40, // 7
376 0xF0, 0x90, 0xF0, 0x90, 0xF0, // 8
377 0xF0, 0x90, 0xF0, 0x10, 0xF0, // 9
378 0xF0, 0x90, 0xF0, 0x90, 0x90, // A
379 0xE0, 0x90, 0xE0, 0x90, 0xE0, // B
380 0xF0, 0x80, 0x80, 0x80, 0xF0, // C
381 0xE0, 0x90, 0x90, 0x90, 0xE0, // D
382 0xF0, 0x80, 0xF0, 0x80, 0xF0, // E
383 0xF0, 0x80, 0xF0, 0x80, 0x80, // F
384}
385
386func (c *chip8) setupInput() {
387}
388
389func (c *chip8) setupGraphics() {
390 window, err := sdl.CreateWindow("go-chip8", sdl.WINDOWPOS_UNDEFINED,
391 sdl.WINDOWPOS_UNDEFINED, w, h, sdl.WINDOW_SHOWN)
392 if err != nil {
393 panic(err)
394 }
395 c.renderer, err = sdl.CreateRenderer(window, -1, 0)
396 if err != nil {
397 panic(err)
398 }
399}
400
401func (c *chip8) drawGraphics() {
402 c.renderer.SetDrawColor(255, 255, 255, 1)
403 x := 0
404 y := 0
405 for i := 0; i < len(c.gfx); i++ {
406 if i%w == 0 {
407 // jump line
408 x = 0
409 y++
410 }
411 if c.gfx[i]^1 == 1 {
412 c.renderer.DrawPoint(int32(x), int32(y))
413 }
414 }
415 c.renderer.Present()
416 c.drawFlag = false
417}
418
419func (c *chip8) setKeys() {
420
421}
422
423func main() {
424 filepath := flag.String("file", "file-path", "file path of the input file")
425
426 flag.Parse()
427
428 var c chip8
429 c.setupGraphics()
430 c.setupInput()
431
432 c.initialize()
433 err := c.loadGame(*filepath)
434 if err != nil {
435 panic(err)
436 }
437
438 for {
439 c.emulateCycle()
440 if c.drawFlag {
441 c.drawGraphics()
442 c.setKeys()
443 }
444 }
445}