Skip to content

Commit

Permalink
Implement Timer
Browse files Browse the repository at this point in the history
At least enough to pass blargg's 02-interrupts.gb
  • Loading branch information
maxfierke committed Jan 9, 2024
1 parent f87c077 commit 048903b
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 4 deletions.
21 changes: 17 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 4 additions & 0 deletions devices/interrupts.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
132 changes: 132 additions & 0 deletions devices/timer.go
Original file line number Diff line number Diff line change
@@ -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()
}
5 changes: 5 additions & 0 deletions hardware/dmg.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type DMG struct {
ic *devices.InterruptController
lcd *devices.LCD
serial *devices.SerialPort
timer *devices.Timer

// Non-components
debugger debug.Debugger
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -71,6 +74,7 @@ func NewDMGDebug(debugger debug.Debugger) (*DMG, error) {
ic: ic,
lcd: lcd,
serial: serial,
timer: timer,
}, nil
}

Expand All @@ -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
}
Expand Down

0 comments on commit 048903b

Please sign in to comment.