diff --git a/gb/cpu.go b/gb/cpu.go index 93ba4ec..322b047 100644 --- a/gb/cpu.go +++ b/gb/cpu.go @@ -30,16 +30,20 @@ type Registers struct { } type CPU struct { - Bus *Bus - Regs Registers - Halted bool - Stepping bool + Bus *Bus + Regs Registers + Halted bool + Stepping bool + InterruptMasterEnable bool } func NewCPU(bus *Bus) *CPU { cpu := CPU{} cpu.Bus = bus - cpu.Regs = Registers{} + // NOTE(m): PC is usually set to 0x100 by the boot rom + // TODO(m): SP is usually set programmatically by the cartridge code. + // Remove this hardcoded value later! + cpu.Regs = Registers{PC: 0x100, SP: 0xDFFF} cpu.Stepping = true return &cpu @@ -48,15 +52,34 @@ func NewCPU(bus *Bus) *CPU { func (cpu *CPU) Step() { if !cpu.Halted { opcode := cpu.Bus.Read(cpu.Regs.PC) - cpu.Regs.PC++ fmt.Printf("%04X: (%02X %02X %02X) A: %02X B: %02X C: %02X\n", cpu.Regs.PC, - opcode, cpu.Bus.Read(cpu.Regs.PC), cpu.Bus.Read(cpu.Regs.PC+1), cpu.Regs.A, cpu.Regs.B, cpu.Regs.C) + opcode, cpu.Bus.Read(cpu.Regs.PC+1), cpu.Bus.Read(cpu.Regs.PC+2), cpu.Regs.A, cpu.Regs.B, cpu.Regs.C) + + cpu.Regs.PC++ switch opcode { case 0x00: // NOP + + case 0x21: + // LD HL, n16 + cpu.Regs.L = cpu.Bus.Read(cpu.Regs.PC) + // emu_cycles(1); + cpu.Regs.H = cpu.Bus.Read(cpu.Regs.PC + 1) + // emu_cycles(1); + cpu.Regs.PC += 2 + + case 0x31: + // LD SP, n16 + lo := cpu.Bus.Read(cpu.Regs.PC) + // emu_cycles(1); + hi := cpu.Bus.Read(cpu.Regs.PC + 1) + // emu_cycles(1); + cpu.Regs.SP = uint16(lo) | uint16(hi)<<8 + cpu.Regs.PC += 2 + case 0x3C: // INC A cpu.Regs.A++ @@ -75,19 +98,49 @@ func (cpu *CPU) Step() { } else { cpu.ClearFlag(H) } + + case 0xCB: + // Prefix byte instructions + cbOpcode := cpu.Bus.Read(cpu.Regs.PC) + + fmt.Printf("%04X: (%02X %02X %02X) A: %02X B: %02X C: %02X\n", cpu.Regs.PC, + cbOpcode, cpu.Bus.Read(cpu.Regs.PC+1), cpu.Bus.Read(cpu.Regs.PC+2), cpu.Regs.A, cpu.Regs.B, cpu.Regs.C) + + cpu.Regs.PC++ + + switch cbOpcode { + default: + fmt.Printf("\nINVALID INSTRUCTION! Unknown CB opcode: %02X\n", cbOpcode) + os.Exit(1) + } + case 0xC3: // JP a16 lo := cpu.Bus.Read(cpu.Regs.PC) // emu_cycles(1); hi := cpu.Bus.Read(cpu.Regs.PC + 1) // emu_cycles(1); - cpu.Regs.PC = uint16(hi)<<8 | uint16(lo) + cpu.Regs.PC = uint16(lo) | uint16(hi)<<8 + + case 0xCD: + // CALL a16 + cpu.StackPush16(cpu.Regs.PC + 2) + + lo := cpu.Bus.Read(cpu.Regs.PC) + // emu_cycles(1); + hi := cpu.Bus.Read(cpu.Regs.PC + 1) + // emu_cycles(1); + cpu.Regs.PC = uint16(lo) | uint16(hi)<<8 case 0xE9: // JP HL val := uint16(cpu.Regs.H)<<8 | uint16(cpu.Regs.L) cpu.Regs.PC = val + case 0xF3: + // DI + cpu.InterruptMasterEnable = false + default: fmt.Printf("\nINVALID INSTRUCTION! Unknown opcode: %02X\n", opcode) os.Exit(1) diff --git a/gb/cpu_test.go b/gb/cpu_test.go index 637d0a4..5eed4fe 100644 --- a/gb/cpu_test.go +++ b/gb/cpu_test.go @@ -54,16 +54,16 @@ func TestInstruction00(t *testing.T) { cpu.Step() - assert.Equal(t, cpu.Regs.PC, uint16(0x01)) + assert.Equal(t, uint16(0x01), cpu.Regs.PC) } func TestInstruction3C(t *testing.T) { // Should increment A register cpu := createCPU([]byte{0x3C, 0x00, 0x00}) - assert.Equal(t, cpu.Regs.A, byte(0x0)) + assert.Equal(t, byte(0x0), cpu.Regs.A) cpu.Step() - assert.Equal(t, cpu.Regs.A, byte(0x01)) + assert.Equal(t, byte(0x01), cpu.Regs.A) // Should clear Zero Flag cpu = createCPU([]byte{0x3C, 0x00, 0x00}) @@ -108,7 +108,7 @@ func TestInstructionC3(t *testing.T) { cpu.Step() - assert.Equal(t, cpu.Regs.PC, uint16(0x150)) + assert.Equal(t, uint16(0x150), cpu.Regs.PC) } func TestInstructionE9(t *testing.T) { @@ -118,5 +118,52 @@ func TestInstructionE9(t *testing.T) { cpu.Step() - assert.Equal(t, cpu.Regs.PC, uint16(0x1234)) + assert.Equal(t, uint16(0x1234), cpu.Regs.PC) +} + +func TestInstructionF3(t *testing.T) { + cpu := createCPU([]byte{0xF3, 0x00, 0x00}) + + cpu.InterruptMasterEnable = true + cpu.Step() + + assert.False(t, cpu.InterruptMasterEnable) +} + +func TestInstruction31(t *testing.T) { + cpu := createCPU([]byte{0x31, 0xFF, 0xCF}) + + cpu.Step() + + // Should load SP with n16 value + assert.Equal(t, uint16(0xCFFF), cpu.Regs.SP) + + // Should step over the 16 bit value onto the next instruction, i.e. increase the program counter with 3. + assert.Equal(t, uint16(0x0003), cpu.Regs.PC) +} + +func TestInstructionCD(t *testing.T) { + cpu := createCPU([]byte{0xCD, 0x4C, 0x48, 0x41}) + + cpu.Step() + + // Should push the address of the next instruction onto the stack + // In this case 0x41 which is at address 0x03 (i.e. the 4th instruction in the row above creating the CPU instance) + assert.Equal(t, uint16(0x03), cpu.Bus.Read16(cpu.Regs.SP)) + + // Should jump to n16 value + assert.Equal(t, uint16(0x484C), cpu.Regs.PC) +} + +func TestInstruction21(t *testing.T) { + cpu := createCPU([]byte{0x21, 0x34, 0x12}) + + cpu.Step() + + // Should load the 16-bit immediate value into the combined HL register + assert.Equal(t, byte(0x12), cpu.Regs.H) + assert.Equal(t, byte(0x34), cpu.Regs.L) + + // Should increase the stack pointer + assert.Equal(t, uint16(0x3), cpu.Regs.PC) }