This project is educational and Open Source. No code is copied from other emulators. Implementation based solely on technical documentation and permitted tests.
Complete Timer: TIMA, TMA and TAC
Summary
The implementation of the Game Boy Timer subsystem has been completed by adding the registersTIMA (Timer Counter, 0xFF05), TMA (Timer Module, 0xFF06)andTAC (Timer Control, 0xFF07). The Timer can now generate interrupts when TIMA overflows (goes from 255 to 0), automatically reloading with the TMA value. This functionality is critical for many games that use the Timer during initialization to generate random seeds or wait for specific time intervals. 21 tests were created complete that validate all frequencies, overflow, TMA recharge and request of interruptions. All tests pass correctly.
Hardware Concept
The Game Boy Timer is a timing system that includes four registers:
- DIV(0xFF04): Continuous counter that increments at 16384 Hz (already implemented in step 0037).
- TIMA (0xFF05): 8-bit counter that increments at a configurable frequency.
- TMA (0xFF06): Recharge value when TIMA overflows.
- TAC (0xFF07): Control register that activates/deactivates the Timer and selects the frequency.
TAC (Timer Control)has the following structure:
- Bit 2: Enable (1 = Timer active, 0 = Timer off).
- Bits 1-0: TIMA increase frequency:
- 00: 4096 Hz → 1024 T-Cycles per increment
- 01: 262144 Hz → 16 T-Cycles per increment
- 10: 65536 Hz → 64 T-Cycles per increment
- 11: 16384 Hz → 256 T-Cycles per increment
- Bits 3-7: They are always 1 (they cannot be written).
TIMA Overflow: When TIMA goes from 255 (0xFF) to 0, two things happen simultaneously:
- TIMA is automatically reloaded with the TMA value.
- Bit 2 of the IF register (Interrupt Flag, 0xFF0F) is activated, requesting a Timer interrupt.
The Timer interrupt will be processed by the CPU if IME (Interrupt Master Enable) is active and bit 2 IE (Interrupt Enable, 0xFFFF) is also active. The Timer interrupt vector is 0x0050.
Use in games: Many games use the Timer during initialization to:
- Generate random seeds (RNG) based on elapsed time.
- Wait specific time intervals before continuing.
- Synchronize events with system time.
If the Timer is not implemented correctly, games can stay in infinite loops waiting cause TIMA to overflow, causing the "eternal white screen" symptom or freezing during initialization.
Fountain: Pan Docs - Timer and Divider Registers
Implementation
The class was extendedTimerinsrc/io/timer.pyto implement TIMA, TMA and TAC.
The implementation includes:
Components created/modified
- Timer (src/io/timer.py):
- Added internal records:
_tima,_tma,_tac. - Added accumulator
_tima_accumulatorto handle fractions of a cycle. - Added reference to MMU to request interrupts.
- Method
tick()extended to process TIMA based on the configured frequency. - Methods
read_tima(),write_tima(),read_tma(),write_tma(),read_tac(),write_tac(). - Method
_get_tima_threshold()to determine the T-Cycles threshold based on frequency. - Method
_request_timer_interrupt()to activate bit 2 of IF. - Method
set_mmu()to connect the MMU to the Timer.
- Added internal records:
- MMU (src/memory/mmu.py):
- Intercepted readings of TIMA (0xFF05), TMA (0xFF06) and TAC (0xFF07) in
read_byte(). - Writings intercepted in TIMA, TMA and TAC in
write_byte(). - The operations are delegated to the Timer, similar to what is done with DIV.
- Intercepted readings of TIMA (0xFF05), TMA (0xFF06) and TAC (0xFF07) in
- Viboy (src/viboy.py):
- Added MMU connection to Timer via
timer.set_mmu(mmu)at both initialization points. - This allows the Timer to request interrupts when TIMA overflows.
- Added MMU connection to Timer via
- Tests (tests/test_io_timer_full.py):
- Complete file created with 21 tests that validate all the functionalities of the Timer.
- Tests for TIMA: initialization, read/write, increase in all frequencies, overflow, reload with TMA.
- Tests for TMA: initialization, reading/writing.
- Tests for TAC: initialization, read/write, enable/disable.
- Tests for interruptions: overflow generates interruption, multiple interruptions.
- Integration tests with MMU: reading/writing through MMU, complete cycle.
Design decisions
TIMA accumulator: An internal accumulator is used (_tima_accumulator) to drive
cycle fractions. When enough T-Cycles are accumulated for an increment, the threshold is subtracted and TIMA is incremented.
This allows multiple increments to be correctly handled in a single call totick().
Overflow detection: Overflow is checked BEFORE increasing TIMA. If TIMA is 0xFF, the following increase will cause overflow, so it is reloaded with TMA and the interruption is requested. This avoids having to verify after the increment and handle the special case of 0xFF -> 0.
TAC Unused Bits: TAC bits 3-7 are always read as 1, although only bits 0-2 are
significant. This is implemented inread_tac()returning(tac & 0x07) | 0xF8.
Circular reference MMU-Timer: Circular dependency is avoided by using the "setter" pattern after
the initialization. First the Timer is created, then it is connected to the MMU withmmu.set_timer(timer), and finally
The MMU is connected to the Timer withtimer.set_mmu(mmu). This allows the Timer to request interruptions without
create circular dependencies in constructors.
Affected Files
src/io/timer.py- Complete implementation of TIMA, TMA and TAC with overflow and interrupt logicsrc/memory/mmu.py- Interception of TIMA, TMA and TAC reads/writessrc/viboy.py- Connection of MMU to the Timer to request interruptionstests/test_io_timer_full.py- Complete suite of 21 tests to validate all functionalities
Tests and Verification
The complete Timer tests were executed to validate the implementation:
- Command executed:
pytest tests/test_io_timer_full.py -v - Around: Windows, Python 3.13.5
- Result: 21 PASSED testsin 0.08s
- How valid:
- Correct initialization of TIMA, TMA and TAC
- Read/write all registers
- TIMA increase in the 4 configured frequencies (4096Hz, 262144Hz, 65536Hz, 16384Hz)
- TIMA overflow and automatic recharge with TMA
- Timer interrupt request (IF bit 2) when TIMA overflows
- Full integration with MMU for reading/writing via memory addresses
Test code (example: overflow and interruption):
def test_tima_overflow_interrupt(self) -> None:
"""Test: Verify that interruption is requested when TIMA overflows"""
mmu = MMU(None)
timer = Timer()
timer.set_mmu(mmu)
# Configure Timer
timer.write_tma(0x42)
timer.write_tac(0x04) # Enable=1, Freq=00 (4096Hz)
timer.write_time(0xFF)
# Verify that IF bit 2 is initially disabled
if_val = mmu.read_byte(IO_IF)
assert(if_val & 0x04) == 0
# Advance to overflow
timer.tick(1024)
# Verify that IF bit 2 was set
if_val = mmu.read_byte(IO_IF)
assert (if_val & 0x04) != 0
This test demonstrates that when TIMA overflows (goes from 0xFF to 0), the hardware automatically activates bit 2 of the IF register, requesting a Timer interrupt. The CPU will process this interrupt if IME is active and bit 2 of IE is also active.
The existing Timer (DIV) tests were also run to verify that previous functionality was not broken:
- Command executed:
pytest tests/test_io_timer.py -v - Result: 10 PASSED testsin 0.06s
- Validation: All DIV tests still work correctly
Sources consulted
- Bread Docs:Timer and Divider Registers
- Bread Docs:Interrupts(for Timer interrupt vector, 0x0050)
Educational Integrity
What I Understand Now
- Timer as a timing system: The Game Boy Timer is a complex system that includes a continuous counter (DIV) and a configurable counter (TIMA) that can generate interrupts. They both work independently but share the same base system clock (4.194304 MHz).
- Overflow and reload: When TIMA overflows, the hardware automatically reloads TIMA with TMA and requests an interruption. This is atomic hardware behavior: both things happen simultaneously.
- Timer Frequencies: The 4 available frequencies (4096Hz, 262144Hz, 65536Hz, 16384Hz) are calculated dividing the system frequency (4.194304 MHz) by the T-Cycles threshold. This allows games to select the speed of increase of TIMA according to your needs.
- Importance for games: Many games depend on the Timer during initialization. If the Timer does not is implemented, games can sit in infinite loops waiting for TIMA to overflow, causing freezing or white screen.
What remains to be confirmed
- Exact overflow timing: According to the documentation, writing to TIMA during the cycle it does overflow can have special behavior. For now, we implement direct writing, but this might require refinement if we find games that depend on this specific behavior.
- Behavior when deactivating/reactivating TAC: When the Timer is deactivated (bit 2 of TAC goes from 1 to 0), the TIMA accumulator is maintained. If reactivated, TIMA continues from where it was. This is implemented, but It is not completely verified with real ROMs.
Hypotheses and Assumptions
TIMA accumulator: We use an internal accumulator to handle fractions of a cycle. This allows
multiple increments occur in a single call totick()if many T-Cycles are passed. This implementation
is reasonable and should behave the same as real hardware, but is not fully verified with documentation
specific technique on the internal behavior of the accumulator.
TAC Unused Bits: TAC bits 3-7 are always read as 1. This is documented in Pan Docs, but the implementation assumes this is hardware behavior and not a side effect of reading. If we find games that depend on this behavior, we will validate it with additional tests.
Next Steps
- [ ] Test the emulator with real ROMs (e.g. Tetris DX) to verify that the Timer solves the problem of freezing during initialization
- [ ] If the Timer works correctly, the game should exit the waiting loop and turn on the screen (LCDC != 0)
- [ ] Verify that Timer interrupts are processed correctly when IME is active
- [ ] If there are timing problems, investigate the exact behavior of the overflow and writing to TIMA during the overflow