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 Interrupt Tracing
Summary
were addedcritical diagnostic logsto monitor the dispatch of interrupts in the CPU, specifically to detect whether the V-Blank interrupt (0x0040) is executing correctly after the game turns on the LCD. The diagnostic includes an informative log with the ⚡ symbol when an interrupt is dispatched, showing the vector, the previous PC and the interrupt type. Additionally, a log was added to the renderer to detect when LCDC is 0x80 (LCD ON, BG OFF), a critical state indicating that the game is waiting for the V-Blank interrupt to configure the rest of the graphics.
Hardware Concept
On the Game Boy, when a game turns on the LCD by typing0x80in the LCDC register (0xFF40),
is activating bit 7 (LCD Enable) but leaving bit 0 (BG Display) at 0. This means that the LCD is
turned on but the background is not drawn yet.
The typical game initialization flow is:
- Turn on LCD: Write
0x80in LCDC (LCD ON, BG OFF) - Wait V-Blank: The game waits for the PPU to generate the V-Blank interrupt (vector 0x0040)
- Set up graphics: Inside the V-Blank handler, the game activates the background and sprites (type
0x93or similar in LCDC)
If the V-Blank interrupt is not dispatched correctly after step 1, the game freezes waiting an interruption that never comes. This can occur if:
- IME is disabled: The CPU has interrupts disabled (IME=False)
- IE is not configured: Register IE (0xFFFF) does not have bit 0 (V-Blank) set
- IF is not being generated: The PPU is not correctly generating the interrupt flag in IF (0xFF0F)
Fountain:Pan Docs - Interrupts, V-Blank Interrupt, LCD Control Register
Implementation
Two strategic diagnostic points were added:
1. Interrupt Dispatch Log (CPU)
Insrc/cpu/core.py, methodhandle_interrupts(), an informative log was added
right after the CPU accepts an interrupt and jumps to the vector. The log shows:
- Interrupt vector (0x0040 for V-Blank, 0x0048 for STAT, etc.)
- Previous PC (address where the CPU was before jumping)
- Interrupt Type (V-Blank, LCD STAT, Timer, Serial, Joypad)
This log is executedonly when the interrupt is actually dispatched, that is, when: IME=True, IE has the bit set, IF has the bit set, and the CPU accepts the interrupt.
2. LCDC Status Log 0x80 (Renderer)
Insrc/gpu/renderer.py, methodrender_frame(), a debug log was added
which is activated when LCDC is exactly0x80. This status indicates that the LCD is on
but the background is off, which is a transient state the game uses while waiting for V-Blank.
Design decisions
- INFO log level for interrupts: Used
logger.info()so that the log be visible even without debug mode, since it is critical for diagnosis. - DEBUG log level for LCDC 0x80: Used
logger.debug()because it is less critical and can generate many messages if the game remains in that state. - ⚡ symbol for interruptions: A distinctive visual symbol was added to facilitate the search in the logs.
Affected Files
src/cpu/core.py- Added informative log inhandle_interrupts()when an interrupt is dispatchedsrc/gpu/renderer.py- Added debug log when LCDC is 0x80
Tests and Verification
Execution mode:Manual with commercial ROM (Pokémon Red, contributed by the user, not distributed)
Command executed: python main.py pkmn.gb
Around:Windows 10, Python 3.13.5
Success Criterion:
- See the message
CRITICAL: [TRAP LCDC] LCDC CHANGE ATTEMPT: 00 -> 80(confirmed in previous step) - See the message
⚡ INTERRUPT DISPATCHED! Vector: 0040 | Previous PC: XXXX | Type: V-Blankafter LCDC change - If the interruption message does NOT appear, identify why (IME=False, IE not configured, IF not generated)
Expected observation:
- If it appears
⚡ INTERRUPT DISPATCHED! Vector: 0040: The CPU works correctly and the problem is graphical (renderer does not draw what the game expects after V-Blank) - If it does NOT appear: The bug is in the interruption system (IME, IE, IF, or V-Blank generation by the PPU)
Result: draft- Pending manual execution to check if the interruption log appears
Legal notes:The Pokémon Red ROM is user-contributed for local testing, it is not distributed or linked in the repository.
Sources consulted
Educational Integrity
What I Understand Now
- Game initialization flow:Games turn on the LCD with BG off (0x80) and wait for V-Blank to configure the rest of the graphics. If V-Blank is not dispatched, the game freezes.
- Conditions for interruptions:An interrupt is dispatched only if IME=True, IE has the bit set, and IF has the bit set. If any of these conditions fail, the interrupt is not processed.
- Interrupt diagnosis:The interrupt dispatch log allows you to identify if the problem is in the CPU (does not accept interrupts) or in the PPU (does not generate V-Blank).
What remains to be confirmed
- IME status in Pokémon:Check if IME is enabled when the game writes 0x80 to LCDC. If IME=False, interrupts will not be processed even if they are pending.
- IE Settings:Check if the IE register (0xFFFF) has bit 0 (V-Blank) activated. If not enabled, the interrupt will not be processed even if IF is enabled.
- V-Blank generation by PPU:Verify if the PPU is correctly generating the interrupt flag in IF (0xFF0F) when entering V-Blank mode.
Hypotheses and Assumptions
Main hypothesis:The game turns on the LCD (0x80), but the V-Blank interrupt is not dispatched because: (a) IME is disabled, (b) IE does not have bit 0 set, or (c) the PPU is not correctly generating the flag in IF.
Next verification step:Run the emulator and look for the log⚡ INTERRUPT DISPATCHED!.
If it appears, the problem is graphic. If it does not appear, the problem is with the interrupt system.
Next Steps
- [ ] Run the emulator with Pokémon Red and check if the log appears
⚡ INTERRUPT DISPATCHED! Vector: 0040 - [ ] If NOT displayed: Add additional logs to check the status of IME, IE, and IF when 0x80 is written to LCDC
- [ ] If it appears: Investigate why the renderer does not draw correctly after V-Blank (check palette settings, VRAM, tilemap)
- [ ] Verify that the PPU is correctly generating the V-Blank flag in IF when it enters V-Blank mode