Step 0477: Por Qué CGB se Queda con IME/IE en 0 - Prueba de Causa Raíz

← Volver al índice

Resumen Ejecutivo

Step 0476 mostró que las ROMs CGB tienen IME=0 e IE=0x00, pero eso puede ser causa o síntoma. Step 0477 determina con evidencia por qué el juego no llega a habilitarlos: arregla el disassembler para mostrar I/O real (no DB 0xE0/0xF0), añade timeline de IME/IE/EI/DI con PC y timestamps, y aplica clasificación automática para identificar la causa raíz.

Resultado: ✅ Disassembler corregido (LDH, LD (FF00+C), LD (a16), CB prefix). ✅ Tracking de transiciones IME/EI/DI implementado. ✅ Tracking de writes IE/IF con timestamps. ✅ Tests clean-room pasando (5/5). ✅ Clasificador automático implementado (Caso A/B/C/D). ✅ Métricas añadidas a snapshots de rom_smoke.

Concepto de Hardware

EI Delayed Enable (Pan Docs)

La instrucción EI (Enable Interrupts) habilita IME (Interrupt Master Enable) con un retraso de 1 instrucción. Esto significa que:

  • Cuando se ejecuta EI, IME NO se activa inmediatamente
  • IME se activa DESPUÉS de ejecutar la siguiente instrucción
  • Esto permite que la instrucción siguiente a EI se ejecute sin interrupciones

Fuente: Pan Docs - EI instruction: "The interrupt master enable flag is set one instruction after EI is executed."

Clasificación de Causa Raíz

Step 0477 implementa un clasificador automático que identifica 4 casos posibles:

  1. Caso A: EI nunca se ejecuta (el juego está atascado antes de habilitar interrupciones)
  2. Caso B: EI se ejecuta pero IME no sube (bug EI delayed enable)
  3. Caso C: IME sube pero IE=0 (juego no habilita IE o no lo necesita)
  4. Caso D: IME+IE OK pero no hay service (revisar generación de requests)

Implementación

Fase A: Arreglar el Disassembler

El disassembler en rom_smoke_0442.py mostraba DB 0xE0/0xF0 para instrucciones LDH, ocultando el I/O real. Se añadieron:

  • LDH (FF00+n),A (0xE0)
  • LDH A,(FF00+n) (0xF0)
  • LD (FF00+C),A (0xE2)
  • LD A,(FF00+C) (0xF2)
  • LD (a16),A (0xEA)
  • LD A,(a16) (0xFA)
  • Prefijo CB (0xCB) - consume 2 bytes

También se implementó disasm_window() para desensamblar una ventana alrededor del PC y evitar empezar en mitad de instrucción.

Fase B: Tracking de Transiciones IME/EI/DI

Se añadieron miembros privados en CPU para trackear:

  • ime_set_events_count_ - Contador de veces que IME se activa
  • last_ime_set_pc_ - PC donde IME se activó por última vez
  • last_ime_set_timestamp_ - Timestamp de la última activación
  • last_ei_pc_ - PC de la última ejecución de EI
  • last_di_pc_ - PC de la última ejecución de DI

En MMU se añadieron timestamps para writes a IE/IF:

  • last_if_write_timestamp_ - Timestamp del último write a IF
  • Getter get_last_ie_write_timestamp() - Timestamp del último write a IE

Fase C: Tests Clean-Room

Se crearon dos tests para validar la implementación:

  • test_ei_delayed_enable_0477.py - Verifica que EI habilita IME con retraso de 1 instrucción (3 tests, todos pasando)
  • test_di_cancels_pending_ei_0477.py - Verifica que DI cancela un EI pendiente (2 tests, todos pasando)

Fase D: Métricas y Clasificador en rom_smoke

Se añadieron métricas al snapshot de rom_smoke:

  • IME_SetEvents - Contador de activaciones de IME
  • IME_SetPC - PC donde IME se activó
  • IME_SetTS - Timestamp de activación
  • EI_PC - PC de última ejecución de EI
  • DI_PC - PC de última ejecución de DI
  • EI_Pending - Estado de EI pending (delayed enable)
  • IEWriteTS - Timestamp del último write a IE
  • IF_WriteTS - Timestamp del último write a IF

Se implementó el clasificador automático _classify_ime_ie_state() que identifica automáticamente el caso A/B/C/D basado en las métricas.

Archivos Afectados

  • tools/rom_smoke_0442.py - Disassembler corregido, métricas añadidas, clasificador implementado
  • src/core/cpp/CPU.hpp - Miembros privados para tracking IME/EI/DI
  • src/core/cpp/CPU.cpp - Implementación de tracking y getters
  • src/core/cpp/MMU.hpp - Timestamp para IF writes
  • src/core/cpp/MMU.cpp - Implementación de timestamp tracking
  • src/core/cython/cpu.pxd - Declaraciones de getters
  • src/core/cython/cpu.pyx - Wrappers Python de getters
  • src/core/cython/mmu.pxd - Declaraciones de getters de timestamp
  • src/core/cython/mmu.pyx - Wrappers Python de getters de timestamp
  • tests/test_ei_delayed_enable_0477.py - Tests de EI delayed enable (nuevo)
  • tests/test_di_cancels_pending_ei_0477.py - Tests de DI cancela pending EI (nuevo)

Tests y Verificación

Tests Clean-Room

Comando ejecutado: pytest -q tests/test_ei_delayed_enable_0477.py tests/test_di_cancels_pending_ei_0477.py

Resultado: 5 passed in 0.49s

Test: EI Delayed Enable Básico

def test_ei_delayed_enable_basic(self):
    # Estado inicial: IME=0
    assert cpu.get_ime() == 0
    assert cpu.get_ei_pending() == False
    
    # Ejecutar EI
    cpu.step()  # EI
    assert cpu.get_ime() == 0  # NO se activa inmediatamente
    assert cpu.get_ei_pending() == True
    
    # Ejecutar NOP (siguiente instrucción)
    cpu.step()  # NOP
    assert cpu.get_ime() == 1  # Se activa después de NOP
    assert cpu.get_ei_pending() == False
    assert cpu.get_ime_set_events_count() == 1

Validación Nativa: Validación de módulo compilado C++ con tracking de IME/EI/DI.

Próximos Pasos

Step 0477 proporciona la instrumentación necesaria para identificar la causa raíz. El siguiente paso (Step 0478) debería:

  1. Ejecutar rom_smoke con las nuevas métricas en ROMs CGB problemáticas
  2. Analizar los snapshots para ver qué caso (A/B/C/D) se identifica automáticamente
  3. Usar el disassembler corregido para ver el I/O real del loop
  4. Revisar la timeline de IME/IE/EI/DI para entender el flujo