From 7678fda9e77557971f5d5b7ca721a49c0794d44c Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Wed, 3 Sep 2025 09:11:16 +0200 Subject: [PATCH] Add more instructions. Fix JR bug. Start implementation of IO R/W --- gb/bus.go | 9 +++ gb/cpu.go | 72 +++++++++++++++++++++++- gb/cpu_test.go | 149 ++++++++++++++++++++++++++++++++++++++++++++++++- gb/io.go | 46 +++++++++++++++ 4 files changed, 272 insertions(+), 4 deletions(-) create mode 100644 gb/io.go diff --git a/gb/bus.go b/gb/bus.go index 169f1ec..87abdf9 100644 --- a/gb/bus.go +++ b/gb/bus.go @@ -39,7 +39,11 @@ func (bus *Bus) Read(address uint16) byte { } return value } else if address < 0xE000 { + //WRAM (Working RAM) return bus.RAM.WRAMRead(address) + } else if address < 0xFF80 { + //IO Registers + return IORead(address) } else { fmt.Printf("Reading from bus address %X not implemented!\n", address) return 0 @@ -62,8 +66,13 @@ func (bus *Bus) Write(address uint16, value byte) error { } return nil } else if address < 0xE000 { + //WRAM (Working RAM) bus.RAM.WRAMWrite(address, value) return nil + } else if address < 0xFF80 { + //IO Registers + IOWrite(address, value) + return nil } else { return fmt.Errorf("Writing to bus address %X not implemented!", address) } diff --git a/gb/cpu.go b/gb/cpu.go index 77c5342..c95d946 100644 --- a/gb/cpu.go +++ b/gb/cpu.go @@ -53,8 +53,9 @@ func (cpu *CPU) Step() { if !cpu.Halted { opcode := cpu.Bus.Read(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+1), cpu.Bus.Read(cpu.Regs.PC+2), cpu.Regs.A, cpu.Regs.B, cpu.Regs.C) + fmt.Printf("%04X: (%02X %02X %02X) A: %02X B: %02X C: %02X D: %02X E: %02X H: %02X L: %02X\n", cpu.Regs.PC, + 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.D, cpu.Regs.E, cpu.Regs.H, cpu.Regs.L) cpu.Regs.PC++ @@ -63,6 +64,25 @@ func (cpu *CPU) Step() { case 0x00: // NOP + case 0x0D: + // DEC C + cpu.Regs.C-- + + // Set appropriate flags + if cpu.Regs.C == 0 { + cpu.SetFlag(Z) + } else { + cpu.ClearFlag(Z) + } + + cpu.SetFlag(N) + + if (cpu.Regs.C & 0x0F) == 0x0F { + cpu.SetFlag(H) + } else { + cpu.ClearFlag(H) + } + case 0x0E: // LD C, n8 val := cpu.Bus.Read(cpu.Regs.PC) @@ -88,6 +108,25 @@ func (cpu *CPU) Step() { address := uint16(cpu.Regs.D)<<8 | uint16(cpu.Regs.E) cpu.Bus.Write(address, cpu.Regs.A) + case 0x14: + // INC D + cpu.Regs.D++ + + // Set appropriate flags + if cpu.Regs.D == 0 { + cpu.SetFlag(Z) + } else { + cpu.ClearFlag(Z) + } + + cpu.ClearFlag(N) + + if (cpu.Regs.D & 0x0F) == 0 { + cpu.SetFlag(H) + } else { + cpu.ClearFlag(H) + } + case 0x1C: // INC E cpu.Regs.E++ @@ -118,6 +157,7 @@ func (cpu *CPU) Step() { // Jump relative to 8-bit signed offset // emu_cycles(3); offset := int8(cpu.Bus.Read(cpu.Regs.PC)) + cpu.Regs.PC++ cpu.Regs.PC = uint16(int(cpu.Regs.PC) + int(offset)) } @@ -170,10 +210,21 @@ func (cpu *CPU) Step() { cpu.ClearFlag(H) } + case 0x3E: + // LD A, n8 + val := cpu.Bus.Read(cpu.Regs.PC) + // emu_cycles(1); + cpu.Regs.A = val + cpu.Regs.PC++ + case 0x47: // LD B, A cpu.Regs.B = cpu.Regs.A + case 0x78: + // LD A, B + cpu.Regs.A = cpu.Regs.B + case 0xCB: // Prefix byte instructions cbOpcode := cpu.Bus.Read(cpu.Regs.PC) @@ -226,11 +277,28 @@ func (cpu *CPU) Step() { // emu_cycles(1); cpu.Regs.PC = uint16(lo) | uint16(hi)<<8 + case 0xE0: + // LDH [a8], A + offset := cpu.Bus.Read(cpu.Regs.PC) + address := 0xFF00 | uint16(offset) + cpu.Bus.Write(address, cpu.Regs.A) + cpu.Regs.PC++ + case 0xE9: // JP HL val := uint16(cpu.Regs.H)<<8 | uint16(cpu.Regs.L) cpu.Regs.PC = val + case 0xEA: + // LD [a16], A + lo := cpu.Bus.Read(cpu.Regs.PC) + cpu.Regs.PC++ + hi := cpu.Bus.Read(cpu.Regs.PC) + cpu.Regs.PC++ + + address := uint16(lo) | uint16(hi)<<8 + cpu.Bus.Write(address, cpu.Regs.A) + case 0xF3: // DI cpu.InterruptMasterEnable = false diff --git a/gb/cpu_test.go b/gb/cpu_test.go index 3f63fe0..71aa3c4 100644 --- a/gb/cpu_test.go +++ b/gb/cpu_test.go @@ -193,6 +193,9 @@ func TestInstruction47(t *testing.T) { // Should load value in register A into register B assert.Equal(t, byte(0xDE), cpu.Regs.B) + + // Should increase the stack pointer + assert.Equal(t, uint16(0x1), cpu.Regs.PC) } func TestInstruction11(t *testing.T) { @@ -312,14 +315,14 @@ func TestInstruction20(t *testing.T) { cpu.Step() - assert.Equal(t, uint16(0x0B), cpu.Regs.PC) + assert.Equal(t, uint16(0x0C), cpu.Regs.PC) // Should jump to negative offset if Z is not set cpu = createCPU([]byte{0x20, 0xFB, 0x00}) cpu.Step() - assert.Equal(t, uint16(0xFFFC), cpu.Regs.PC) + assert.Equal(t, uint16(0xFFFD), cpu.Regs.PC) } func TestInstruction10(t *testing.T) { @@ -329,3 +332,145 @@ func TestInstruction10(t *testing.T) { assert.True(t, cpu.Halted) } + +func TestInstruction14(t *testing.T) { + // Should increment D register + cpu := createCPU([]byte{0x14, 0x00, 0x00}) + + cpu.Step() + assert.Equal(t, byte(0x01), cpu.Regs.D) + + // Should clear Zero Flag + cpu = createCPU([]byte{0x14, 0x00, 0x00}) + + cpu.SetFlag(Z) + cpu.Step() + assert.False(t, cpu.IsFlagSet(Z)) + + // Should set Zero Flag + cpu = createCPU([]byte{0x14, 0x00, 0x00}) + cpu.Regs.D = 0xFF + + cpu.Step() + assert.True(t, cpu.IsFlagSet(Z)) + + // Should clear Subtract Flag + cpu = createCPU([]byte{0x14, 0x00, 0x00}) + + cpu.SetFlag(N) + cpu.Step() + assert.False(t, cpu.IsFlagSet(N)) + + // Should set Half Carry Flag if we overflow from bit 3 + cpu = createCPU([]byte{0x14, 0x00, 0x00}) + cpu.Regs.D = 0x0F + + cpu.Step() + assert.True(t, cpu.IsFlagSet(H)) + + // Should clear Half Carry Flag if we don't overflow from bit 3 + cpu = createCPU([]byte{0x14, 0x00, 0x00}) + + cpu.SetFlag(H) + cpu.Step() + assert.False(t, cpu.IsFlagSet(H)) +} + +func TestInstruction0D(t *testing.T) { + // Should decrement C register + cpu := createCPU([]byte{0x0D, 0x00, 0x00}) + cpu.Regs.C = 0x01 + + cpu.Step() + assert.Equal(t, byte(0x00), cpu.Regs.C) + + // Should clear Zero Flag + cpu = createCPU([]byte{0x0D, 0x00, 0x00}) + + cpu.SetFlag(Z) + cpu.Step() + assert.False(t, cpu.IsFlagSet(Z)) + + // Should set Zero Flag + cpu = createCPU([]byte{0x0D, 0x00, 0x00}) + cpu.Regs.C = 0x01 + + cpu.Step() + assert.True(t, cpu.IsFlagSet(Z)) + + // Should set Subtract Flag + cpu = createCPU([]byte{0x0D, 0x00, 0x00}) + + cpu.SetFlag(N) + cpu.Step() + assert.True(t, cpu.IsFlagSet(N)) + + // Should set Half Carry Flag if we overflow from bit 3 + cpu = createCPU([]byte{0x0D, 0x00, 0x00}) + cpu.Regs.C = 0x10 + + cpu.Step() + assert.True(t, cpu.IsFlagSet(H)) + + // Should clear Half Carry Flag if we don't overflow from bit 3 + cpu = createCPU([]byte{0x0D, 0x00, 0x00}) + cpu.Regs.C = 0x01 + + cpu.SetFlag(H) + cpu.Step() + assert.False(t, cpu.IsFlagSet(H)) +} + +func TestInstruction78(t *testing.T) { + cpu := createCPU([]byte{0x78, 0x00, 0x00}) + + cpu.Regs.B = 0xDE + + cpu.Step() + + // Should load value in register B into register A + assert.Equal(t, byte(0xDE), cpu.Regs.A) + + // Should increase the stack pointer + assert.Equal(t, uint16(0x1), cpu.Regs.PC) +} + +func TestInstructionEA(t *testing.T) { + cpu := createCPU([]byte{0xEA, 0x23, 0xD1}) + cpu.Regs.A = 0x42 + + cpu.Step() + + // Should load byte in register A into the memory location pointed to by the 16-bit immediate value + val := cpu.Bus.Read(0xD123) + assert.Equal(t, byte(0x42), val) + + // Should increase the stack pointer + assert.Equal(t, uint16(0x3), cpu.Regs.PC) +} + +func TestInstruction3E(t *testing.T) { + cpu := createCPU([]byte{0x3E, 0xDE, 0x00}) + + cpu.Step() + + // Should load the 8-bit immediate value into register A + assert.Equal(t, byte(0xDE), cpu.Regs.A) + + // Should increase the stack pointer + assert.Equal(t, uint16(0x2), cpu.Regs.PC) +} + +func TestInstructionE0(t *testing.T) { + cpu := createCPU([]byte{0xE0, 0x07, 0x00}) + cpu.Regs.A = 0x42 + + cpu.Step() + + // Should copy the value in register A into the byte at address 0xFF00 + 0x07 + val := cpu.Bus.Read(0xFF07) + assert.Equal(t, byte(0x42), val) + + // Should increase the stack pointer + assert.Equal(t, uint16(0x2), cpu.Regs.PC) +} diff --git a/gb/io.go b/gb/io.go new file mode 100644 index 0000000..7803936 --- /dev/null +++ b/gb/io.go @@ -0,0 +1,46 @@ +package gb + +import ( + "fmt" + "os" +) + +var SerialData [2]byte + +func IORead(address uint16) byte { + if address == 0xFF01 { + return SerialData[0] + } + + if address == 0xFF02 { + return SerialData[1] + } + + if (address >= 0xFF04) && (address <= 0xFF07) { + fmt.Printf("Reading from IO: invalid address %X. Timer not yet implemented!", address) + os.Exit(1) + } + + fmt.Printf("Reading from IO: invalid address %X", address) + os.Exit(1) + + return 0 +} + +func IOWrite(address uint16, value byte) { + if address == 0xFF01 { + SerialData[0] = value + } + + if address == 0xFF02 { + SerialData[1] = value + } + + if (address >= 0xFF04) && (address <= 0xFF07) { + fmt.Printf("Writing to IO: invalid address %X. Timer not yet implemented!", address) + os.Exit(1) + } + + fmt.Printf("Writing to IO: invalid address %X", address) + os.Exit(1) +}