package chip8 import ( "fmt" "math/rand" ) const graphicsBufferSize = 64 * 32 type Chip8 struct { addressRegister uint16 beepTimer byte drawRequired bool delayTimer byte graphics [graphicsBufferSize]byte keys [16]byte memory [4096]byte opcode uint16 pc uint16 registers [16]byte // an array - has a fixed length stack []uint16 // a slice - basically a c++ vector } // we can't have const arrays in go // This is a good a solution as any func getLetters() []byte { return []byte{ 0xF0, 0x90, 0x90, 0x90, 0xF0, // 0 0x20, 0x60, 0x20, 0x20, 0x70, // 1 0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2 0xF0, 0x10, 0xF0, 0x10, 0xF0, // 3 0x90, 0x90, 0xF0, 0x10, 0x10, // 4 0xF0, 0x80, 0xF0, 0x10, 0xF0, // 5 0xF0, 0x80, 0xF0, 0x90, 0xF0, // 6 0xF0, 0x10, 0x20, 0x40, 0x40, // 7 0xF0, 0x90, 0xF0, 0x90, 0xF0, // 8 0xF0, 0x90, 0xF0, 0x10, 0xF0, // 9 0xF0, 0x90, 0xF0, 0x90, 0x90, // A 0xE0, 0x90, 0xE0, 0x90, 0xE0, // B 0xF0, 0x80, 0x80, 0x80, 0xF0, // C 0xE0, 0x90, 0x90, 0x90, 0xE0, // D 0xF0, 0x80, 0xF0, 0x80, 0xF0, // E 0xF0, 0x80, 0xF0, 0x80, 0x80} // F } func NewCHIP8(prog []byte) *Chip8 { cpu := Chip8{pc: 0x200} memory_slice := cpu.memory[:80] copy(memory_slice, getLetters()) memory_slice = cpu.memory[200:] copy(memory_slice, prog) // Do some extra checking to ensure the right // Stuff is copied return &cpu } func (cpu *Chip8) GetGraphicsBuffer() [graphicsBufferSize]byte { cpu.drawRequired = false return cpu.graphics } func (cpu *Chip8) DrawIsNeeded() bool { return cpu.drawRequired } func (cpu *Chip8) TickTimers() { if cpu.delayTimer > 0 { cpu.delayTimer-- } if cpu.beepTimer > 0 { cpu.beepTimer-- } } func (cpu *Chip8) zeroIndexOpcodes() { if cpu.opcode == 0x00E0 { // fuck it the gc can do the hard work for us cpu.graphics = [64 * 32]byte{} cpu.drawRequired = true } else if cpu.opcode == 0x00EE { // what if there's nothing in the stack? // return something sensible cpu.pc = cpu.stack[len(cpu.stack)-1] cpu.stack[len(cpu.stack)-1] = 0 cpu.stack = cpu.stack[:len(cpu.stack)-1] } } func (cpu *Chip8) goTo() { // GOTO 1NNN cpu.pc = cpu.opcode & 0x0FFF } func (cpu *Chip8) callSubroutine() { // Call subroutine at 0x2NNN cpu.stack = append(cpu.stack, cpu.pc) cpu.pc = cpu.opcode & 0x0FFF } func (cpu *Chip8) skipIfRegisterEqual() { // 3XNN // If register x == NN skip next instruction r := (cpu.opcode) >> 8 & 0x0F if cpu.registers[r] == byte(cpu.opcode&0xFF) { cpu.pc += 2 } } func (cpu *Chip8) skipIfRegisterNotEqual() { // 4XNN // If register x != NN skip next instruction r := (cpu.opcode) >> 8 & 0x0F if cpu.registers[r] != byte(cpu.opcode&0xFF) { cpu.pc += 2 } } func (cpu *Chip8) skipIfRegistersEqual() { // 5XY0 // If register X == register Y skip next instruction x, y := (cpu.opcode>>8)&0x0F, (cpu.opcode>>4)&0x0F if cpu.registers[x] == cpu.registers[y] { cpu.pc += 2 } } func (cpu *Chip8) setRegisterTo() { // 6XNN // Set register X to NN r := (cpu.opcode >> 8) & 0x0F cpu.registers[r] = byte(cpu.opcode & 0xFF) } func (cpu *Chip8) registerPlusEqualsNN() { // 7XNN // register X += NN (carry flag not set) reg := (cpu.opcode >> 8) & 0x0F cpu.registers[reg] += uint8(cpu.opcode & 0xFF) } func (cpu *Chip8) bitOpsAndMath() { instruction := cpu.opcode & 0x0F regX, regY := cpu.opcode>>8&0x0F, cpu.opcode>>4&0x0F switch instruction { case 0: // 8XY0 // Assign value of register Y to register X cpu.registers[regX] = cpu.registers[regY] case 1: // 8XY1 // Set register X to x|Y cpu.registers[regX] = cpu.registers[regX] | cpu.registers[regY] case 2: // 8XY2 // Set register x to X&Y cpu.registers[regX] = cpu.registers[regX] & cpu.registers[regY] case 3: // 8XY3 // Set register x to X^Y cpu.registers[regX] = cpu.registers[regX] ^ cpu.registers[regY] case 4: // 8XY4 // Set register x to X+=Y (Set VF to 1 when there's a carry and 0 if not) res := cpu.registers[regX] + cpu.registers[regY] if res > 0xFF { cpu.registers[0x0F] = 1 } else { cpu.registers[0x0F] = 0 } cpu.registers[regX] = res & 0xFF case 5: // 8XY5 // Set register x to X-=Y (Set VF to 1 when there's a carry and 0 if not) res := cpu.registers[regX] - cpu.registers[regY] if res > 0xFF { cpu.registers[0x0F] = 1 } else { cpu.registers[0x0F] = 0 } cpu.registers[regX] = res & 0xFF case 6: // BXY6 // Store lsb of reg X in reg F and then shift reg X >> 1 cpu.registers[0x0F] = cpu.registers[regX] & 0x01 cpu.registers[regX] >>= 1 case 7: // 8XY57 // Set register x to X=Y-X (Set VF to 1 when there's a carry and 0 if not) res := cpu.registers[regY] - cpu.registers[regX] if res < 0 { cpu.registers[0x0F] = 0 } else { cpu.registers[0x0f] = 1 } cpu.registers[regX] = res & 0xFF } } func (cpu *Chip8) skipIfRegistersNotEqual() { x, y := (cpu.opcode>>8)&0x0F, (cpu.opcode>>4)&0x0F if cpu.registers[x] != cpu.registers[y] { cpu.pc += 2 } } func (cpu *Chip8) setAddressRegister() { // ANNN // Sets the address register to NNN cpu.addressRegister = cpu.opcode & 0x0FFF } func (cpu *Chip8) jumpToV0PlusAddress() { // BNNN // PC=V0+NNN cpu.pc = uint16(cpu.registers[0]) + (cpu.opcode & 0x0FFF) } func (cpu *Chip8) setRegisterToRand() { // CXNN // Vx = rand() & NN cpu.registers[(cpu.opcode>>8)&0x0F] = byte(cpu.opcode&0xFF) & byte(rand.Intn(256)) } func (cpu *Chip8) displaySprite() { // DXYN // Draws a sprite in the graphics buffer // VF is set to 1 if any pixels are flipped and zero otherwise cpu.registers[0x0F] = 0 x := int(cpu.registers[(cpu.opcode>>8)&0x0F]) y := int(cpu.registers[(cpu.opcode>>4)&0x0F]) for row := 0; row < int(cpu.opcode&0xF); row++ { pixel := cpu.memory[int(cpu.addressRegister)+row] for column := 0; column < 8; column++ { if pixel&(0x80>>column) != 0 { graphicsPosition := x + column + ((y + row) * 60) if cpu.graphics[graphicsPosition] == 1 { cpu.registers[0xF] = 1 } cpu.graphics[graphicsPosition] ^= 1 } } } cpu.drawRequired = true } func (cpu *Chip8) skipOnKeyOpcodes() { opcode := cpu.opcode & 0xFF key := cpu.registers[(cpu.opcode>>8)&0x0F] switch opcode { case 0x9E: // EX9E // skip if key X is pressed if cpu.keys[key] != 0 { cpu.pc += 2 } case 0xA1: // EXA1 // skip if kkey X is not pressed if cpu.keys[key] == 0 { cpu.pc += 2 } } } func (cpu *Chip8) fifteenIndexOpcodes() { instruction := cpu.opcode & 0xFF reg := int(cpu.opcode>>8) & 0xF switch instruction { // FX07 // Set VX to the value of the delay timer case 0x07: cpu.registers[reg] = cpu.delayTimer case 0x0A: // FX0A // block + wait for key press found := false for i, val := range cpu.keys { if val != 0 { cpu.registers[reg] = byte(i) found = true } } if !found { cpu.pc -= 2 } case 0x15: // FX15 // Set delay timer to VX cpu.delayTimer = cpu.registers[reg] case 0x18: // FX18 // SET THE BEEP TIMER TO VX // BEEP cpu.beepTimer = cpu.registers[reg] case 0x1E: // FX1E // Add VX to the address register // Set VF to 1 when range overflow if int(cpu.registers[reg])+int(cpu.addressRegister) > 0xFFF { cpu.registers[0x0F] = 1 } else { cpu.registers[0x0F] = 0 } cpu.addressRegister = uint16((int(cpu.addressRegister) + int(cpu.registers[reg])) & 0xFFF) case 0x29: // FX29 // Sets address register to location of char stored in VX cpu.addressRegister = uint16(cpu.registers[reg]) * 5 case 0x33: // FX33 // Stores BCD representation of VX cpu.memory[cpu.addressRegister] = cpu.registers[reg] / 100 cpu.memory[cpu.addressRegister+1] = (cpu.registers[reg] / 10) % 10 cpu.memory[cpu.addressRegister+2] = cpu.registers[reg] % 10 case 0x55: // FX55 // takes values from and including reg X and stores then in memory for i := 0; i <= reg; i++ { cpu.memory[int(cpu.addressRegister)+i] = cpu.registers[i] } case 0x65: // FX65 // takes values from memory and stores them in registers for i := 0; i <= reg; i++ { cpu.registers[i] = cpu.memory[int(cpu.addressRegister)+i] } } } func (cpu *Chip8) PerformCycle() { var opcode uint16 = (uint16(cpu.memory[cpu.pc]) << 8) + uint16(cpu.memory[cpu.pc]) cpu.pc += 2 switch opcode >> 12 { case 0: cpu.zeroIndexOpcodes() case 1: cpu.goTo() case 2: cpu.callSubroutine() case 3: cpu.skipIfRegisterEqual() case 4: cpu.skipIfRegisterNotEqual() case 5: cpu.skipIfRegistersEqual() case 6: cpu.setRegisterTo() case 7: cpu.registerPlusEqualsNN() case 8: cpu.bitOpsAndMath() case 9: cpu.skipIfRegistersNotEqual() case 0xA: cpu.setAddressRegister() case 0xB: cpu.jumpToV0PlusAddress() case 0xC: cpu.setRegisterToRand() case 0xD: cpu.displaySprite() case 0xE: cpu.skipOnKeyOpcodes() case 0xF: cpu.fifteenIndexOpcodes() } } func main() { fmt.Printf("Hello world!\n") prog := []byte{1, 2, 3, 4} new_cpu := NewCHIP8(prog) fmt.Printf("%d\n", new_cpu.opcode) }