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.
Debug Final: Reactivación de la Traza de CPU para Cazar el Bucle
Resumen
El sensor de VRAM del Step 0204 ha confirmado que la CPU nunca intenta escribir en la memoria de vídeo. Esto significa que el emulador está atrapado en un bucle lógico de software (un "wait loop") al inicio de la ejecución de la ROM, antes de cualquier rutina gráfica. Para identificar este bucle, reactivamos el sistema de trazado de la CPU para capturar las primeras 200 instrucciones ejecutadas desde el arranque, revelando el patrón del bucle infinito y permitiéndonos entender qué condición de hardware no estamos cumpliendo.
Concepto de Hardware: Análisis de Flujo de Control
Si la CPU no avanza, es porque está ejecutando un salto condicional (JR, JP, CALL, RET) que siempre la lleva de vuelta al mismo punto. Al ver la secuencia de instrucciones, identificaremos el bucle (ej: "Lee registro X, Compara con Y, Salta si no es igual").
Los bucles de espera comunes en el arranque de la Game Boy incluyen:
- Bucle de Joypad:
LD A, (FF00)→BIT ...→JR ...(Esperando que se suelte un botón). - Bucle de Timer:
LD A, (FF04)→CP ...→JR ...(Esperando a que el timer avance). - Bucle de V-Blank:
LDH A, (44)(Lee LY) →CP 90(Compara con 144) →JR NZ(Salta si no es VBlank). - Bucle de Checksum: Lectura de memoria y comparaciones matemáticas.
El último patrón que se repita en la traza será nuestro culpable. Al ver la secuencia exacta de instrucciones, podremos identificar qué registro o flag está comprobando el juego y por qué falla.
Implementación
Implementamos un sistema de trazado simple en CPU::step() que imprime las primeras 200 instrucciones ejecutadas. El trazado captura el estado de la CPU antes de ejecutar cada instrucción, incluyendo:
- Contador de instrucción (0-199)
- Program Counter (PC) actual
- Opcode que se va a ejecutar
- Estado de todos los registros principales (AF, BC, DE, HL, SP)
Componentes modificados
src/core/cpp/CPU.cpp: Agregado sistema de trazado con variables estáticas para controlar el límite de instrucciones.
Código implementado
El trazado se implementa justo antes del fetch del opcode, para capturar el PC antes de que se modifique:
// --- TRAZA DE CPU (Step 0205) ---
// Variables estáticas para el control de la traza
static int debug_trace_counter = 0;
static const int DEBUG_TRACE_LIMIT = 200;
// Imprimir las primeras N instrucciones para identificar el bucle de arranque
if (debug_trace_counter < DEBUG_TRACE_LIMIT) {
uint8_t opcode_preview = mmu_->read(regs_->pc);
printf("[CPU TRACE %03d] PC: 0x%04X | Opcode: 0x%02X | AF: 0x%04X | BC: 0x%04X | DE: 0x%04X | HL: 0x%04X | SP: 0x%04X\n",
debug_trace_counter, regs_->pc, opcode_preview, regs_->af, regs_->get_bc(), regs_->get_de(), regs_->get_hl(), regs_->sp);
debug_trace_counter++;
}
// --------------------------------
Decisiones de diseño
- Límite de 200 instrucciones: Suficiente para capturar varios ciclos de un bucle repetitivo sin inundar la consola.
- Variables estáticas: Permiten mantener el estado del contador entre llamadas a
step()sin necesidad de modificar la interfaz de la clase. - Lectura previa del opcode: Leemos el opcode directamente de memoria antes de llamar a
fetch_byte()para no modificar el PC antes de imprimir el estado. - Inclusión de todos los registros: El estado completo de los registros permite identificar qué valores está comparando el bucle.
Archivos Afectados
src/core/cpp/CPU.cpp- Agregado sistema de trazado con#include <cstdio>y variables estáticas de control.
Tests y Verificación
Para verificar el trazado:
- Recompilar el módulo C++: Ejecutar
.\rebuild_cpp.ps1para compilar los cambios. - Ejecutar el emulador: Ejecutar
python main.py roms/tetris.gb > cpu_trace.logpara redirigir la salida a un archivo. - Analizar la salida: Buscar patrones repetitivos en el log que indiquen el bucle infinito.
Validación de módulo compilado C++: El trazado se ejecuta dentro del código C++ compilado, garantizando que capturamos el flujo de ejecución real de la CPU emulada.
Fuentes Consultadas
- Pan Docs: CPU Instruction Set
- Implementación basada en conocimiento general de arquitectura LR35902 y técnicas de debugging de emuladores.
Integridad Educativa
Lo que Entiendo Ahora
- Análisis de flujo de control: Los bucles infinitos en emulación suelen ser causados por saltos condicionales que nunca se cumplen, esperando una condición de hardware que no se está emulando correctamente.
- Trazado de instrucciones: Capturar el estado de la CPU antes de cada instrucción permite identificar patrones repetitivos que revelan bucles de espera.
- Limitación de salida: Es crítico limitar la cantidad de trazas para evitar saturar la consola y colgar el terminal, especialmente en bucles infinitos.
Lo que Falta Confirmar
- Patrón del bucle: Una vez ejecutado el trazado, necesitamos analizar la salida para identificar qué instrucciones se repiten y qué condición están esperando.
- Causa raíz: Después de identificar el bucle, necesitamos determinar qué componente de hardware no está funcionando correctamente (Timer, PPU, Joypad, etc.).
Hipótesis y Suposiciones
Asumimos que el bucle es causado por una condición de hardware no cumplida, no por un error en la implementación de las instrucciones. El trazado nos permitirá confirmar o refutar esta hipótesis.
Próximos Pasos
- [ ] Ejecutar el emulador con el trazado activado y analizar la salida.
- [ ] Identificar el patrón repetitivo en las primeras 200 instrucciones.
- [ ] Determinar qué condición de hardware está esperando el bucle.
- [ ] Implementar la corrección del componente de hardware faltante o incorrecto.