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.
Operación "Interrupt Awakening" - Depuración de Activación de Interrupciones
Resumen
Este Step implementa la Operación "Interrupt Awakening" para investigar por qué Pokémon Red está atrapado en un bucle infinito esperando que el flag 0xD732 cambie. El análisis del Step 0279 confirmó que el problema NO es un Reset Loop, sino un "coma inducido": el juego está atascado en el bucle de polling (PC: 0x614D-0x6153) esperando que una ISR de V-Blank modifique el flag, pero las interrupciones están deshabilitadas (IE=0x00). Aunque se detectó un EI en PC:0x60A6, las interrupciones no parecen estar activas durante el polling. Este Step añade instrumentación ultra-precisa para rastrear el estado de IE e IME cuando se ejecuta EI, y monitorea el bucle de polling para detectar si alguien está escribiendo en IE durante la espera.
Concepto de Hardware
El Retraso de un Ciclo de la Instrucción EI
La instrucción EI (Enable Interrupts, opcode 0xFB) tiene un comportamiento especial en el hardware real del Game Boy: el Interrupt Master Enable (IME) se activa DESPUÉS de ejecutar la siguiente instrucción, no inmediatamente. Este retraso de un ciclo es crítico porque permite que la instrucción siguiente a EI se ejecute sin interrupciones, lo cual es necesario para configuraciones atómicas o para evitar condiciones de carrera.
Flujo de activación de interrupciones:
- Ejecución de EI: El opcode
0xFBse ejecuta, peroIMEno se activa inmediatamente. En su lugar, se marca una bandera interna (ime_scheduled_) que indica queIMEdebe activarse después de la siguiente instrucción. - Ejecución de la siguiente instrucción: La instrucción que sigue a
EIse ejecuta conIME=false, garantizando que no se interrumpa. - Activación de IME: Al inicio del siguiente ciclo de instrucción, antes del fetch, se verifica si
ime_scheduled_estrue. Si lo es, se activaIMEy se limpia la bandera. - Procesamiento de interrupciones: Una vez que
IMEestá activo, el sistema puede procesar interrupciones pendientes siIE & IF != 0.
Fuente: Pan Docs - "EI Instruction": "Interrupts are enabled after the instruction following EI."
Registros de Interrupciones
El Game Boy tiene dos registros críticos para el manejo de interrupciones:
- IE (0xFFFF) - Interrupt Enable: Registro de habilitación de fuentes de interrupciones. Cada bit habilita una fuente específica:
- Bit 0: V-Blank
- Bit 1: LCD STAT
- Bit 2: Timer
- Bit 3: Serial
- Bit 4: Joypad
- IF (0xFF0F) - Interrupt Flag: Registro de flags de interrupciones pendientes. Cada bit indica si una interrupción está pendiente.
- IME (Interrupt Master Enable): Flag interno de la CPU que controla si las interrupciones pueden ser procesadas. Solo se puede activar mediante
EI(con retraso) o desactivar medianteDI(inmediato).
Condición para procesar una interrupción: IME == true && (IE & IF) != 0
Fuente: Pan Docs - "Interrupts": "An interrupt is processed if IME is set and the corresponding bit in both IE and IF is set."
Implementación
Este Step añade dos instrumentaciones críticas para depurar el problema de activación de interrupciones:
1. Rastreo Ultra-Preciso de EI e IME
Se modificó el caso 0xFB (EI) en CPU.cpp para capturar el estado exacto de IE e IME cuando se intenta habilitar las interrupciones. Esto nos permite identificar si el problema es que IE está en 0x00 cuando se ejecuta EI, o si IME no se activa correctamente.
case 0xFB: // EI (Enable Interrupts)
{
// --- Step 0280: Rastreo Ultra-Preciso de EI e IME ---
uint8_t ie_val = mmu_->read(0xFFFF);
printf("[CPU-EI] Instrucción EI en PC:0x%04X | IE Actual:0x%02X | IME previo:%d | IME programado:%d\n",
original_pc, ie_val, ime_ ? 1 : 0, ime_scheduled_ ? 1 : 0);
ime_scheduled_ = true;
cycles_ += 1;
return 1;
}
Información capturada:
- PC original: Dirección donde se ejecutó
EI - IE Actual: Valor del registro IE en el momento de ejecutar
EI - IME previo: Estado de IME antes de ejecutar
EI - IME programado: Si ya había un
EIpendiente (debería ser 0 normalmente)
2. Sniper de Polling con Estado de IE
Se añadió un monitor del bucle de polling (PC: 0x614D-0x6153) que captura el estado de IE, IF, IME y el flag 0xD732 durante la espera. Esto nos permite ver si alguien está escribiendo en IE durante la espera, o si IE cambia mágicamente a cero.
// --- Step 0280: Sniper de Polling con Estado de IE ---
if (original_pc >= 0x614D && original_pc <= 0x6153) {
static int polling_watch_count = 0;
if (polling_watch_count < 20) {
printf("[POLLING-WATCH] PC:%04X | IE:0x%02X | IF:0x%02X | IME:%d | D732:0x%02X\n",
original_pc, mmu_->read(0xFFFF), mmu_->read(0xFF0F),
ime_ ? 1 : 0, mmu_->read(0xD732));
polling_watch_count++;
}
}
Información capturada:
- PC: Dirección actual dentro del bucle de polling
- IE: Valor del registro IE durante la espera
- IF: Valor del registro IF (interrupciones pendientes)
- IME: Estado del Interrupt Master Enable
- D732: Valor del flag que el juego está esperando que cambie
3. Verificación de la Lógica de handle_interrupts
Se verificó que la función handle_interrupts() no modifica el registro IE (solo lo lee). La función solo lee IE para calcular las interrupciones pendientes (pending = IE & IF), pero nunca escribe en IE. Esto confirma que el problema no está en el procesamiento de interrupciones, sino en la activación de IE o IME.
Componentes modificados
src/core/cpp/CPU.cpp:- Modificado
case 0xFB(EI) para agregar rastreo detallado de IE e IME - Agregado sniper de polling al final de
step()para monitorear el bucle de espera
- Modificado
Decisiones de diseño
- Límite de 20 logs en polling: Se limita el número de logs del sniper de polling a 20 para evitar saturar el log, pero es suficiente para ver el patrón del bucle.
- Captura de IE en tiempo real: Se lee
IEdirectamente de la MMU en cada log para capturar cambios en tiempo real, no se usa un valor cacheado. - Uso de original_pc: Se usa
original_pc(PC antes del fetch) para asegurar que capturamos la dirección correcta, incluso si el PC avanza durante la ejecución de la instrucción.
Archivos Afectados
src/core/cpp/CPU.cpp- Modificado caso 0xFB (EI) y agregado sniper de polling
Tests y Verificación
La verificación se realizará mediante análisis de logs durante la ejecución de Pokémon Red:
- Logs [CPU-EI]: Deben aparecer cuando el juego ejecuta
EI. SiIE Actuales0x00en el momento delEI, confirmaremos que el problema es que el juego está habilitando el maestro (IME) pero no ha habilitado ninguna fuente individual (IE). - Logs [POLLING-WATCH]: Deben aparecer cuando el juego entra al bucle de polling. Si
IEcambia mágicamente a cero durante la espera, sabremos que algo está escribiendo enIEincorrectamente. - Validación de módulo compilado C++: El código se compila sin errores y el módulo se genera correctamente.
Comando de prueba:
python setup.py build_ext --inplace
python main.py roms/pkmn.gb > debug_step_0280.log 2>&1
Análisis esperado:
- Buscar
[CPU-EI]en el log para ver el estado de IE cuando se ejecuta EI - Buscar
[POLLING-WATCH]para ver el estado de IE durante el bucle de espera - Verificar si IE cambia entre el momento del EI y el bucle de polling
Fuentes Consultadas
- Pan Docs: EI Instruction - Enable Interrupts
- Pan Docs: Interrupts
- Análisis del Step 0279: Confirmación de que el problema NO es un Reset Loop, sino un bucle de espera infinito
Integridad Educativa
Lo que Entiendo Ahora
- Retraso de un ciclo de EI: La instrucción
EIno activaIMEinmediatamente, sino después de ejecutar la siguiente instrucción. Esto es un comportamiento del hardware real, no una limitación de nuestra implementación. - Diferencia entre IE e IME:
IE(0xFFFF) habilita fuentes específicas de interrupciones, mientras queIMEes un flag interno que controla si las interrupciones pueden ser procesadas. Ambos deben estar activos para que una interrupción se procese. - El problema real: El juego está atascado en un bucle de espera porque
IE=0x00, lo que impide que las interrupciones se procesen, incluso siIMEestá activo y hay interrupciones pendientes enIF.
Lo que Falta Confirmar
- ¿Por qué IE se queda en 0x00?: Necesitamos verificar si el juego nunca escribe en
IEdespués delEI, o si algo está escribiendo0x00enIEincorrectamente. - ¿El EI se ejecuta correctamente?: Necesitamos verificar si el
EIenPC:0x60A6realmente programaIMEcorrectamente, y siIMEse activa después de la siguiente instrucción. - ¿Hay escrituras en IE durante el polling?: El sniper de polling nos dirá si alguien está escribiendo en
IEdurante la espera, lo cual podría explicar por quéIEse queda en0x00.
Hipótesis y Suposiciones
Hipótesis principal: El juego ejecuta EI en PC:0x60A6, pero IE está en 0x00 en ese momento. El juego espera que algo más (probablemente una ISR o código de inicialización) habilite las fuentes de interrupciones en IE, pero eso nunca ocurre porque las interrupciones están deshabilitadas.
Suposición: El juego debería escribir en IE (por ejemplo, LD A, 0x0D; LDH (0xFF), A para habilitar V-Blank y Timer) antes o después del EI, pero por alguna razón no lo hace o se ejecuta incorrectamente.
Próximos Pasos
- [ ] Ejecutar el emulador con la nueva instrumentación y analizar los logs
[CPU-EI]y[POLLING-WATCH] - [ ] Verificar si
IEestá en0x00cuando se ejecutaEIenPC:0x60A6 - [ ] Verificar si
IEcambia durante el bucle de polling - [ ] Si
IEestá en0x00, buscar en el código del juego dónde debería habilitarse - [ ] Si
IEcambia a0x00durante el polling, identificar qué código está escribiendo enIE - [ ] Implementar corrección basada en los hallazgos