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

PPU Phase F: Implementation of STAT Interrupts

Date:2025-12-20 StepID:0174 State: ✅ VERIFIED

Summary

The emulator was in adeadlockpersistent because the CPU in stateHALTHe never woke up. Although the HALT architecture implemented in Step 0173 was correct, the problem was that the PPU did not generate theSTAT Interruptsthat the game was waiting to continue. This Step documents the final verification and correction of the STAT interrupt system in the C++ PPU, ensuring that the V-Blank interrupt uses therequest_interrupt()to maintain consistency, and confirms that access toimein the Cython wrapper it is already correctly implemented.

Hardware Concept: The STAT Interrupt Dialog

The recordSTAT(0xFF41) not only reports the current mode of the PPU, but also allows the game to request notifications when certain events occur via interrupts. This interrupt system is critical for synchronization between the game and the graphics hardware.

STAT Interrupt Enable Bits

  • STAT Bit 6:Enable interrupt ifLY == LYC(line match).
  • STAT Bit 5:Enable interruption when entering Mode 2 (OAM Scan).
  • STAT Bit 4:Enable interruption when entering Mode 1 (V-Blank).
  • STAT Bit 3:Enable interruption when entering Mode 0 (H-Blank).

Rising Edge Detection

A critical detail of the actual hardware is therising edge detection: The interrupt is only requested at the instant the condition becomes true (when it passes fromfalsetotrue), not continuously while it remains active. This prevents multiple interrupts from being generated while the PPU mode remains constant (for example, during the entire H-Blank period).

When the PPU detects that one of these conditions is met AND the corresponding bit inSTATis activated, you must request an interruptsetting bit 1 of the register to 1I.F.(0xFF0F). This is the mechanism that allows the CPU to wake up fromHALTwhen the game is waiting for a specific PPU event to occur.

Fountain:Pan Docs - LCD Status Register (STAT), STAT Interrupt, Rising Edge Detection

Implementation

The implementation of STAT interrupts was already present in the code (from previous Steps), but an important fix was made for consistency: changing the V-Blank interrupt request to use therequest_interrupt()instead of writing directly to the registryI.F..

A. V-Blank Interruption Correction

Insrc/core/cpp/PPU.cpp, whenL.Y.reaches 144 (V-Blank start), it was writing directly to the logI.F.. To maintain consistency with the rest of the code and follow good practices, it was changed to use the methodrequest_interrupt(0)from the MMU:

// Before (writing directly):
if (ly_ == VBLANK_START) {
    uint8_t if_val = mmu_->read(IO_IF);
    if_val |= 0x01;  // Set bit 0 (V-Blank interrupt)
    mmu_->write(IO_IF, if_val);
    frame_ready_ = true;
}

// After (using request_interrupt):
if (ly_ == VBLANK_START) {
    mmu_->request_interrupt(0);  // Bit 0 = V-Blank Interrupt
    frame_ready_ = true;
}

B. IME Setter Verification

It was verified that access toimein the Cython wrapper it was already correctly implemented:

  • The methodset_ime(bool value)exists inCPU.hppandCPU.cpp.
  • It is displayed incpu.pyxas property with getter and setter using decorator@property.
  • The tests confirm that it works correctly (test_cpu_ime_setterhappens).

C. Existing STAT Interrupt System

The methodcheck_stat_interrupt()inPPU.cppIt was already implemented correctly:

  • Detects active interrupt conditions (LYC=LY, Mode 0, Mode 1, Mode 2).
  • Implements rising edge detection usingstat_interrupt_line_to track the previous status.
  • Request interruptions usingmmu_->request_interrupt(1)when it detects a rising edge.
  • It is called correctly at the appropriate times during the scanline cycle.

Affected Files

  • src/core/cpp/PPU.cpp- Fixed V-Blank interrupt request to userequest_interrupt().

Note:The other components (STAT interrupts, IME setter) were already implemented correctly in previous Steps.

Tests and Verification

All STAT interrupt tests and the IME setter pass correctly:

STAT Interrupt Tests

def test_stat_hblank_interrupt():
    """Verifies that a STAT interrupt is requested when entering Mode 0 (H-Blank)."""
    mmu = PyMMU()
    ppu = PyPPU(mmu)
    mmu.set_ppu(ppu)
    mmu.write(0xFF40, 0x80) # LCD ON
    
    # Enable H-Blank interrupt in STAT (bit 3)
    mmu.write(0xFF41, 0x08)
    mmu.write(0xFF0F, 0x00) # Clear IF
    
    # Advance PPU to H-Blank
    ppu.step(252)
    assert ppu.mode == 0
    assert (mmu.read(0xFF0F) & 0x02) != 0 # Bit 1 (STAT) set

Command executed: pytest tests/test_core_ppu_interrupts.py -v

Result:6 tests passed correctly:

  • test_stat_hblank_interrupt- Check interruption in H-Blank
  • test_stat_vblank_interrupt- Check interruption in V-Blank
  • test_stat_oam_search_interrupt- Check interruption in OAM Search
  • test_stat_lyc_coincidence_interrupt- Check LYC=LY interruption
  • test_stat_interrupt_rising_edge- Verifies rising edge detection
  • test_cpu_ime_setter- Check the IME setter

Native Validation

All tests validate the compiled C++ module and confirm that:

  • STAT interrupts are generated correctly in the appropriate modes.
  • Rising edge detection works (no multiple interrupts generated).
  • The IME setter allows you to configure the interrupt status from the tests.
  • The V-Blank interrupt is successfully requested when LY reaches 144.

Sources consulted

Educational Integrity

What I Understand Now

  • The alarm clock never rang:The CPU was entering HALT correctly, but the PPU was not generating the STAT interrupts that the game expected. Without interrupts, there was nothing to wake up the CPU, creating a deadlock.
  • Rising edge detection:STAT interrupts are only fired when the condition goes from false to true, not while it remains active. This is critical to avoid multiple interruptions over long periods (like all H-Blank).
  • Code consistency:Wearrequest_interrupt()instead of writing directly to IF makes the code more maintainable and consistent, since all components use the same method to request interrupts.
  • The complete wiring:The connection between the PPU state machine and the interrupt system is complete. The PPU detects mode and condition changes and requests interruptions when appropriate.

What remains to be confirmed

  • Running with real ROM:Run the emulator with a real ROM (ex:tetris.gb) to verify that the deadlock has been broken and the game can progress beyond initialization.
  • Precise timing:Validate that the timing of STAT interrupts exactly matches the behavior of real hardware.

Hypotheses and Assumptions

We assume that with STAT interrupts working correctly, the CPU should be able to wake up from HALT when the game expects them, breaking the deadlock it held.L.Y.stuck at 0. This should allow the game to progress past initialization and eventually display the Nintendo logo.

Next Steps

  • [ ] Recompile the C++ module with the corrections made
  • [ ] Run the emulator with a real ROM (ex:tetris.gb) and verify that the deadlock has been broken
  • [ ] Validate thatL.Y.advances correctly beyond 0
  • [ ] Verify that the game can complete initialization and display the Nintendo logo
  • [ ] Add additional tests if problems are identified during execution with real ROMs