⚠️ Clean-Room / Educational

This project is educational and Open Source. No code is copied from other emulators. Implementation based solely on technical documentation and permitted tests.

Timer (DIV) and Log Cleaning

Date:2025-12-18 StepID:0037 State: Verified

Summary

The emulator suffered fromunacceptable performancedue to excessive logs blocking the main thread (especially on MacBook Air 2015). Level logs were silencedINFOinside the critical loop (MMU and renderer), changing them toDEBUGS. In addition, the subsystem was implementedTimerwith him registrationDIV(0xFF04), which is critical for games like Tetris DX that use it for number generation random (RNG). The Timer continuously increments at 16384 Hz (every 256 T-Cycles) and resets when 0xFF04 is written.

Hardware Concept

HeTimerof the Game Boy is a timing system that includes several registers:

  • DIV(0xFF04): Divider Register - Counter that continuously increments at a fixed rate (16384 Hz).
  • TIMA (0xFF05): Timer Counter - Configurable counter that can generate interrupts.
  • TMA (0xFF06): Timer Module - Recharge value when TIMA overflows.
  • TAC (0xFF07): Timer Control - Controls whether TIMA is active and its frequency.

In this step, we implement onlyDIV, which is the most critical for many games.

DIV record (0xFF04)

DIV is an internal counter16 bitwhich increases at a fixed speed:16384Hz. The DIV record exposes only the8 bits highof the internal counter (bits 8-15).

Key Features:

  • Increase each256 T-Cycles(4.194304 MHz / 16384 Hz = 256).
  • Anywriting to DIV resets the internal counter to 0, regardless of the written value.
  • Many games use DIV to generate random numbers (RNG) by reading their value at unpredictable times.
  • The internal counter does wrap-around automatically (0xFFFF → 0x0000).

Impact of Logging:Print onstdoutIt's an operationvery slowthat blocks the main thread. On a 2015 MacBook Air, writing thousands of messages per second to the console can slow performance from 60 FPS to less than 1 FPS. Therefore, the level logsINFOwithin the critical loop must be changed toDEBUGSso that they only appear when debug mode is explicitly activated.

Fountain:Pan Docs - Timer and Divider Registers

Implementation

1. Log Cleaning

The following logs were changedINFOtoDEBUGS:

  • MMU (src/memory/mmu.py): Log "IO WRITE" that was executed every time an I/O register was written (0xFF00-0xFF7F).
  • Renderer (src/gpu/renderer.py): Log "LCDC: LCD off" that was executed every frame when the LCD was off.
  • Viboy (src/viboy.py): Log "V-Blank" that was executed each frame during V-Blank.

Heheartbeat(every 60 frames ≈ 1 second) stays onINFOto confirm that the emulator is alive.

2. Timer Implementation

The class was createdTimerinsrc/io/timer.pywith the following methods:

  • tick(t_cycles): Advances the Timer according to the T-Cycles elapsed. Accumulate cycles in the internal counter.
  • read_div(): Reads the DIV register (high 8 bits of the internal counter).
  • write_div(value): Resets the internal counter to 0. The written value is ignored.
  • get_div_counter(): Gets the full value of the internal counter (16 bits) for tests.

Counter implementation:The internal counter is 16 bits and is incremented by accumulating T-Cycles. DIV exposes only the high 8 bits:DIV = (div_counter >> 8) & 0xFF.

3. Integration into MMU

Added support for reading/writing DIV (0xFF04) in the MMU:

  • Inread_byte(0xFF04): The reading is intercepted and delegated to the Timer.
  • Inwrite_byte(0xFF04): The writing is intercepted and delegated to the Timer (resets the counter).
  • Added methodset_timer()to connect the Timer to the MMU (avoid circular dependency).

4. Integration in Viboy

The Timer was integrated into the main system:

  • An instance is createdTimerin__init__()andload_cartridge().
  • The Timer is connected to the MMU throughmmu.set_timer().
  • In the methodtick(), it is calledtimer.tick(t_cycles)after executing the CPU instruction.

Note:For now, only DIV is implemented. TIMA/TMA/TAC will be added later when needed.

Affected Files

  • src/io/timer.py- New Timer class with DIV registration
  • src/io/__init__.py- Export Timer
  • src/memory/mmu.py- Timer integration (read/write 0xFF04), silencing of IO WRITE logs
  • src/gpu/renderer.py- Silence of "LCDC disabled" logs
  • src/viboy.py- Integration of Timer in main system, muting of V-Blank logs
  • tests/test_io_timer.py- Timer test suite (10 tests)

Tests and Verification

A complete test suite was created for the Timer with10 teststhat validate:

  • Successful initialization (DIV = 0)
  • DIV increase every 256 T-Cycles
  • Multiple increment and wrap-around
  • DIV reset when writing to 0xFF04
  • Integration with MMU (read/write)

Test Execution

Command executed: python3 -m pytest tests/test_io_timer.py -v

Around:macOS (darwin 21.6.0), Python 3.9.6

Result:10 PASSED testsin 0.46s

What is valid:

  • The Timer increases correctly at 16384 Hz (every 256 T-Cycles).
  • DIV exposes only the high 8 bits of the internal 16-bit counter.
  • Any write to the DIV resets the internal counter, regardless of the value written.
  • Integration with MMU works correctly (read/write 0xFF04).

Test Code

Key fragment of the test that validates the increase in DIV:

def test_div_increment(self) -> None:
    """Test: Verify that DIV increases every 256 T-Cycles"""
    timer = Timer()
    
    # DIV must start at 0
    assert timer.read_div() == 0
    
    # Advance 255 T-Cycles (should not increase yet)
    timer.tick(255)
    assert timer.read_div() == 0
    
    # Advance 1 more T-Cycle (total 256) -> DIV should increment
    timer.tick(1)
    assert timer.read_div() == 1
    
    # Advance another 256 T-Cycles -> DIV should be 2
    timer.tick(256)
    assert timer.read_div() == 2

Why this test demonstrates the behavior of the hardware:The test verifies that DIV increases exactly every 256 T-Cycles, which is the correct frequency (16384 Hz) according to the hardware specification. Additionally, it validates that the internal counter is accumulating correctly and that the DIV exposes only the high 8 bits.

Sources consulted

Educational Integrity

What I Understand Now

  • DIV is critical for RNG:Many games use DIV to generate random numbers by reading their value at unpredictable times. If DIV always returns 0, games can wait or generate sequences predictable.
  • Logging kills performance:Write instdoutIt is extremely slow and crashes the main thread. On older hardware (MacBook Air 2015), this can reduce performance from 60 FPS to less than 1 FPS.
  • DIV exposes only 8 high bits:The internal counter is 16 bits, but DIV only exposes bits 8-15. This means that DIV increments every 256 T-Cycles (when bit 8 of the counter changes).
  • DIV Reset:Any write to the DIV resets the internal counter to 0, regardless of the value written. This is real hardware behavior, not a bug.

What remains to be confirmed

  • TIMA/TMA/TAC:For now we only implement DIV. TIMA (Timer Counter) and TAC (Timer Control) are They will be implemented later when needed for specific games. TIMA can generate interruptions when it overflows, which is more complex.
  • DIV initial value:On a real Game Boy, the internal counter can have a random value when turning on. For now, we initialize it to 0 for reproducibility in tests. This could affect games that They depend on the initial value for RNG.
  • Impact on actual performance:Although we silence the logs, we have not yet tested the emulator with a real ROM (like Tetris DX) to confirm that performance improves significantly. This will be validated in the next step.

Hypotheses and Assumptions

Assumption about DIV frequency:We assume that DIV increases every 256 T-Cycles based on documentation (16384 Hz = 4194304 Hz / 256). This assumption is supported by Pan Docs, but we have not Verified with real hardware or specific test ROMs.

Assumption about DIV reset:We assume that any write to the DIV resets the internal counter, regardless of the written value. This assumption is supported by Pan Docs and is common behavior in hardware of the time.

Next Steps

  • [ ] Test the emulator with Tetris DX to confirm that performance improves significantly
  • [ ] Verify that DIV is read correctly during game execution
  • [ ] Confirm that the LCD turns on after initial charging (now that performance should be normal)
  • [ ] Implement TIMA/TMA/TAC if needed for specific games
  • [ ] Adjust initial DIV value if necessary for compatibility with games that rely on RNG