go-chip8/chip8/chip8.go
chip8/chip8.goRaw
1package chip8
2
3import (
4 "fmt"
5 "io/ioutil"
6 "math/rand"
7)
8
9// W represents the width of the screen
10const W = 64
11
12// H represents the height of the screen
13const H = 32
14
15// Chip8 contains all the data and methods for the Chip8 emulator
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
39// Initialize registers and memory
40func (c *Chip8) Initialize() {
41 c.pc = 0x200
42 c.opcode = 0
43 c.index = 0
44 c.sp = 0
45
46 for i := 0; i < len(fontSet); i++ {
47 c.memory[i] = fontSet[i]
48 }
49}
50
51// EmulateCycle emulates the chip8 cycle
52func (c *Chip8) EmulateCycle() {
53 // Fetch Opcode
54 c.opcode = uint16(c.memory[c.pc])<<8 | uint16(c.memory[c.pc+1])
55 x := byte((c.opcode & 0x0F00) >> 8)
56 y := byte((c.opcode & 0x00F0) >> 4)
57 nn := byte(c.opcode & 0x00FF)
58 nnn := uint16(c.opcode & 0x0FFF)
59
60 // Decode Opcode
61 // https://en.wikipedia.org/wiki/CHIP-8#Opcode_table
62 // http://www.multigesture.net/wp-content/uploads/mirror/goldroad/chip8_instruction_set.shtml
63 switch c.opcode & 0xF000 {
64 case 0x0000:
65 switch c.opcode & 0x000F {
66 case 0x0000:
67 // 00E0 Clear screen
68 for i := 0; i < len(c.Gfx); i++ {
69 c.Gfx[i] = 0
70 }
71 c.pc += 2
72 c.DrawFlag = true
73 break
74 case 0x000E:
75 // 00EE Returns from a subroutine
76 c.sp--
77 c.pc = c.stack[c.sp]
78 c.pc += 2
79 break
80 default:
81 fmt.Printf("Unknown opcode [0x0000]: 0x%X\n", c.opcode)
82 }
83 case 0x1000:
84 // 1NNN Jumps to address NNN
85 c.pc = nnn
86 break
87 case 0x2000:
88 // 2NNN Calls subroutine at NNN
89 c.stack[c.sp] = c.pc
90 c.sp++
91 c.pc = nnn
92 break
93 case 0x3000:
94 // 3XNN Skips the next instruction if VX equals NN. (Usually
95 // the next instruction is a jump to skip a code block)
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[x] += nn
126 c.pc += 2
127 break
128 case 0x8000:
129 switch c.opcode & 0x000F {
130 case 0x0000:
131 // 0x8XY0 Sets VX to the value of VY
132 c.v[x] = c.v[y]
133 c.pc += 2
134 case 0x0001:
135 // 0x8XY1 Sets VX to VX or VY. (Bitwise OR operation)
136 c.v[x] = (c.v[x] | c.v[y])
137 c.pc += 2
138 case 0x0002:
139 // 0x8XY2 Sets VX to VX and VY. (Bitwise AND operation)
140 c.v[x] = (c.v[x] & c.v[y])
141 c.pc += 2
142 case 0x0003:
143 // 0x8XY3 Sets VX to VX xor VY
144 c.v[x] = (c.v[x] ^ c.v[y])
145 c.pc += 2
146 case 0x0004:
147 // 0x8XY4 Adds VY to VX. VF is set to 1 when there's a
148 // carry, and to 0 when there isn't
149 if c.v[y] > (0xFF - c.v[x]) {
150 c.v[0xF] = 1
151 } else {
152 c.v[0xF] = 0
153 }
154 c.v[x] += c.v[y]
155 c.pc += 2
156 break
157 case 0x0005:
158 // 0x8XY5 VY is subtracted from VX. VF is set to 0 when
159 // there's a borrow, and 1 when there isn't
160 if c.v[x] > c.v[y] {
161 c.v[0xF] = 0x1
162 } else {
163 c.v[0xF] = 0x0
164 }
165 c.v[x] -= c.v[y]
166 c.pc += 2
167 case 0x0006:
168 // 0x8XY6 Stores the least significant bit of VX in VF
169 // and then shifts VX to the right by 1
170 if c.opcode&0x1 >= 1 {
171 c.v[0xF] = 1
172 } else {
173 c.v[0xF] = 0
174 }
175 c.v[x] = c.v[x] >> 1
176 c.pc += 2
177 case 0x0007:
178 // 0x8XY7 Sets VX to VY minus VX. VF is set to 0 when
179 // there's a borrow, and 1 when there isn't
180 if c.v[y] > c.v[x] {
181 c.v[0xF] = 0x1
182 } else {
183 c.v[0xF] = 0x0
184 }
185 c.v[x] = c.v[y] - c.v[x]
186 c.pc += 2
187 case 0x000E:
188 // 0x8XYE Stores the most significant bit of VX in VF
189 // and then shifts VX to the left by 1
190 if c.opcode&0x80 == 0x80 {
191 c.v[0xF] = 1
192 } else {
193 c.v[0xF] = 0
194 }
195 c.v[x] = c.v[x] << 1
196 c.pc += 2
197 default:
198 fmt.Printf("Unknown opcode [0x8000]: 0x%X\n", c.opcode)
199 }
200 case 0x9000:
201 // 9XY0 Skips the next instruction if VX doesn't equal VY.
202 // (Usually the next instruction is a jump to skip a code
203 // block)
204 if c.v[x] != c.v[y] {
205 c.pc += 2
206 }
207 c.pc += 2
208 case 0xA000:
209 // ANNN set index to NNN position
210 c.index = nnn
211 c.pc += 2
212 break
213 case 0xB000:
214 // BNNN Jumps to the address NNN plus V0
215 c.pc = nnn + uint16(c.v[0])
216 case 0xC000:
217 // CXNN Sets VX to the result of a bitwise and operation on a
218 // random number (Typically: 0 to 255) and NN
219 r := byte(rand.Intn(255))
220 c.v[x] = r & nn
221 c.pc += 2
222 case 0xD000:
223 // DXYN Draws a sprite at coordinate (VX, VY) that has a width
224 // of 8 pixels and a height of N+1 pixels. Each row of 8 pixels
225 // is read as bit-coded starting from memory location I; I
226 // value doesn’t change after the execution of this
227 // instruction. As described above, VF is set to 1 if any
228 // screen pixels are flipped from set to unset when the sprite
229 // is drawn, and to 0 if that doesn’t happen
230
231 height := c.opcode & 0x000F
232
233 var pixel byte
234 c.v[0xF] = 0
235
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 pos := (uint16(c.v[x]) + xline) + ((uint16(c.v[y]) + yline) * W)
241 if pos >= 2048 {
242 break
243 }
244 if c.Gfx[pos] == 1 {
245 c.v[0xF] = 1
246 }
247 c.Gfx[pos] ^= 1
248 }
249 }
250
251 }
252
253 c.DrawFlag = true
254 c.pc += 2
255 break
256 case 0xE000:
257 switch c.opcode & 0x00FF {
258 case 0x009E:
259 // EX9E Skips the next instruction if the key stored in
260 // VX is pressed. (Usually the next instruction is a
261 // jump to skip a code block)
262 if c.Key[c.v[x]] != 0 {
263 c.pc += 2
264 }
265 c.pc += 2
266 break
267 case 0x00A1:
268 // EXA1 Skips the next instruction if the key stored in
269 // VX isn't pressed. (Usually the next instruction is a
270 // jump to skip a code block)
271 if c.Key[c.v[x]] != 1 {
272 c.pc += 2
273 }
274 c.pc += 2
275 default:
276 fmt.Printf("Unknown opcode [0xE000]: 0x%X\n", c.opcode)
277 }
278 break
279 case 0xF000:
280 switch c.opcode & 0x00FF {
281 case 0x0007:
282 // FX07 Sets VX to the value of the delay timer
283 c.v[x] = c.delayTimer
284 c.pc += 2
285 case 0x000A:
286 // FX0A A key press is awaited, and then stored in VX.
287 // (Blocking Operation. All instruction halted until
288 // next key event)
289 pressed := false
290 for i := 0; i < 16; i++ {
291 if c.Key[i] == 1 {
292 c.v[x] = byte(i)
293 pressed = true
294 }
295 }
296 if !pressed {
297 return
298 }
299 c.pc += 2
300 case 0x0015:
301 // FX15 Sets the delay timer to VX
302 c.delayTimer = c.v[x]
303 c.pc += 2
304 case 0x0018:
305 // FX18 Sets the sound timer to VX
306 c.soundTimer = c.v[x]
307 c.pc += 2
308 case 0x001E:
309 // FX1E Adds VX to I. VF is not affected
310 c.index += uint16(c.v[x])
311 c.pc += 2
312 case 0x0029:
313 // FX29 Sets I to the location of the sprite for the
314 // character in VX. Characters 0-F (in hexadecimal) are
315 // represented by a 4x5 font
316 c.index = uint16(c.v[x]) * 5
317 c.pc += 2
318 break
319 case 0x0033:
320 c.memory[c.index] = c.v[x] / 100
321 c.memory[c.index+1] = (c.v[x] / 10) % 10
322 c.memory[c.index+2] = (c.v[x] / 100) % 10
323 c.pc += 2
324 break
325 case 0x0055:
326 // FX55 Stores V0 to VX (including VX) in memory
327 // starting at address I. The offset from I is
328 // increased by 1 for each value written, but I itself
329 // is left unmodified
330 for i := uint16(0); i <= uint16(x); i++ {
331 c.memory[c.index+i] = c.v[i]
332 }
333 c.pc += 2
334 case 0x0065:
335 // 0xFX65 Fills V0 to VX (including VX) with values
336 // from memory starting at address I. The offset from I
337 // is increased by 1 for each value written, but I
338 // itself is left unmodified
339 for i := uint16(0); i < uint16(x)+1; i++ {
340 c.v[i] = c.memory[c.index+i]
341 }
342 c.pc += 2
343 break
344 default:
345 fmt.Printf("Unknown opcode [0xF000]: 0x%X\n", c.opcode)
346 }
347 break
348 default:
349 fmt.Printf("Unknown opcode: 0x%X\n", c.opcode)
350 }
351
352 // Update timers
353 if c.delayTimer > 0 {
354 c.delayTimer--
355 }
356 if c.soundTimer > 0 {
357 if c.soundTimer == 1 {
358 fmt.Printf("Beep!\n")
359 }
360 c.soundTimer--
361 }
362}
363
364// LoadGame loads the rom file of the given file path into the Chip8 memory
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}