This project is educational and Open Source. No code is copied from other emulators. Implementation based solely on technical documentation and permitted tests.
Operation "Interrupt Awakening" - Interrupt Activation Debugging
Summary
This Step implements theOperation Interrupt Awakeningto investigate why Pokémon Red is stuck in an infinite loop waiting for the flag0xD732change. Analysis of Step 0279 confirmed that the problemIt is NOT a Reset Loop, but a"induced coma": The game is stuck in the polling loop (PC: 0x614D-0x6153) waiting for a V-Blank ISR to modify the flag, but interrupts are disabled (IE=0x00). Although it was detected aEIinPC:0x60A6, interrupts do not appear to be active during polling. This Step adds ultra-precise instrumentation to track the status ofI.E.andIMEwhen it runsEI, and monitors the polling loop to detect if anyone is writing toI.E.during the wait.
Hardware Concept
The Delay of an EI Instruction Cycle
The instructionEI(Enable Interrupts, opcode0xFB) has special behavior on real Game Boy hardware:the Interrupt Master Enable (IME) is activated AFTER the following instruction is executed, not immediately. This one-cycle delay is critical because it allows the instruction followingEIrun without interruptions, which is necessary for atomic configurations or to avoid race conditions.
Interrupt activation flow:
- EI Execution: The opcode
0xFBit runs, butIMEIt does not activate immediately. Instead, an internal flag (ime_scheduled_) which indicates thatIMEshould be activated after the next instruction. - Execution of the following instruction: The instruction that follows
EIis executed withIME=false, ensuring that it is not interrupted. - IME Activation: At the beginning of the next instruction cycle, before the fetch, it is checked if
ime_scheduled_istrue. If it is, it is activatedIMEand the flag is cleaned. - Interrupt processing: Once
IMEis active, the system can process pending interrupts ifIE & IF != 0.
Fountain:Pan Docs - "EI Instruction": "Interrupts are enabled after the instruction following EI."
Interrupt Logs
The Game Boy has two critical registers for interrupt handling:
- IE (0xFFFF) - Interrupt Enable: Interrupt sources enable register. Each bit enables a specific source:
- Bit 0: V-Blank
- Bit 1: LCD STAT
- Bit 2: Timer
- Bit 3: Serial
- Bit 4: Joypad
- IF (0xFF0F) - Interrupt Flag: Pending interrupt flag register. Each bit indicates whether an interrupt is pending.
- IME (Interrupt Master Enable): Internal CPU flag that controls whether interrupts can be processed. It can only be activated by
EI(with delay) or deactivate viaD.I.(immediate).
Condition to process an interrupt: IME == true && (IE & IF) != 0
Fountain:Pan Docs - "Interrupts": "An interrupt is processed if IME is set and the corresponding bit in both IE and IF is set."
Implementation
This Step adds two critical instrumentations to debug the interrupt triggering issue:
1. Ultra-Precise EI and IME Tracking
The case was modified0xFB(EI) inCPU.cppto capture the exact status ofI.E.andIMEwhen trying to enable interrupts. This allows us to identify if the problem is thatI.E.is in0x00when it runsEI, or ifIMEdoes not activate correctly.
case 0xFB://EI (Enable Interrupts)
{
// --- Step 0280: Ultra-Precise EI and IME Tracking ---
uint8_t ie_val = mmu_->read(0xFFFF);
printf("[CPU-EI] EI instruction on PC:0x%04X | Current IE:0x%02X | Previous IME:%d | Programmed IME:%d\n",
original_pc, ie_val, ime_ ? 1 : 0, ime_scheduled_ ? 1 : 0);
ime_scheduled_ = true;
cycles_ += 1;
return 1;
}
Information captured:
- Original PC: Address where it was executed
EI - Current IE: IE register value at run time
EI - Previous IME: IME status before running
EI - programmed IME: If there was already a
EIslope (should be 0 normally)
2. Polling Sniper with IE Status
Added a polling loop monitor (PC: 0x614D-0x6153) that captures the status ofI.E., I.F., IMEand the flag0xD732during the wait. This allows us to see if someone is writing inI.E.while waiting, or ifI.E.magically changes to zero.
// --- Step 0280: Polling Sniper with IE Status ---
if (original_pc >= 0x614D && original_pc<= 0x6153) {
static int polling_watch_count = 0;
if (polling_watch_count < 20) {
printf("[POLLING-WATCH] PC:%04X | IE:0x%02X | IF:0x%02X | IME:%d | D732:0x%02X\n",
original_pc, mmu_->read(0xFFFF), mmu_->read(0xFF0F),
ime_? 1 : 0, mmu_->read(0xD732));
polling_watch_count++;
}
}
Information captured:
- PC: Current address within the polling loop
- I.E.: IE register value during wait
- I.F.: IF register value (pending interrupts)
- IME: Status of Interrupt Master Enable
- D732: Value of the flag that the game is waiting for to change
3. Verification of handle_interrupts Logic
It was verified that the functionhandle_interrupts()does not modify the registryI.E.(only reads it). The function reads onlyI.E.to calculate pending interruptions (pending = IE & IF), but never writes inI.E.. This confirms that the problem is not in the interrupt processing, but in the activation ofI.E.eitherIME.
Modified components
src/core/cpp/CPU.cpp:- Modified
case 0xFB(EI) to add detailed IE and IME tracing - Added polling sniper at the end of
step()to monitor the wait loop
- Modified
Design decisions
- Limit of 20 logs in polling: The number of polling sniper logs is limited to 20 to avoid saturating the log, but it is enough to see the loop pattern.
- Real-time IE capture: It reads
I.E.directly from the MMU in each log to capture changes in real time, a cached value is not used. - Using original_pc: Used
original_pc(PC before fetch) to ensure that we capture the correct address, even if the PC moves forward during the execution of the instruction.
Affected Files
src/core/cpp/CPU.cpp- Modified case 0xFB (EI) and added polling sniper
Tests and Verification
Verification will be carried out through log analysis during the execution of Pokémon Red:
- Logs [CPU-EI]: They should appear when the game runs
EI. YeahCurrent IEis0x00at the time ofEI, we will confirm that the problem is that the game is enabling the master (IME) but you have not enabled any individual fonts (I.E.). - Logs [POLLING-WATCH]: They should appear when the game enters the polling loop. Yeah
I.E.magically changes to zero during the wait, we will know that something is writing toI.E.incorrectly. - C++ Compiled Module Validation: The code compiles without errors and the module is generated correctly.
Test command:
python setup.py build_ext --inplace
python main.py roms/pkmn.gb > debug_step_0280.log 2>&1
Expected analysis:
- Look for
[CPU-EI]in the log to see the status of IE when EI is executed - Look for
[POLLING-WATCH]to see IE status during wait loop - Check if IE changes between the time of the EI and the polling loop
Sources consulted
- Bread Docs:EI Instruction - Enable Interrupts
- Bread Docs:Interrupts
- Analysis of Step 0279: Confirmation that the problem is NOT a Reset Loop, but an infinite waiting loop
Educational Integrity
What I Understand Now
- Delay of an EI cycle:The instruction
EInot activeIMEimmediately, but after executing the next instruction. This is actual hardware behavior, not a limitation of our implementation. - Difference between IE and IME:
I.E.(0xFFFF) enables specific sources of interrupts, whileIMEis an internal flag that controls whether interrupts can be processed. Both must be active for an interrupt to be processed. - The real problem: The game is stuck in a waiting loop because
IE=0x00, which prevents interrupts from being processed, even ifIMEis active and there are interruptions pending onI.F..
What remains to be confirmed
- Why does IE stay at 0x00?: We need to check if the game never writes to
I.E.after theEI, or if something is writing0x00inI.E.incorrectly. - Is the EI executed correctly?: We need to verify if the
EIinPC:0x60A6really programIMEcorrectly, and ifIMEis activated after the next instruction. - Are there writes to IE during polling?: The polling sniper will tell us if someone is writing in
I.E.during the wait, which could explain whyI.E.stays in0x00.
Hypotheses and Assumptions
Main hypothesis:The game runsEIinPC:0x60A6, butI.E.is in0x00then. The game expects something else (probably an ISR or initialization code) to enable interrupt sources inI.E., but that never happens because interrupts are disabled.
Assumption:The game should write toI.E.(For example,LD A, 0x0D; LDH (0xFF), Ato enable V-Blank and Timer) before or after theEI, but for some reason it doesn't or it runs incorrectly.
Next Steps
- [ ] Run the emulator with the new instrumentation and analyze the logs
[CPU-EI]and[POLLING-WATCH] - [ ] Check if
I.E.is in0x00when it runsEIinPC:0x60A6 - [ ] Check if
I.E.changes during polling loop - [ ] Yeah
I.E.is in0x00, look in the game code where it should be enabled - [ ] Yeah
I.E.change to0x00during polling, identify what code you are writing inI.E. - [ ] Implement correction based on findings