Implement more CPU instructions
This commit is contained in:
parent
82d3898216
commit
c18615c629
69
gb/cpu.go
69
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)
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user