Step 0411: Fix IRQ Pipeline + Post-Boot Registers Consistent
📋 Executive Summary
Problem: Step 0410 revealed that Pokémon (pkmn.gb, Oro.gbc) are blocked withIF=0x00waiting for interruptions that never arrive, preventing the loading of tiles.
Hypothesis: Possible mismatch between post-boot CPU registers and HW mode detected, or LCD/Timer turned off.
Solution: Complete instrumentation of the IRQ pipeline with direct counters + align post-boot registers with HW mode (DMG vs CGB) detected from header ROM.
Result: ✅ IRQs being generated correctly. Oro.gbc advances significantly (RETI, IE=0x1F). Pokémon Red/Tetris DX expect Joypad input (new Step required).
🔧 Hardware Concept
1. Interrupt Pipeline (IF, IE, IME)
IRQ flow on Game Boy(Pan Docs - Interrupts):
- IF (0xFF0F): Interrupt Flag - Bits set when hardware requests IRQ (VBlank, STAT, Timer, Serial, Joypad)
- IE (0xFFFF): Interrupt Enable - Bits that enable which IRQs can be served
- IME: Interrupt Master Enable - Global flag that enables/disables all IRQs (enabled with EI, deactivated with DI)
- Condition to serve IRQ:
IME && (IE & IF) != 0
VBlank IRQ Example:
1. PPU enters VBlank (LY=144) → request_interrupt(0) → IF bit 0 = 1
2. If IE bit 0 = 1 (VBlank enabled) AND IME = 1 → CPU jumps to vector 0x0040
3. CPU runs ISR VBlank
4. ISR clears IF bit 0 (writing 0 to IF) and executes RETI
2. Post-Boot CPU Registers according to HW Mode
Problem: The recordTOpost-boot identifies the hardware to the game:
- DMG:
A=0x01→ Game knows it's on the original Game Boy - CGB:
A=0x11→ Game knows it's on Game Boy Color
Dual-mode games (DMG/CGB compatible) take different paths depending onTO. YeahTOdoes not match the real HW mode, the game may be left in an inconsistent state.
Complete Post-Boot State(Pan Docs - Power Up Sequence):
DMG: AF=0x01B0, BC=0x0013, DE=0x00D8, HL=0x014D, SP=0xFFFE, PC=0x0100
CGB: AF=0x1180, BC=0x0000, DE=0xFF56, HL=0x000D, SP=0xFFFE, PC=0x0100
3. LCDC bit7 and Timer (TAC bit2)
- LCDC bit7=0 (LCD OFF): PPU stops, LY remains at 0,NO VBlank IRQ
- TAC bit2=0 (Timer OFF): TIMA register does not increment,NO IRQ Timer
If both are off,I.F.can stay at 0 indefinitely.
💻 Implementation
Task 1A: Direct counters inMMU::request_interrupt
Added counters inMMU.hppto trackallIRQ requests (independent of changes in IF):
// MMU.hpp - Counters for IRQ requests
mutable int irq_req_vblank_count_; // Total requests VBlank (bit 0)
mutable int irq_req_stat_count_; // Total requests STAT (bit 1)
mutable int irq_req_timer_count_; // Total requests Timer (bit 2)
mutable int irq_req_serial_count_; // Total requests Serial (bit 3)
mutable int irq_req_joypad_count_; // Total requests Joypad (bit 4)
// MMU.cpp - request_interrupt()
void MMU::request_interrupt(uint8_t bit) {
// Increment counter according to the bit (ALWAYS, regardless of IF)
switch (bit) {
case 0: irq_req_vblank_count__+; break;
case 1: irq_req_stat_count+++; break;
case 2: irq_req_timer_count+++; break;
case 3: irq_req_serial_count__+; break;
case 4: irq_req_joypad_count__+; break;
}
// ... rest of the code
}
Task 1B: Periodic summary of IRQ requests
Addedlog_irq_requests_summary()in MMU, called every 120 frames from PPU:
// PPU.cpp - Entering VBlank
if (frame_counter_ % 120 == 0) {
mmu_->log_irq_requests_summary(frame_counter_);
}
// MMU.cpp - log_irq_requests_summary()
[IRQ-SUMMARY] Requests generated (total):
[IRQ-SUMMARY] VBlank (bit 0): 2
[IRQ-SUMMARY] STAT (bit 1): 144
[IRQ-SUMMARY] Timer (bit 2): 3
[IRQ-SUMMARY] Current status:
[IRQ-SUMMARY] IE (0xFFFF): 0x1F (VBlank STAT Timer Serial Joypad)
[IRQ-SUMMARY] IF (0xFF0F): 0x01 (VBlank)
[IRQ-SUMMARY] LCDC (0xFF40): 0xE3 (LCD ON)
[IRQ-SUMMARY] TAC (0xFF07): 0x04 (Timer ON)
[IRQ-SUMMARY] Analysis:
[IRQ-SUMMARY] ✓ THERE ARE PENDING INTERRUPTIONS
Task 2: Post-Boot Registers aligned with HW Mode
Added methodapply_post_boot_state(bool is_cgb_mode)inCoreRegisters:
// Registers.cpp
void CoreRegisters::apply_post_boot_state(bool is_cgb_mode) {
if (is_cgb_mode) {
a = 0x11; b = 0x00; c = 0x00;
d = 0xFF; e = 0x56;
h = 0x00; l = 0x0D;
f = 0x80; // Z=1, N=0, H=0, C=0
} else {
a = 0x01; b = 0x00; c = 0x13;
d = 0x00; e = 0xD8;
h = 0x01; l = 0x4D;
f = 0xB0; // Z=1, N=0, H=1, C=1
}
sp = 0xFFFE;
pc = 0x0100;
}
// viboy.py - After loading ROM
hardware_mode_str = self._mmu.get_hardware_mode()
is_cgb_mode = (hardware_mode_str == "CGB")
self._regs.apply_post_boot_state(is_cgb_mode)
Task 3: Extend wait-loop listener
Added full state capture inCPU.cppwhen wait-loop is detected:
[WAITLOOP] Timer: TAC=0x00, DIV=0x42, TIMA=0x00
[WAITLOOP] - Timer OFF
[WAITLOOP] ⚠️ TIMER OFF: There will be no IRQ Timer
[WAITLOOP] CGB: KEY1=0x00, VBK=0x00, SVBK=0x00
🧪 Tests and Verification
Commands Executed
python3 setup.py build_ext --inplace
timeout 60s python3 main.py roms/pkmn.gb > logs/step0411_pkmn_irq_fix.log 2>&1
timeout 60s python3 main.py roms/Oro.gbc > logs/step0411_oro_irq_fix.log 2>&1
timeout 30s python3 main.py roms/tetris_dx.gbc > logs/step0411_tetris_dx_baseline.log 2>&1
# Safe parsing (without cluttering context)
grep -E "\[IRQ-SUMMARY\]|\[WAITLOOP\]|Post-Boot State" logs/step0411_*.log | head -n 160
Results: Oro.gbc (Pokémon Gold)
✅ BIG ADVANCE
- ✅ VBlank IRQs: 2 (generating correctly)
- ✅ STAT IRQs: 144 (LCD STAT interrupts active)
- ✅ Timer IRQs: 3
- ✅ IE=0x1F (all IRQs enabled)
- ✅ RETI running (ISRs working)
- ✅ LCDC changes dynamically (0x91→0x00→0xE3)
- ✅ Hardware Mode: CGB detected, consistent post-boot logs
Results: pkmn.gb (Pokémon Red)
⚠️ PARTIAL PROGRESS
- ✅ VBlank IRQs: 1+ (generating)
- ✅ LCDC: Dynamic changes (0x91→0x80→0x81→0xE3)
- ⚠️ IE=0x0D (VBlank, Timer, Joypad enabled, but Timer OFF)
- ⚠️ TAC=0x00 (Timer off → no IRQ Timer)
- ⚠️ Waiting for Joypad input (polling active at 0x600A-0x600E)
- ✅ Hardware Mode: DMG detected,
A=0x01
Results: tetris_dx.gbc (Tetris DX)
⚠️ WAITING FOR INPUT (no regression)
- ✅ VBlank IRQs: 1+ (generating)
- ⚠️ IE=0x00 (interrupts NOT enabled at startup)
- ⚠️ Waiting for Joypad input (active polling)
- ✅ Hardware Mode: CGB detected,
A=0x11
Test Fragment (IRQ Analysis)
// Implicit test: Verification of IRQ counters
// The test runs the emulator and analyzes logs
// Log extract (Oro.gbc):
[IRQ-SUMMARY] Step 0411 - Frame 0
[IRQ-SUMMARY] Requests generated (total):
[IRQ-SUMMARY] VBlank (bit 0): 2
[IRQ-SUMMARY] STAT (bit 1): 144
[IRQ-SUMMARY] Timer (bit 2): 3
[IRQ-SUMMARY] Current status:
[IRQ-SUMMARY] IE (0xFFFF): 0x1F (VBlank STAT Timer Serial Joypad)
[IRQ-SUMMARY] IF (0xFF0F): 0x01 (VBlank)
[IRQ-SUMMARY] LCDC (0xFF40): 0xE3 (LCD ON)
[IRQ-SUMMARY] TAC (0xFF07): 0x04 (Timer ON)
[IRQ-SUMMARY] Analysis:
[IRQ-SUMMARY] ✓ THERE ARE PENDING INTERRUPTIONS
// Compiled C++ module validation ✓
📁 Modified Files
src/core/cpp/MMU.hpp- Added IRQ counters and declarationlog_irq_requests_summary()src/core/cpp/MMU.cpp- Implementation of counters inrequest_interrupt()and summary functionsrc/core/cpp/PPU.cpp- Periodic call tolog_irq_requests_summary()src/core/cpp/CPU.cpp- Extended wait-loop detector with Timer and CGB infosrc/core/cpp/Registers.hpp- Declaration ofapply_post_boot_state()src/core/cpp/Registers.cpp- Implementation ofapply_post_boot_state()src/core/cython/registers.pyx- Binding Cython forapply_post_boot_state()src/core/cython/registers.pxd- C++ declaration for Cythonsrc/viboy.py- Call toapply_post_boot_state()after loading ROM
🔍 Key Findings
- IRQ pipeline works: The counters show that
request_interrupt()is called correctly (VBlank, STAT, Timer). - Oro.gbc advances significantly: Run RETI, have IE=0x1F active, and LCDC changes dynamically (off/on). This indicates that the game progresses beyond boot.
- Pokémon Red and Tetris DX await input: Both remain in Joypad polling (IE low, waiting for buttons). They need input simulation to move forward.
- Post-Boot Registers now consistent:
A=0x01(DMG) for pkmn.gb,A=0x11(CGB) for Oro.gbc and tetris_dx.gbc. - Improved diagnostics:
[IRQ-SUMMARY]provides clear analysis of the status of IRQs, facilitating future debugging.
🚀 Next Steps
- Step 0412: Joypad Input Simulation- Implement simulated auto-input (e.g. automatically pressing START after N frames) so that games that expect input can advance.
- Step 0413: Verify post-input tile loading- Once the games receive input, check if they load tiles correctly.
- Step 0414: Investigate why Oro.gbc advances but pkmn.gb does not- Analyze differences in initialization between both Pokémon games.