⚠️ 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.

V-Blank Polling: Independent IF of IME

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

Summary

It was verified and documented that the recordIF (Interrupt Flag, 0xFF0F)is updatedalwayswhen V-Blank occurs, regardless of the status ofIME (Interrupt Master Enable). This allows games to do manual IF polling to detect V-Blank without using interrupts automatic. Specific tests were created to validate this critical behavior and the documentation in the code to make this aspect of the hardware clear.

Hardware Concept

On the Game Boy, there are two mechanisms to detect hardware events (V-Blank, Timer, etc.):

  1. Automatic Interruptions: YeahIME=True, I.E.has the bit active, andI.F.has the bit active, the CPU automatically jumps to the interrupt vector.
  2. Manual Polling: The game manually reads the logI.F.and check if some bit is active. If it detects V-Blank (bit 0), it runs its own update routine.

CRITICAL: The recordI.F.ispure hardware. When an event occurs (V-Blank, Timer, etc.), hardwarealwaysactivates the corresponding bit inI.F., regardlessof:

  • The state ofIME(Interrupt Master Enable)
  • The state ofI.E.(Interrupt Enable, 0xFFFF)

This means that even if a game runsD.I.(Disable Interrupts,IME=False) and never executesEI(Enable Interrupts), hardware keeps updatingI.F.when V-Blank occurs. The game can readI.F.manually and detect V-Blank to update graphics.

Fountain: Pan Docs - Interrupts, V-Blank Interrupt Flag

Implementation

It was verified that the current implementation insrc/gpu/ppu.pyalready updatedI.F.correctly when V-Blank occurs, without depending onIME. However, it improved documentation to make this critical behavior explicit.

Components created/modified

  • src/gpu/ppu.py: Added explicit documentation in the methodstep()explaining thatI.F.is always updated, regardless ofIME.
  • tests/test_ppu_vblank_polling.py: New file with 3 tests that validate:
    • test_vblank_sets_if_with_ime_false: Check thatI.F.is activated withIME=False
    • test_vblank_if_persists_until_cleared: Check thatI.F.persists until manual cleaning
    • test_vblank_if_independent_of_ie: Check thatI.F.is updated regardless ofI.E.

Design decisions

The current implementation was already correct: the PPU updatesI.F.directly callingmmu.write_byte(0xFF0F, if_val | 0x01)without checking the status ofIME. This is correct becauseI.F.is a hardware registry that is automatically updated when the event occurs, not when the CPU processes the interrupt.

The MMU allows free writing on0xFF0F(there are no special restrictions), which It is correct because the hardware can updateI.F.whenever.

Affected Files

  • src/gpu/ppu.py- Improvement of documentation in methodstep()to explain thatI.F.is updated regardless ofIME
  • tests/test_ppu_vblank_polling.py- New file with tests to validate V-Blank polling

Tests and Verification

The new tests were executed to validate the behavior of V-Blank polling:

Command executed

pytest tests/test_ppu_vblank_polling.py -v

Around

  • YOU:Windows 10
  • Python: 3.13.5

Result

============================= test session starts =============================
platform win32 -- Python 3.13.5, pytest-9.0.2, pluggy-1.6.0
collecting ... collected 3 items

tests/test_ppu_vblank_polling.py::TestPPUVBlankPolling::test_vblank_sets_if_with_ime_false PASSED [ 33%]
tests/test_ppu_vblank_polling.py::TestPPUVBlankPolling::test_vblank_if_persists_until_cleared PASSED [ 66%]
tests/test_ppu_vblank_polling.py::TestPPUVBlankPolling::test_vblank_if_independent_of_ie PASSED [100%]

======================== 3 passed, 2 warnings in 0.31s ========================

How valid

  • test_vblank_sets_if_with_ime_false: Validates that the recordI.F.is updated when V-Blank occurs (LY=144), even whenIME=False. This shows that the hardware updateI.F.regardless of the state ofIME, allowing manual polling.
  • test_vblank_if_persists_until_cleared: Validates thatI.F.remains active until the game manually clears it, and it is reactivated on the next V-Blank. This shows polling behavior: the game can readI.F., detect V-Blank, do your job, and clear the bit manually.
  • test_vblank_if_independent_of_ie: Validates thatI.F.it even updates whenI.E.(Interrupt Enable) has bit 0 disabled. This shows thatI.F.It is pure hardware and does not depend on the configuration ofI.E..

Test code

Essential fragment of the main test:

def test_vblank_sets_if_with_ime_false(self) -> None:
    """Test: IF is updated when V-Blank occurs, even with IME=False."""
    mmu = MMU(None)
    cpu = CPU(mmu)
    ppu = PPU(mmu)
    mmu.set_ppu(ppu)
    
    # Set IME=False (automatic interrupts disabled)
    cpu.ime = False
    
    # Clear IF initially
    mmu.write_byte(IO_IF, 0x00)
    
    # Advance PPU to V-Blank (line 144)
    total_cycles = 144 * 456
    ppu.step(total_cycles)
    
    # CRITICAL: IF must have bit 0 set, even with IME=False
    if_val = mmu.read_byte(IO_IF)
    assert(if_val & 0x01) == 0x01

Why this test demonstrates something about the hardware: This test verifies that the recordI.F.behaves like pure hardware: it updates automatically when the event occurs (V-Blank), without depending of the software configuration (IME). This is exactly how the real hardware works Game Boy, allowing games to do manual polling even when automatic interrupts are on disabled.

Sources consulted

  • Bread Docs:Interrupts- Explanation of IF, IE, IME and polling
  • Bread Docs:LCD Timing- V-Blank and LY registration

Educational Integrity

What I Understand Now

  • IF is pure hardware: The recordIF (0xFF0F)updates automatically by the hardware when an event occurs (V-Blank, Timer, etc.), regardless of the status ofIMEeitherI.E.. This is different from other systems where interrupt flags may depend on software configuration.
  • Polling vs Automatic Interrupts: Games can use two strategies to detect V-Blank: (1) Automatic interrupts (requiresIME=True, I.E.with active bit, andI.F.with active bit), or (2) Manual Polling (readI.F.periodically and check bits). The second strategy works even withIME=False.
  • Separation of responsibilities: I.F.indicates "what events have occurred",I.E.indicates "what events I want to process automatically", andIMEindicates "yes I want" "process interrupts automatically." Hardware always updatesI.F., but only processes automatically ifIME=Trueand the corresponding bit is active inI.E..

What remains to be confirmed

  • Behavior on real hardware: Although documentation and tests confirm thatI.F.is updated regardless ofIME, it would be useful to verify this with a specific ROM test that pollI.F.withIME=Falseand verify that it detects V-Blank correctly.
  • Exact timing: The current test verifies thatI.F.is activated whenLY=144, but it does not verify the exact timing within the line cycle. On real hardware,I.F.is activated at the beginning of line 144, but it is not clear whether it occurs at the beginning or the end of the line cycle.

Hypotheses and Assumptions

Verified assumption: The current implementation assumes thatI.F.is updated whenL.Y.reaches 144, regardless ofIME. The tests confirm that this assumption is correct and matches the documented behavior of the hardware.

Outstanding assumption: We assume that the activation timing ofI.F.(at the beginning of line 144) is correct, but this is not completely verified with test ROMs. If a game polls very precise timing, there could be minor discrepancies.

Next Steps

  • [ ] Verify that the games that pollI.F.withIME=Falsecan detect V-Blank correctly
  • [ ] If the game still does not progress, investigate whether there are other aspects of polling that are not implemented correctly
  • [ ] Consider adding optional logging to track when the game reads/writes toI.F.to debug polling
  • [ ] Check if the game expects any specific statusLCDCor other records before polling