Step 0477: Why CGB Stays with IME/IE at 0 - Root Cause Test
Executive Summary
Step 0476 showed that CGB ROMs have IME=0 and IE=0x00, but that could be a cause or a symptom. Step 0477 clearly determines why the game fails to enable them: fixes the disassembler to show real I/O (not DB 0xE0/0xF0), adds IME/IE/EI/DI timeline with PC and timestamps, and applies automatic classification to identify the root cause.
Result:✅ Disassembler corrected (LDH, LD (FF00+C), LD (a16), CB prefix). ✅ IME/EI/DI transition tracking implemented. ✅ Tracking IE/IF writes with timestamps. ✅ Clean-room tests passing (5/5). ✅ Automatic classifier implemented (Case A/B/C/D). ✅ Metrics added to rom_smoke snapshots.
Hardware Concept
EI Delayed Enable (Pan Docs)
The instructionEI(Enable Interrupts) enables IME (Interrupt Master Enable) with a delay of 1 instruction. This means that:
- When it runs
EI, IME is NOT activated immediately - IME is activated AFTER executing the following instruction
- This allows the statement following
EIrun without interruptions
Fountain:Pan Docs - EI instruction: "The interrupt master enable flag is set one instruction after EI is executed."
Root Cause Classification
Step 0477 implements an automatic classifier that identifies 4 possible cases:
- Case A:EI never runs (game is stuck before enabling interrupts)
- Case B:EI runs but IME does not go up (bug EI delayed enable)
- Case C:IME goes up but IE=0 (game doesn't enable IE or doesn't need it)
- Case D:IME+IE OK but there is no service (check request generation)
Implementation
Phase A: Fix the Disassembler
The disassembler inrom_smoke_0442.pyshowedDB 0xE0/0xF0for LDH instructions, hiding the real I/O. Added:
LDH (FF00+n),A(0xE0)LDH A,(FF00+n)(0xF0)LD (FF00+C),A(0xE2)LD A,(FF00+C)(0xF2)RH (a16),A(0xEA)LD A,(a16)(0xFA)- CB prefix (0xCB) - consumes 2 bytes
It was also implementeddisasm_window()to disassemble a window around the PC and avoid starting in the middle of an instruction.
Phase B: Tracking IME/EI/DI Transitions
Private members added inCPUto track:
ime_set_events_count_- Counter of times that IME is activatedlast_ime_set_pc_- PC where IME was last activatedlast_ime_set_timestamp_- Timestamp of last activationlast_ei_pc_- PC of the last EI runlast_di_pc_- PC of the last DI run
InMMUadded timestamps for writes to IE/IF:
last_if_write_timestamp_- Timestamp of last write to IF- Getter
get_last_ie_write_timestamp()- Timestamp of the last write to IE
Phase C: Clean-Room Tests
Two tests were created to validate the implementation:
test_ei_delayed_enable_0477.py- Verify that EI enables IME with a delay of 1 instruction (3 tests, all passing)test_di_cancels_pending_ei_0477.py- Verify that DI cancels a pending EI (2 tests, all passing)
Phase D: Metrics and Classifier in rom_smoke
Metrics were added to the snapshotrom_smoke:
IME_SetEvents- IME activation counterIME_SetPC- PC where IME was activatedIME_SetTS- Activation timestampEI_PC- Latest EI Running PCDI_PC- Latest DI Run PCEI_Pending- EI status pending (delayed enable)IEWriteTS- Timestamp of the last write to IEIF_WriteTS- Timestamp of last write to IF
Automatic classifier implemented_classify_ime_ie_state()which automatically identifies the A/B/C/D case based on the metrics.
Affected Files
tools/rom_smoke_0442.py- Disassembler fixed, metrics added, classifier implementedsrc/core/cpp/CPU.hpp- Private members for IME/EI/DI trackingsrc/core/cpp/CPU.cpp- Implementation of tracking and getterssrc/core/cpp/MMU.hpp- Timestamp for IF writessrc/core/cpp/MMU.cpp- Implementation of timestamp trackingsrc/core/cython/cpu.pxd- Getter declarationssrc/core/cython/cpu.pyx- Python getters wrapperssrc/core/cython/mmu.pxd- Timestamp getter declarationssrc/core/cython/mmu.pyx- Python wrappers of timestamp getterstests/test_ei_delayed_enable_0477.py- EI delayed enable tests (new)tests/test_di_cancels_pending_ei_0477.py- DI tests cancel pending EI (new)
Tests and Verification
Clean-Room Tests
Command executed: pytest -q tests/test_ei_delayed_enable_0477.py tests/test_di_cancels_pending_ei_0477.py
Result: 5 passed in 0.49s
Test: EI Delayed Enable Basic
def test_ei_delayed_enable_basic(self):
# Initial state: IME=0
assert cpu.get_ime() == 0
assert cpu.get_ei_pending() == False
# Run EI
cpu.step() # EI
assert cpu.get_ime() == 0 # NOT triggered immediately
assert cpu.get_ei_pending() == True
# Execute NOP (next instruction)
cpu.step() # NOP
assert cpu.get_ime() == 1 # Triggered after NOP
assert cpu.get_ei_pending() == False
assert cpu.get_ime_set_events_count() == 1
Native Validation:Validation of compiled C++ module with IME/EI/DI tracking.