Este proyecto es educativo y Open Source. No se copia código de otros emuladores. Implementación basada únicamente en documentación técnica y tests permitidas.
Diagnóstico de PC Stuck y Por Qué CGB Nunca Habilita IE/IME
Resumen
Diagnóstico de por qué CGB nunca habilita IE/IME y por qué algunas ROMs tienen PC stuck. Se implementó instrumentación mínima gated para rastrear writes a IE/IF, ejecución de EI/DI, y watch de lecturas de IO. Se modificó rom_smoke_0442.py para snapshots con PC hotspots y IO reads top 3. Validación real con tetris_dx.gbc y mario.gbc reveló que ambos juegos escriben a IE múltiples veces pero IE permanece en 0x00, indicando que los writes se pierden o son sobrescritos. ✅ Causa dominante identificada: IE writes lost or overwritten.
Concepto de Hardware
En la Game Boy, el registro IE (0xFFFF - Interrupt Enable) controla qué interrupciones están habilitadas. Si IE bit0 = 0, aunque el PPU solicite VBlank interrupt (IF bit0 = 1), la CPU no la servirá. El registro IE es write-only desde la perspectiva del hardware (el juego puede escribir valores, pero algunos bits pueden ser read-only o tener comportamiento especial en CGB).
Registro IE (0xFFFF - Interrupt Enable): Indica qué interrupciones están habilitadas. Bit 0 = VBlank interrupt, Bit 1 = LCD STAT interrupt, Bit 2 = Timer interrupt, Bit 3 = Serial interrupt, Bit 4 = Joypad interrupt. En modo CGB, algunos bits pueden tener comportamiento diferente o ser read-only.
Instrucciones EI/DI:
- EI (0xFB): Habilita IME (Interrupt Master Enable) con retraso de 1 instrucción. IME se activa DESPUÉS de ejecutar la siguiente instrucción. Esto permite que la instrucción siguiente a EI se ejecute sin interrupciones.
- DI (0xF3): Desactiva IME inmediatamente. Se usa típicamente al inicio de rutinas críticas.
Framing Correcto del Problema: "IE bit0=0" en CGB no es una causa, es un síntoma de que el juego no está llegando al punto donde habilita interrupciones (o sus writes a IE se pierden). Si el juego intenta escribir IE pero IE permanece en 0x00, hay dos posibilidades:
- IE writes missing: El juego nunca intenta escribir IE (no hay writes a 0xFFFF)
- IE writes lost: El juego intenta escribir IE pero los writes se pierden o son sobrescritos
Fuente: Pan Docs - Interrupts, Interrupt Enable Register (IE), EI/DI Instructions
Implementación
Se implementó instrumentación mínima gated para diagnosticar por qué CGB nunca habilita IE/IME. La instrumentación incluye contadores de writes a IE/IF, ejecución de EI/DI, y watch de lecturas de IO.
Fase A - Instrumentación Mínima (Gated)
Se añadieron contadores estáticos a nivel de archivo para rastrear writes a IE/IF y ejecución de EI/DI:
- MMU.cpp: Contadores `ie_write_count` y `if_write_count` (static uint32_t) que se incrementan cuando se escriben a 0xFFFF y 0xFF0F respectivamente. Log gated (solo si VIBOY_DEBUG_PPU=1) con límite de 20 logs.
- MMU.cpp: Watch de lecturas de IO (JOYP, STAT, LY, IF, IE, KEY1, VBK, SVBK) usando std::map para contar lecturas por dirección.
- CPU.cpp: Contadores `ei_count_global` y `di_count_global` (static uint32_t) que se incrementan cuando se ejecutan EI y DI.
- MMU.hpp/CPU.hpp: Getters públicos `get_ie_write_count()`, `get_if_write_count()`, `get_last_ie_written()`, `get_last_if_written()`, `get_io_read_count()`, `get_ei_count()`, `get_di_count()`
- Cython (.pxd/.pyx): Getters expuestos a Python para acceso desde herramientas de diagnóstico
Fase B - rom_smoke: Snapshots con "Hotspots"
Se modificó `rom_smoke_0442.py` para que en snapshots imprima PC hotspots y IO reads top 3:
- Contadores de hotspots: `pc_samples` (Dict: PC -> count) que se actualiza cada 50 steps
- Snapshots mejorados: Cada 60 frames (o frames 0, 60, 120, 180, 240) se imprimen:
- Contadores IE/IF writes y EI/DI
- IO reads top 3 (direcciones más leídas)
- PC hotspots top 3 (PCs más frecuentes)
- Métricas existentes (TilemapNZ, VRAMNZ, LCDC, STAT, LY)
Fase C - Test Opcional
Se creó `tests/test_ie_write_persists_0470.py` para verificar que writes a IE persisten correctamente:
- test_ie_write_persists: Verifica que write a IE (0xFFFF=0x01) persiste (read 0xFFFF==0x01) y que el contador de writes se incrementa
- test_ie_write_multiple_values: Verifica que múltiples writes a IE persisten correctamente (0x01, 0x03, 0x07, 0x0F)
Fase D - Decisión Automática
Se ejecutó `rom_smoke_0442.py` para tetris_dx.gbc y mario.gbc (240 frames cada uno) y se generó decisión automática basada en los datos recopilados:
- tetris_dx.gbc: IEWrite=7, EI=2, DI=4, pero IE=0x00 → IE writes lost or overwritten + EI timing bug
- mario.gbc: IEWrite=62, EI=0, DI=0, pero IE=0x00 → IE writes lost or overwritten + EI never executed
Causa Dominante Identificada: IE writes lost or overwritten. Ambos juegos escriben a IE múltiples veces pero IE permanece en 0x00, indicando que los writes se pierden o son sobrescritos inmediatamente.
Archivos Afectados
src/core/cpp/MMU.hpp- Getters para contadores IE/IF writes y IO readssrc/core/cpp/MMU.cpp- Contadores de writes a IE/IF y watch de lecturas de IOsrc/core/cpp/CPU.hpp- Getters para contadores EI/DIsrc/core/cpp/CPU.cpp- Contadores de ejecución de EI/DIsrc/core/cython/mmu.pxd- Declaraciones Cython para getters MMUsrc/core/cython/mmu.pyx- Wrappers Python para getters MMUsrc/core/cython/cpu.pxd- Declaraciones Cython para getters CPUsrc/core/cython/cpu.pyx- Wrappers Python para getters CPUtools/rom_smoke_0442.py- Snapshots con PC hotspots y IO reads top 3tests/test_ie_write_persists_0470.py- Test para verificar que writes a IE persisten
Tests y Verificación
Validación de la implementación:
- Tests unitarios:
pytest tests/test_ie_write_persists_0470.py- 2 tests pasando (0.25s)def test_ie_write_persists(self): """Test: Verificar que write a IE (0xFFFF) persiste.""" self.mmu.write(0xFFFF, 0x01) ie_read = self.mmu.read(0xFFFF) assert ie_read == 0x01, \ f"IE write no persiste: escribí 0x01, leí 0x{ie_read:02X}" ie_write_count = self.mmu.get_ie_write_count() assert ie_write_count > 0, \ f"Contador de IE writes no se incrementó" - Validación Nativa: Validación de módulo compilado C++
- ROMs de test: tetris_dx.gbc y mario.gbc (240 frames cada uno) con snapshots cada 60 frames
- Decisión Automática: Análisis de datos recopilados para identificar causa dominante (IE writes lost or overwritten)
Decisión Automática
Basado en los datos recopilados de tetris_dx.gbc y mario.gbc (frames 120 y 180):
tetris_dx.gbc
- Frame 120: IEWrite=1, EI=0, DI=1, IE=0x00, PC hotspots en 0x1304-0x1306
- Frame 180: IEWrite=7, EI=2, DI=4, IE=0x00, PC hotspots en 0x1308, 0x1302, 0x1303
- Causa: IE writes lost or overwritten + EI timing bug
- Evidencia: IEWrite=7 pero IE=0x00, EI=2 pero IME=0, IOReads dominado por IF/IE (polling stuck)
mario.gbc
- Frame 120: IEWrite=42, EI=0, DI=0, IE=0x00, PC hotspots en 0x12A0, 0x129D, 0x12A2
- Frame 180: IEWrite=62, EI=0, DI=0, IE=0x00, PC hotspots en 0x12A0, 0x129D, 0x12A2
- Causa: IE writes lost or overwritten + EI never executed
- Evidencia: IEWrite=62 pero IE=0x00, EI=0, IOReads dominado por IF/IE (polling stuck)
Causa Dominante Global
IE writes lost or overwritten: Ambos juegos escriben a IE múltiples veces pero IE permanece en 0x00, indicando que los writes se pierden o son sobrescritos inmediatamente. El juego queda atrapado en loops de polling esperando que IE/IF cambien.
Hipótesis Principal: Algún componente del sistema (posiblemente relacionado con CGB o modo de hardware) está sobrescribiendo IE (0xFFFF) después de que el juego lo escribe, o los writes no persisten correctamente.
Fuentes Consultadas
- Pan Docs: Interrupts, Interrupt Enable Register (IE), EI/DI Instructions
- Pan Docs: CGB Registers (comportamiento especial de IE en modo CGB)
Integridad Educativa
Lo que Entiendo Ahora
- IE writes tracking: Los contadores de writes a IE/IF permiten determinar si el juego intenta habilitar interrupciones o está atascado antes
- EI/DI tracking: Los contadores de ejecución de EI/DI permiten determinar si el juego intenta habilitar IME
- IO polling detection: El watch de lecturas de IO permite identificar qué registro está esperando el juego (LY/STAT, IF/IE, KEY1, etc.)
- PC hotspots: Los hotspots de PC permiten identificar loops de polling esperando que algún registro cambie
Lo que Falta Confirmar
- IE write persistence: Verificar que writes a IE persisten correctamente en modo CGB (posible bug en mapping de IE)
- EI timing: Verificar timing de EI en tetris_dx.gbc (EI ejecutado pero IME sigue 0)
- Componente que sobrescribe IE: Identificar qué componente sobrescribe IE después de que el juego lo escribe
Hipótesis y Suposiciones
Hipótesis Principal: Algún componente del sistema (posiblemente relacionado con CGB o modo de hardware) está sobrescribiendo IE (0xFFFF) después de que el juego lo escribe. Esto podría ser:
- Bug en mapping de IE en modo CGB (IE se mapea a otra dirección o se lee desde otra fuente)
- Componente que resetea IE periódicamente (posiblemente relacionado con boot ROM o inicialización)
- Bug en persistencia de writes a IE (writes no se guardan correctamente en memoria)
Próximos Pasos
- [ ] Step 0471: Añadir logging detallado de writes a IE para identificar qué componente sobrescribe IE
- [ ] Step 0471: Verificar que writes a IE persisten correctamente en modo CGB
- [ ] Step 0471: Verificar timing de EI en tetris_dx.gbc (EI ejecutado pero IME sigue 0)