Step 0477: Why CGB Stays with IME/IE at 0 - Root Cause Test

← Return to index

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 runsEI, IME is NOT activated immediately
  • IME is activated AFTER executing the following instruction
  • This allows the statement followingEIrun 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:

  1. Case A:EI never runs (game is stuck before enabling interrupts)
  2. Case B:EI runs but IME does not go up (bug EI delayed enable)
  3. Case C:IME goes up but IE=0 (game doesn't enable IE or doesn't need it)
  4. 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 activated
  • last_ime_set_pc_- PC where IME was last activated
  • last_ime_set_timestamp_- Timestamp of last activation
  • last_ei_pc_- PC of the last EI run
  • last_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
  • Getterget_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 counter
  • IME_SetPC- PC where IME was activated
  • IME_SetTS- Activation timestamp
  • EI_PC- Latest EI Running PC
  • DI_PC- Latest DI Run PC
  • EI_Pending- EI status pending (delayed enable)
  • IEWriteTS- Timestamp of the last write to IE
  • IF_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 implemented
  • src/core/cpp/CPU.hpp- Private members for IME/EI/DI tracking
  • src/core/cpp/CPU.cpp- Implementation of tracking and getters
  • src/core/cpp/MMU.hpp- Timestamp for IF writes
  • src/core/cpp/MMU.cpp- Implementation of timestamp tracking
  • src/core/cython/cpu.pxd- Getter declarations
  • src/core/cython/cpu.pyx- Python getters wrappers
  • src/core/cython/mmu.pxd- Timestamp getter declarations
  • src/core/cython/mmu.pyx- Python wrappers of timestamp getters
  • tests/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.

Next Steps

Step 0477 provides the instrumentation necessary to identify the root cause. The next step (Step 0478) should:

  1. Executerom_smokewith the new metrics on problematic CGB ROMs
  2. Analyze the snapshots to see which case (A/B/C/D) is automatically identified
  3. Use the fixed disassembler to see the actual I/O of the loop
  4. Review the IME/IE/EI/DI timeline to understand the flow