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