arnaucube
arnaucube/go-chip8Public Self-hosted repository
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}