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 IE Hunter: Rastreo del Registro IE e Interrupciones
Resumen
Este Step implementa la "Operación IE Hunter" para rastrear quién y cuándo modifica el registro
de habilitación de interrupciones (IE, dirección 0xFFFF). El análisis del Step 0273
reveló que IE = 0x00 (todas las interrupciones deshabilitadas) mientras que
IF = 0x01 (V-Blank pendiente), lo que impide que el juego procese interrupciones
y causa un deadlock en bucles de espera.
Se añadió instrumentación en tres puntos críticos: (1) captura de cada escritura en 0xFFFF
en MMU.cpp, (2) rastreo del flujo de ejecución después de que termine el bucle de limpieza
de VRAM (PC:36E3), y (3) logging de las instrucciones EI (Enable Interrupts) y DI
(Disable Interrupts) en CPU.cpp. El objetivo es identificar el momento exacto en que el
registro IE se deshabilita y qué código lo causa.
Concepto de Hardware
El sistema de interrupciones de la Game Boy tiene dos niveles de control que deben estar activos simultáneamente para que una interrupción se procese:
1. IME (Interrupt Master Enable) - Flag Interno de la CPU
El IME es un flag interno de la CPU que se controla mediante las instrucciones EI (0xFB)
y DI (0xF3). Este flag determina si la CPU puede procesar interrupciones en el siguiente
ciclo de instrucción. La instrucción EI tiene un comportamiento especial: activa el IME
después de ejecutar la siguiente instrucción, permitiendo que esa instrucción se ejecute
sin interrupciones. Esto es crítico para rutinas que necesitan ejecutar una secuencia atómica.
2. IE (Interrupt Enable Register) - Registro Mapeado en 0xFFFF
El registro IE es un registro de hardware mapeado en la dirección 0xFFFF que controla
qué tipos de interrupciones están habilitadas. Cada bit corresponde a un tipo de interrupción:
- Bit 0: V-Blank Interrupt
- Bit 1: LCD STAT Interrupt
- Bit 2: Timer Interrupt
- Bit 3: Serial Interrupt
- Bit 4: Joypad Interrupt
Si un bit está en 0, ese tipo de interrupción está deshabilitado, incluso si el hardware
solicita la interrupción (activando el bit correspondiente en IF).
Condición para Procesar una Interrupción
Para que una interrupción se procese, se deben cumplir tres condiciones simultáneas:
- IME = 1: El flag interno de la CPU debe estar activo.
- IE[bit] = 1: El bit correspondiente en IE debe estar activo.
- IF[bit] = 1: El bit correspondiente en IF debe estar activo (solicitud pendiente).
Si cualquiera de estas condiciones falla, la interrupción no se procesa, aunque el hardware siga
solicitándola. En el caso de Pokémon Red, el análisis del Step 0273 reveló que IE = 0x00
(todos los bits deshabilitados) mientras que IF = 0x01 (V-Blank pendiente), lo que significa
que el juego está esperando una interrupción que nunca se puede procesar porque IE está apagado.
Fuente: Pan Docs - Interrupts Section. El registro IE es de lectura/escritura y se
puede modificar en cualquier momento mediante una instrucción LD (0xFFFF), A o similar.
Implementación
Se implementaron tres sistemas de instrumentación complementarios para rastrear el estado de las interrupciones y el flujo de ejecución:
1. Rastreo de Escrituras en IE (MMU.cpp)
Se añadió un bloque de instrumentación en el método MMU::write() que detecta cada
escritura en la dirección 0xFFFF (registro IE). El log incluye:
- El nuevo valor escrito en IE
- El PC desde donde se ejecutó la escritura
- El banco ROM actual
Esto permite identificar exactamente qué código está modificando IE y cuándo ocurre.
// --- Step 0274: IE-WRITE - Rastreo del Registro de Habilitación de Interrupciones ---
if (addr == 0xFFFF) {
printf("[IE-WRITE] Nuevo valor: 0x%02X desde PC: 0x%04X (Banco:%d)\n",
value, debug_current_pc, current_rom_bank_);
}
2. Rastreo Post-Limpieza VRAM (CPU.cpp)
Se añadió un sistema de "trail" (rastro) que se activa cuando el PC sale del bucle de limpieza
de VRAM en 0x36E3. El sistema asume que el bucle tiene 6 bytes (3 bytes de instrucciones
+ 3 bytes de salto condicional), por lo que la salida debería estar en PC:36E9.
Cuando se detecta que el PC llega a 0x36E9, se activa el modo de rastreo y se imprimen
las siguientes 100 instrucciones con el estado completo de la CPU (registros, opcodes, IE, IF).
Esto permite ver qué camino toma el juego después de limpiar la VRAM y si hay código que deshabilita
IE en ese momento.
// --- Step 0274: Seguimiento Post-Limpieza VRAM ---
static bool tracing_after_vram_clear = false;
static int trace_count = 0;
if (regs_->pc == 0x36E9 && !tracing_after_vram_clear) {
printf("[VRAM-CLEAR-EXIT] El bucle de limpieza ha terminado. Iniciando Trail...\n");
tracing_after_vram_clear = true;
}
if (tracing_after_vram_clear && trace_count < 100) {
printf("[TRAIL] PC:%04X OP:%02X AF:%04X BC:%04X DE:%04X HL:%04X IE:%02X IF:%02X\n",
regs_->pc, mmu_->read(regs_->pc),
regs_->get_af(), regs_->get_bc(), regs_->get_de(), regs_->get_hl(),
mmu_->read(0xFFFF), mmu_->read(0xFF0F));
trace_count++;
}
3. Monitor de Instrucciones EI/DI (CPU.cpp)
Se añadió logging en los casos de las instrucciones EI (0xFB) y DI (0xF3)
para rastrear cuándo el código intenta activar o desactivar el IME. Esto complementa el rastreo de IE,
ya que ambas condiciones (IME e IE) deben estar activas para procesar interrupciones.
case 0xF3: // DI (Disable Interrupts)
printf("[CPU] DI (Disable Interrupts) en PC:0x%04X\n", (regs_->pc - 1) & 0xFFFF);
ime_ = false;
ime_scheduled_ = false;
cycles_ += 1;
return 1;
case 0xFB: // EI (Enable Interrupts)
printf("[CPU] EI (Enable Interrupts) en PC:0x%04X\n", (regs_->pc - 1) & 0xFFFF);
ime_scheduled_ = true;
cycles_ += 1;
return 1;
Decisiones de Diseño
- Límite de 100 trazas: El rastreo post-limpieza se limita a 100 instrucciones para evitar saturación de logs. Si el problema ocurre más tarde, se puede aumentar el límite.
- PC:36E9 como trigger: Se asume que el bucle de limpieza tiene 6 bytes basándose en el análisis del Step 0273. Si el trigger no se activa, se puede ajustar la dirección.
- Logging sin límite para IE-WRITE: Las escrituras en IE son críticas y relativamente infrecuentes, por lo que no se limita el número de logs para no perder información.
Archivos Afectados
src/core/cpp/MMU.cpp- Añadido rastreo de escrituras en IE (0xFFFF)src/core/cpp/CPU.cpp- Añadido rastreo post-limpieza VRAM y logging de EI/DI
Tests y Verificación
La instrumentación se valida mediante ejecución del emulador con Pokémon Red y análisis de los logs generados. Los logs deben mostrar:
- [IE-WRITE]: Cada vez que se escribe en 0xFFFF, especialmente si se escribe 0x00
- [VRAM-CLEAR-EXIT]: Cuando el bucle de limpieza termina
- [TRAIL]: Las siguientes 100 instrucciones después de salir del bucle
- [CPU] DI/EI: Cada vez que se ejecuta una instrucción DI o EI
Comando de prueba:
python main.py roms/pkmn.gb
Validación esperada: Los logs deben revelar si hay código que escribe 0x00
en 0xFFFF después de la limpieza de VRAM, o si el IME se desactiva mediante una instrucción
DI sin que se reactive posteriormente.
Validación de módulo compilado C++: La compilación debe completarse sin errores y los logs deben aparecer durante la ejecución del emulador.
Fuentes Consultadas
- Pan Docs: Interrupts Section - Explicación del sistema de interrupciones, IME, IE e IF
- Pan Docs: CPU Instruction Set - Comportamiento de las instrucciones EI y DI
- Análisis del Step 0273: Identificación del problema de IE=0x00 e IF=0x01
Integridad Educativa
Lo que Entiendo Ahora
- Sistema de interrupciones de dos niveles: El IME (flag interno) y el IE (registro mapeado) son independientes y ambos deben estar activos para procesar interrupciones. Esto permite un control fino del sistema de interrupciones.
- Comportamiento de EI: La instrucción EI activa el IME después de ejecutar la siguiente instrucción, permitiendo secuencias atómicas. Esto es crítico para rutinas que necesitan ejecutar código sin interrupciones.
- Deadlock por IE=0: Si el registro IE está en 0x00, ninguna interrupción se puede procesar, incluso si el hardware solicita interrupciones (IF) y el IME está activo. Esto puede causar bucles infinitos si el juego espera una interrupción que nunca se procesa.
Lo que Falta Confirmar
- Momento exacto de deshabilitación: Necesitamos identificar qué código escribe 0x00 en 0xFFFF y cuándo ocurre. Los logs de [IE-WRITE] deberían revelar esto.
- Flujo post-limpieza: El rastreo [TRAIL] debe mostrar qué camino toma el juego después de limpiar la VRAM y si hay código que deshabilita IE en ese momento.
- Estado de IME: Los logs de [CPU] DI/EI deben mostrar si el IME se desactiva y no se reactiva, o si el problema es solo el registro IE.
Hipótesis y Suposiciones
Hipótesis principal: El juego deshabilita IE (escribe 0x00 en 0xFFFF) durante o después de la limpieza de VRAM, y nunca lo reactiva antes de entrar en el bucle de espera. Esto causa que el juego espere una interrupción que nunca se puede procesar.
Suposición sobre PC:36E9: Asumimos que el bucle de limpieza tiene 6 bytes basándonos en el análisis del Step 0273. Si el trigger no se activa, puede ser que el bucle tenga una longitud diferente o que el PC salte a otra dirección.
Próximos Pasos
- [ ] Ejecutar el emulador con Pokémon Red y analizar los logs generados
- [ ] Identificar el momento exacto en que IE se deshabilita (buscar [IE-WRITE] con valor 0x00)
- [ ] Analizar el [TRAIL] para ver qué código se ejecuta después de la limpieza de VRAM
- [ ] Verificar si hay una instrucción DI que desactiva el IME sin reactivarlo
- [ ] Si se identifica el código culpable, implementar corrección o ajuste en el emulador