Step 0411: Fix IRQ Pipeline + Post-Boot Registers Coherente
📋 Resumen Ejecutivo
Problema: Step 0410 reveló que Pokémon (pkmn.gb, Oro.gbc) quedan bloqueados con IF=0x00 esperando interrupciones que nunca llegan, impidiendo la carga de tiles.
Hipótesis: Posible mismatch entre post-boot CPU registers y modo HW detectado, o LCD/Timer apagados.
Solución: Instrumentación completa del pipeline de IRQ con contadores directos + alinear registros post-boot con modo HW (DMG vs CGB) detectado desde header ROM.
Resultado: ✅ IRQs generándose correctamente. Oro.gbc avanza significativamente (RETI, IE=0x1F). Pokémon Red/Tetris DX esperan Joypad input (nuevo Step requerido).
🔧 Concepto de Hardware
1. Pipeline de Interrupciones (IF, IE, IME)
Flujo de IRQ en Game Boy (Pan Docs - Interrupts):
- IF (0xFF0F): Interrupt Flag - Bits activados cuando hardware solicita IRQ (VBlank, STAT, Timer, Serial, Joypad)
- IE (0xFFFF): Interrupt Enable - Bits que habilitan qué IRQs pueden ser atendidas
- IME: Interrupt Master Enable - Flag global que habilita/deshabilita todas las IRQs (activado con EI, desactivado con DI)
- Condición para servir IRQ:
IME && (IE & IF) != 0
Ejemplo de IRQ VBlank:
1. PPU entra en VBlank (LY=144) → request_interrupt(0) → IF bit 0 = 1
2. Si IE bit 0 = 1 (VBlank habilitado) Y IME = 1 → CPU salta a vector 0x0040
3. CPU ejecuta ISR VBlank
4. ISR limpia IF bit 0 (escribiendo 0 a IF) y ejecuta RETI
2. Post-Boot CPU Registers según Modo HW
Problema: El registro A post-boot identifica el hardware al juego:
- DMG:
A=0x01→ Juego sabe que está en Game Boy original - CGB:
A=0x11→ Juego sabe que está en Game Boy Color
Juegos dual-mode (compatible DMG/CGB) toman caminos diferentes según A. Si A no coincide con el modo HW real, el juego puede quedar en estado inconsistente.
Post-Boot State completo (Pan Docs - Power Up Sequence):
DMG: AF=0x01B0, BC=0x0013, DE=0x00D8, HL=0x014D, SP=0xFFFE, PC=0x0100
CGB: AF=0x1180, BC=0x0000, DE=0xFF56, HL=0x000D, SP=0xFFFE, PC=0x0100
3. LCDC bit7 y Timer (TAC bit2)
- LCDC bit7=0 (LCD OFF): PPU se detiene, LY queda en 0, NO hay VBlank IRQ
- TAC bit2=0 (Timer OFF): Registro TIMA no incrementa, NO hay Timer IRQ
Si ambos están apagados, IF puede quedarse en 0 indefinidamente.
💻 Implementación
Tarea 1A: Contadores directos en MMU::request_interrupt
Añadidos contadores en MMU.hpp para rastrear todos los requests de IRQ (independientes de cambios en IF):
// MMU.hpp - Contadores de IRQ requests
mutable int irq_req_vblank_count_; // Total requests VBlank (bit 0)
mutable int irq_req_stat_count_; // Total requests STAT (bit 1)
mutable int irq_req_timer_count_; // Total requests Timer (bit 2)
mutable int irq_req_serial_count_; // Total requests Serial (bit 3)
mutable int irq_req_joypad_count_; // Total requests Joypad (bit 4)
// MMU.cpp - request_interrupt()
void MMU::request_interrupt(uint8_t bit) {
// Incrementar contador según el bit (SIEMPRE, sin importar IF)
switch (bit) {
case 0: irq_req_vblank_count_++; break;
case 1: irq_req_stat_count_++; break;
case 2: irq_req_timer_count_++; break;
case 3: irq_req_serial_count_++; break;
case 4: irq_req_joypad_count_++; break;
}
// ... resto del código
}
Tarea 1B: Resumen periódico de IRQ requests
Añadido log_irq_requests_summary() en MMU, llamado cada 120 frames desde PPU:
// PPU.cpp - Al entrar en VBlank
if (frame_counter_ % 120 == 0) {
mmu_->log_irq_requests_summary(frame_counter_);
}
// MMU.cpp - log_irq_requests_summary()
[IRQ-SUMMARY] Requests generados (totales):
[IRQ-SUMMARY] VBlank (bit 0): 2
[IRQ-SUMMARY] STAT (bit 1): 144
[IRQ-SUMMARY] Timer (bit 2): 3
[IRQ-SUMMARY] Estado actual:
[IRQ-SUMMARY] IE (0xFFFF): 0x1F (VBlank STAT Timer Serial Joypad)
[IRQ-SUMMARY] IF (0xFF0F): 0x01 (VBlank )
[IRQ-SUMMARY] LCDC (0xFF40): 0xE3 (LCD ON)
[IRQ-SUMMARY] TAC (0xFF07): 0x04 (Timer ON)
[IRQ-SUMMARY] Análisis:
[IRQ-SUMMARY] ✓ HAY INTERRUPCIONES PENDIENTES
Tarea 2: Post-Boot Registers alineados con Modo HW
Añadido método apply_post_boot_state(bool is_cgb_mode) en CoreRegisters:
// Registers.cpp
void CoreRegisters::apply_post_boot_state(bool is_cgb_mode) {
if (is_cgb_mode) {
a = 0x11; b = 0x00; c = 0x00;
d = 0xFF; e = 0x56;
h = 0x00; l = 0x0D;
f = 0x80; // Z=1, N=0, H=0, C=0
} else {
a = 0x01; b = 0x00; c = 0x13;
d = 0x00; e = 0xD8;
h = 0x01; l = 0x4D;
f = 0xB0; // Z=1, N=0, H=1, C=1
}
sp = 0xFFFE;
pc = 0x0100;
}
// viboy.py - Después de cargar ROM
hardware_mode_str = self._mmu.get_hardware_mode()
is_cgb_mode = (hardware_mode_str == "CGB")
self._regs.apply_post_boot_state(is_cgb_mode)
Tarea 3: Extender detector de wait-loop
Añadida captura de estado completo en CPU.cpp cuando se detecta wait-loop:
[WAITLOOP] Timer: TAC=0x00, DIV=0x42, TIMA=0x00
[WAITLOOP] - Timer OFF
[WAITLOOP] ⚠️ TIMER APAGADO: No habrá Timer IRQ
[WAITLOOP] CGB: KEY1=0x00, VBK=0x00, SVBK=0x00
🧪 Tests y Verificación
Comandos Ejecutados
python3 setup.py build_ext --inplace
timeout 60s python3 main.py roms/pkmn.gb > logs/step0411_pkmn_irq_fix.log 2>&1
timeout 60s python3 main.py roms/Oro.gbc > logs/step0411_oro_irq_fix.log 2>&1
timeout 30s python3 main.py roms/tetris_dx.gbc > logs/step0411_tetris_dx_baseline.log 2>&1
# Análisis seguro (sin saturar contexto)
grep -E "\[IRQ-SUMMARY\]|\[WAITLOOP\]|Post-Boot State" logs/step0411_*.log | head -n 160
Resultados: Oro.gbc (Pokémon Oro)
✅ GRAN AVANCE
- ✅ VBlank IRQs: 2 (generándose correctamente)
- ✅ STAT IRQs: 144 (LCD STAT interrupciones activas)
- ✅ Timer IRQs: 3
- ✅ IE=0x1F (todas las IRQs habilitadas)
- ✅ RETI ejecutándose (ISRs funcionando)
- ✅ LCDC cambia dinámicamente (0x91→0x00→0xE3)
- ✅ Hardware Mode: CGB detectado, registros post-boot coherentes
Resultados: pkmn.gb (Pokémon Red)
⚠️ PROGRESO PARCIAL
- ✅ VBlank IRQs: 1+ (generándose)
- ✅ LCDC: Cambios dinámicos (0x91→0x80→0x81→0xE3)
- ⚠️ IE=0x0D (VBlank, Timer, Joypad habilitadas, pero Timer OFF)
- ⚠️ TAC=0x00 (Timer apagado → no hay Timer IRQ)
- ⚠️ Esperando Joypad input (polling activo en 0x600A-0x600E)
- ✅ Hardware Mode: DMG detectado,
A=0x01
Resultados: tetris_dx.gbc (Tetris DX)
⚠️ ESPERANDO INPUT (sin regresión)
- ✅ VBlank IRQs: 1+ (generándose)
- ⚠️ IE=0x00 (interrupciones NO habilitadas al inicio)
- ⚠️ Esperando Joypad input (polling activo)
- ✅ Hardware Mode: CGB detectado,
A=0x11
Fragmento de Test (Análisis IRQ)
// Test implícito: Verificación de contadores IRQ
// El test ejecuta el emulador y analiza logs
// Extracto de log (Oro.gbc):
[IRQ-SUMMARY] Step 0411 - Frame 0
[IRQ-SUMMARY] Requests generados (totales):
[IRQ-SUMMARY] VBlank (bit 0): 2
[IRQ-SUMMARY] STAT (bit 1): 144
[IRQ-SUMMARY] Timer (bit 2): 3
[IRQ-SUMMARY] Estado actual:
[IRQ-SUMMARY] IE (0xFFFF): 0x1F (VBlank STAT Timer Serial Joypad)
[IRQ-SUMMARY] IF (0xFF0F): 0x01 (VBlank )
[IRQ-SUMMARY] LCDC (0xFF40): 0xE3 (LCD ON)
[IRQ-SUMMARY] TAC (0xFF07): 0x04 (Timer ON)
[IRQ-SUMMARY] Análisis:
[IRQ-SUMMARY] ✓ HAY INTERRUPCIONES PENDIENTES
// Validación de módulo compilado C++ ✓
📁 Archivos Modificados
src/core/cpp/MMU.hpp- Añadidos contadores IRQ y declaración delog_irq_requests_summary()src/core/cpp/MMU.cpp- Implementación de contadores enrequest_interrupt()y función de resumensrc/core/cpp/PPU.cpp- Llamada periódica alog_irq_requests_summary()src/core/cpp/CPU.cpp- Extendido detector de wait-loop con Timer y CGB infosrc/core/cpp/Registers.hpp- Declaración deapply_post_boot_state()src/core/cpp/Registers.cpp- Implementación deapply_post_boot_state()src/core/cython/registers.pyx- Binding Cython paraapply_post_boot_state()src/core/cython/registers.pxd- Declaración C++ para Cythonsrc/viboy.py- Llamada aapply_post_boot_state()después de cargar ROM
🔍 Hallazgos Clave
- Pipeline de IRQ funciona: Los contadores demuestran que
request_interrupt()se llama correctamente (VBlank, STAT, Timer). - Oro.gbc avanza significativamente: Ejecuta RETI, tiene IE=0x1F activo, y LCDC cambia dinámicamente (apagado/encendido). Esto indica que el juego progresa más allá del boot.
- Pokémon Red y Tetris DX esperan input: Ambos quedan en polling de Joypad (IE bajo, esperando botones). Necesitan simulación de input para avanzar.
- Post-Boot Registers ahora coherentes:
A=0x01(DMG) para pkmn.gb,A=0x11(CGB) para Oro.gbc y tetris_dx.gbc. - Diagnóstico mejorado:
[IRQ-SUMMARY]proporciona análisis claro del estado de IRQs, facilitando debugging futuro.
🚀 Próximos Pasos
- Step 0412: Simulación de Joypad Input - Implementar auto-input simulado (ej: presionar START automáticamente tras N frames) para que juegos que esperan input puedan avanzar.
- Step 0413: Verificar carga de tiles post-input - Una vez que los juegos reciban input, verificar si cargan tiles correctamente.
- Step 0414: Investigar por qué Oro.gbc avanza pero pkmn.gb no - Analizar diferencias en inicialización entre ambos juegos Pokémon.