From 86f9e12c745376791f0d35f87e2c4dc08c3f3b9f Mon Sep 17 00:00:00 2001 From: Max Fierke Date: Sat, 6 Jan 2024 23:08:28 -0600 Subject: [PATCH] Implement interrupt polling Enough to pass EI & DI tests in Blargg's interrupts ROM --- cpu/cpu.go | 33 ++++++++++++++++++++++++ debug/debugger.go | 8 +++--- debug/gb_doctor.go | 2 ++ devices/interrupts.go | 60 ++++++++++++++++++++++++++++++++++++------- hardware/dmg.go | 6 ++++- 5 files changed, 96 insertions(+), 13 deletions(-) diff --git a/cpu/cpu.go b/cpu/cpu.go index ae4e2de..887cad8 100644 --- a/cpu/cpu.go +++ b/cpu/cpu.go @@ -5,6 +5,7 @@ import ( "math/bits" "github.com/maxfierke/gogo-gb/cpu/isa" + "github.com/maxfierke/gogo-gb/devices" "github.com/maxfierke/gogo-gb/mem" ) @@ -1703,6 +1704,38 @@ func (cpu *CPU) Execute(mmu *mem.MMU, inst *isa.Instruction) (nextPC uint16, cyc return cpu.PC.Read() + uint16(opcode.Bytes), uint8(opcode.Cycles[0]), nil } +func (cpu *CPU) PollInterrupts(mmu *mem.MMU, ic *devices.InterruptController) uint8 { + if cpu.ime { + interrupt := ic.ConsumeRequest() + if interrupt == devices.INT_NONE { + return 0 + } + + // Disable interrupts while we process this one + cpu.ime = false + + // Jump to interrupt handler + cpu.push(mmu, cpu.PC.Read()) + cpu.PC.Write(uint16(interrupt)) + + // If we were halted, we're not now! + cpu.halted = false + + // Consuming an IRQ is 16 cycles + return 16 + } else if cpu.halted { + if interrupt := ic.NextRequest(); interrupt != 0 { + // Wakey-wakey + cpu.halted = false + } + + return 0 + } else { + // Ignore + return 0 + } +} + func (cpu *CPU) Reset() { cpu.Reg.Reset() cpu.PC.Write(0x0000) diff --git a/debug/debugger.go b/debug/debugger.go index 9b04046..04c1bbb 100644 --- a/debug/debugger.go +++ b/debug/debugger.go @@ -11,6 +11,7 @@ type Debugger interface { Setup(cpu *cpu.CPU, mmu *mem.MMU) OnDecode(cpu *cpu.CPU, mmu *mem.MMU) OnExecute(cpu *cpu.CPU, mmu *mem.MMU) + OnInterrupt(cpu *cpu.CPU, mmu *mem.MMU) OnRead(mmu *mem.MMU, addr uint16) mem.MemRead OnWrite(mmu *mem.MMU, addr uint16, value byte) mem.MemWrite } @@ -32,9 +33,10 @@ func NewNullDebugger() *NullDebugger { return &NullDebugger{} } -func (nd *NullDebugger) Setup(cpu *cpu.CPU, mmu *mem.MMU) {} -func (nd *NullDebugger) OnDecode(cpu *cpu.CPU, mmu *mem.MMU) {} -func (nd *NullDebugger) OnExecute(cpu *cpu.CPU, mmu *mem.MMU) {} +func (nd *NullDebugger) Setup(cpu *cpu.CPU, mmu *mem.MMU) {} +func (nd *NullDebugger) OnDecode(cpu *cpu.CPU, mmu *mem.MMU) {} +func (nd *NullDebugger) OnExecute(cpu *cpu.CPU, mmu *mem.MMU) {} +func (nd *NullDebugger) OnInterrupt(cpu *cpu.CPU, mmu *mem.MMU) {} func (nd *NullDebugger) OnRead(mmu *mem.MMU, addr uint16) mem.MemRead { return mem.ReadPassthrough() diff --git a/debug/gb_doctor.go b/debug/gb_doctor.go index 58af5fb..a46d1c1 100644 --- a/debug/gb_doctor.go +++ b/debug/gb_doctor.go @@ -25,6 +25,8 @@ func (gbd *GBDoctorDebugger) OnExecute(cpu *cpu.CPU, mmu *mem.MMU) { gbd.printState(cpu, mmu) } +func (gbd *GBDoctorDebugger) OnInterrupt(cpu *cpu.CPU, mmu *mem.MMU) {} + func (gbd *GBDoctorDebugger) OnRead(mmu *mem.MMU, addr uint16) mem.MemRead { if addr == devices.REG_LCD_LY { return mem.ReadReplace(0x90) // gameboy-doctor needs a stubbed out LCD diff --git a/devices/interrupts.go b/devices/interrupts.go index 5259961..51cbb59 100644 --- a/devices/interrupts.go +++ b/devices/interrupts.go @@ -9,6 +9,7 @@ import ( const ( REG_IE = 0xFFFF REG_IF = 0xFF0F + INT_NONE = 0x00 INT_VBLANK = 0x40 INT_STAT = 0x48 INT_TIMER = 0x50 @@ -92,22 +93,63 @@ func NewInterruptController() *InterruptController { } } -func (ic *InterruptController) Reset() { - ic.enabled.Write(0x00) - ic.requested.Write(0x00) -} +func (ic *InterruptController) ConsumeRequest() byte { + nextReq := ic.NextRequest() -func (ic *InterruptController) RequestSerial() { - ic.requested.serial = true + if nextReq == INT_VBLANK { + ic.requested.vblank = false + } + + if nextReq == INT_STAT { + ic.requested.lcd = false + } + + if nextReq == INT_JOYPAD { + ic.requested.joypad = false + } + + if nextReq == INT_SERIAL { + ic.requested.serial = false + } + + if nextReq == INT_TIMER { + ic.requested.timer = false + } + + return nextReq } -func (ic *InterruptController) ConsumeSerial() byte { +func (ic *InterruptController) NextRequest() byte { + if ic.enabled.vblank && ic.requested.vblank { + return INT_VBLANK + } + + if ic.enabled.lcd && ic.requested.lcd { + return INT_STAT + } + + if ic.enabled.timer && ic.requested.timer { + return INT_TIMER + } + if ic.enabled.serial && ic.requested.serial { - ic.requested.serial = false return INT_SERIAL } - return 0 + if ic.enabled.joypad && ic.requested.joypad { + return INT_JOYPAD + } + + return INT_NONE +} + +func (ic *InterruptController) Reset() { + ic.enabled.Write(0x00) + ic.requested.Write(0x00) +} + +func (ic *InterruptController) RequestSerial() { + ic.requested.serial = true } func (ic *InterruptController) OnRead(mmu *mem.MMU, addr uint16) mem.MemRead { diff --git a/hardware/dmg.go b/hardware/dmg.go index 635a226..c406b3a 100644 --- a/hardware/dmg.go +++ b/hardware/dmg.go @@ -79,12 +79,16 @@ func (dmg *DMG) DebugPrint() { func (dmg *DMG) Step() bool { dmg.debugger.OnDecode(dmg.cpu, dmg.mmu) - _, err := dmg.cpu.Step(dmg.mmu) + cycles, err := dmg.cpu.Step(dmg.mmu) if err != nil { dmg.logger.Printf("Unexpected error while executing instruction: %v\n", err) return false } + cycles += dmg.cpu.PollInterrupts(dmg.mmu, dmg.ic) + + dmg.serial.Step(uint(cycles), dmg.ic) + return true }