From 048903b7cd7c9e88ea4999335c87da91173cd74d Mon Sep 17 00:00:00 2001 From: Max Fierke Date: Mon, 8 Jan 2024 23:30:39 -0600 Subject: [PATCH] Implement Timer At least enough to pass blargg's 02-interrupts.gb --- README.md | 21 +++++-- devices/interrupts.go | 4 ++ devices/timer.go | 132 ++++++++++++++++++++++++++++++++++++++++++ hardware/dmg.go | 5 ++ 4 files changed, 158 insertions(+), 4 deletions(-) create mode 100644 devices/timer.go diff --git a/README.md b/README.md index 47ff3f3..8b2b12c 100644 --- a/README.md +++ b/README.md @@ -4,14 +4,27 @@ a gameboy emulator for funsies ## TODO - [X] Pass all of Blargg's `cpu_instrs` ROMs via `gameboy-doctor` (expect `02-interrupts.gb`, which isn't verifyable via `gameboy-doctor`) -- [ ] Implement GB serial port (w/ option to log to console) -- [ ] Pass Blargg's `cpu_instrs`/`02-interrupts.gb` ROM (manually verified) +- [X] Implement serial port (w/ option to log to console) +- [X] Implement timer +- [X] Pass Blargg's `cpu_instrs`/`02-interrupts.gb` ROM (manually verified) - [ ] Pass Blargg's `instr_timing.gb` ROM (manually verified) - [ ] Pass Blargg's `mem_timing.gb` ROM (manually verified) -- [ ] Implement LCD & Rendering +- [ ] Implement LCD += [ ] Implement PPU, VRAM, OAM, etc. - [ ] Pass all of Blargg's `mem_timing-2` ROMs (manually verified) - [ ] Implement Joypad -- [ ] Implement Sound + +## Maybe Never? + +Just being realistic about my likelihood of getting to these: + +- [ ] Implement Sound/APU +- [ ] Implement emulation for every known DMG bug +- [ ] Implement SGB mode +- [ ] Implement MBC6 +- [ ] Implement MBC7 +- [ ] Implement any multicarts or Hudson carts +- [ ] Implement (any) accessories ## Inspiration Material diff --git a/devices/interrupts.go b/devices/interrupts.go index 51cbb59..5ff084f 100644 --- a/devices/interrupts.go +++ b/devices/interrupts.go @@ -152,6 +152,10 @@ func (ic *InterruptController) RequestSerial() { ic.requested.serial = true } +func (ic *InterruptController) RequestTimer() { + ic.requested.timer = true +} + func (ic *InterruptController) OnRead(mmu *mem.MMU, addr uint16) mem.MemRead { if addr == REG_IE { return mem.ReadReplace(ic.enabled.Read()) diff --git a/devices/timer.go b/devices/timer.go new file mode 100644 index 0000000..44c323e --- /dev/null +++ b/devices/timer.go @@ -0,0 +1,132 @@ +package devices + +import ( + "fmt" + + "github.com/maxfierke/gogo-gb/mem" +) + +const ( + REG_TIMER_DIV = 0xFF04 + REG_TIMER_TIMA = 0xFF05 + REG_TIMER_TMA = 0xFF06 + REG_TIMER_TAC = 0xFF07 + + TIMER_DIV_CLOCK_CYCLES = 256 + + TIMER_CLK_EN_MASK = 1 << 2 + TIMER_CLK_SEL_MASK = 0x3 + + TIMER_CLK_SEL_CPU_DIV_1024 = 0x00 + TIMER_CLK_SEL_CPU_DIV_16 = 0x01 + TIMER_CLK_SEL_CPU_DIV_64 = 0x02 + TIMER_CLK_SEL_CPU_DIV_256 = 0x03 + + TIMER_TIMA_CLK_1024 = 1024 + TIMER_TIMA_CLK_16 = 16 + TIMER_TIMA_CLK_64 = 64 + TIMER_TIMA_CLK_256 = 256 +) + +type Timer struct { + divider uint8 + counter uint8 + modulo uint8 + incCounter bool + freqSel byte + + dividerClk uint + counterClk uint +} + +func NewTimer() *Timer { + return &Timer{} +} + +func (timer *Timer) FreqDivider() uint { + switch timer.freqSel { + case TIMER_CLK_SEL_CPU_DIV_1024: + return TIMER_TIMA_CLK_1024 + case TIMER_CLK_SEL_CPU_DIV_16: + return TIMER_TIMA_CLK_16 + case TIMER_CLK_SEL_CPU_DIV_64: + return TIMER_TIMA_CLK_64 + case TIMER_CLK_SEL_CPU_DIV_256: + return TIMER_TIMA_CLK_256 + default: + panic(fmt.Sprintf("Unexpected value for frequency divider: %v", timer.freqSel)) + } +} + +func (timer *Timer) Step(cycles uint8, ic *InterruptController) { + if timer.dividerClk < uint(cycles) { + timer.divider += 1 + remainingCycles := uint(cycles) - timer.dividerClk + timer.dividerClk = TIMER_DIV_CLOCK_CYCLES - remainingCycles + } else { + timer.dividerClk -= uint(cycles) + } + + if !timer.incCounter { + return + } + + if timer.counterClk < uint(cycles) { + remainingCycles := uint(cycles) - timer.counterClk + + if timer.counter == 0xFF { + timer.counter = timer.modulo + ic.RequestTimer() + } else { + timer.counter += 1 + } + + timer.counterClk = timer.FreqDivider() - remainingCycles + } else { + timer.counterClk -= uint(cycles) + } +} + +func (timer *Timer) OnRead(mmu *mem.MMU, addr uint16) mem.MemRead { + if addr == REG_TIMER_DIV { + return mem.ReadReplace(timer.divider) + } else if addr == REG_TIMER_TIMA { + return mem.ReadReplace(timer.counter) + } else if addr == REG_TIMER_TMA { + return mem.ReadReplace(timer.modulo) + } else if addr == REG_TIMER_TAC { + tac := timer.freqSel + + if timer.incCounter { + tac |= TIMER_CLK_EN_MASK + } + + return mem.ReadReplace(tac) + } + + return mem.ReadPassthrough() +} + +func (timer *Timer) OnWrite(mmu *mem.MMU, addr uint16, value byte) mem.MemWrite { + if addr == REG_TIMER_DIV { + timer.divider = 0 + } else if addr == REG_TIMER_TIMA { + timer.counter = value + } else if addr == REG_TIMER_TMA { + timer.modulo = value + } else if addr == REG_TIMER_TAC { + + enableCounter := (value & TIMER_CLK_EN_MASK) == TIMER_CLK_EN_MASK + + if enableCounter && !timer.incCounter { + timer.incCounter = true + timer.freqSel = (value & TIMER_CLK_SEL_MASK) + } else { + timer.incCounter = enableCounter + } + } else { + panic(fmt.Sprintf("Attempting to write 0x%02X @ 0x%04X, which is out-of-bounds for timer", value, addr)) + } + + return mem.WriteBlock() +} diff --git a/hardware/dmg.go b/hardware/dmg.go index 8ae8061..da10436 100644 --- a/hardware/dmg.go +++ b/hardware/dmg.go @@ -21,6 +21,7 @@ type DMG struct { ic *devices.InterruptController lcd *devices.LCD serial *devices.SerialPort + timer *devices.Timer // Non-components debugger debug.Debugger @@ -42,6 +43,7 @@ func NewDMGDebug(debugger debug.Debugger) (*DMG, error) { ic := devices.NewInterruptController() lcd := devices.NewLCD() serial := devices.NewSerialPort() + timer := devices.NewTimer() ram := make([]byte, DMG_RAM_SIZE) mmu := mem.NewMMU(ram) @@ -57,6 +59,7 @@ func NewDMGDebug(debugger debug.Debugger) (*DMG, error) { mmu.AddHandler(mem.MemRegion{Start: 0xFEA0, End: 0xFEFF}, unmapped) // Nop writes, zero reads mmu.AddHandler(mem.MemRegion{Start: 0xFF01, End: 0xFF02}, serial) // Serial Port (Control & Data) + mmu.AddHandler(mem.MemRegion{Start: 0xFF04, End: 0xFF07}, timer) // Timer (not RTC) mmu.AddHandler(mem.MemRegion{Start: 0xFF40, End: 0xFF4B}, lcd) // LCD control registers mmu.AddHandler(mem.MemRegion{Start: 0xFF0F, End: 0xFF0F}, ic) @@ -71,6 +74,7 @@ func NewDMGDebug(debugger debug.Debugger) (*DMG, error) { ic: ic, lcd: lcd, serial: serial, + timer: timer, }, nil } @@ -94,6 +98,7 @@ func (dmg *DMG) Step() bool { cycles += dmg.cpu.PollInterrupts(dmg.mmu, dmg.ic) dmg.serial.Step(cycles, dmg.ic) + dmg.timer.Step(cycles, dmg.ic) return true }