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: Reimplementación del Trazado Disparado para Superar Bucles de Inicialización
Resumen
El análisis de la traza del Step 0165 confirmó que la CPU no está en un bucle infinito por un bug, sino que está ejecutando correctamente una rutina de inicialización de limpieza de memoria muy larga. Nuestro método de trazado de longitud fija (200 instrucciones desde PC=0x0100) es ineficiente para ver el código que se ejecuta después de esta rutina. Este Step reimplementa el sistema de trazado "disparado" (triggered) para que se active automáticamente solo cuando el Program Counter (PC) supere la dirección 0x0300, permitiéndonos capturar el código crítico de configuración de hardware que ocurre después de las rutinas de limpieza.
Concepto de Hardware: Rutinas de Inicialización (BIOS/Juego)
Antes de que cualquier juego pueda mostrar gráficos o aceptar input, debe preparar el "escenario". Esto implica una secuencia de inicialización que, típicamente, incluye:
- Desactivar interrupciones: Para evitar que eventos externos interrumpan la secuencia crítica de inicialización.
- Configurar el puntero de pila (Stack Pointer): Establecer un área de memoria válida para las operaciones de pila.
- Limpiar la RAM: Poner a cero grandes áreas como WRAM (Work RAM) y HRAM (High RAM) para asegurar un estado conocido y predecible. Esto se hace con bucles anidados muy rápidos que pueden consumir miles de ciclos de CPU.
- Configurar los registros de hardware: PPU (LCDC, BGP, SCY, SCX), APU, Timer, etc.
- Copiar los datos gráficos (tiles): Transferir los tiles desde la ROM a la VRAM.
- Activar la pantalla y las interrupciones: Finalmente, habilitar el renderizado y los eventos.
Nuestro emulador está ejecutando correctamente el paso 3 (limpieza de memoria). La estrategia de trazado anterior capturaba estas rutinas de limpieza, que son aburridas y repetitivas. Nuestra nueva estrategia es dejar que la CPU corra a toda velocidad a través de estas rutinas y empezar a grabar en el paso 4, donde ocurre la configuración de hardware que realmente nos interesa.
El problema del trazado fijo: Si comenzamos a trazar desde PC=0x0100 con un límite de 200 instrucciones, capturamos principalmente bucles de limpieza. Estos bucles pueden ejecutar miles de instrucciones antes de que se dibuje un solo píxel. Nuestro límite de 200 instrucciones apenas rasca la superficie de esta rutina.
La solución del trazado disparado: Configuramos un "breakpoint" en la dirección 0x0300, que es una apuesta segura para estar más allá de las principales rutinas de limpieza. La CPU ejecuta silenciosamente hasta alcanzar esta dirección, y entonces comenzamos a registrar las instrucciones. Esto nos permite ver exactamente qué registros de hardware intenta configurar el juego.
Implementación
Se modificaron las constantes de trazado en src/core/cpp/CPU.cpp para cambiar el punto de activación y el límite de instrucciones registradas.
Modificaciones en CPU.cpp
Se actualizaron las siguientes constantes estáticas (líneas 7-13):
- DEBUG_TRIGGER_PC: Cambiado de
0x0100a0x0300. Esta dirección está más allá de las rutinas de limpieza de memoria típicas. - DEBUG_INSTRUCTION_LIMIT: Reducido de
200a100. Como ahora estamos capturando código más relevante, no necesitamos tantas instrucciones para identificar el problema.
La lógica del trazado disparado ya estaba implementada correctamente en el método step() (líneas 471-484). Solo necesitábamos ajustar los parámetros:
// Variables estáticas para logging de diagnóstico con sistema "disparado" (triggered)
// El trigger se activa después de las rutinas de limpieza de memoria (0x0300)
// para capturar el código crítico de configuración de hardware
static const uint16_t DEBUG_TRIGGER_PC = 0x0300; // Dirección de activación del trazado (después de limpieza)
static bool debug_trace_activated = false; // Bandera de activación
static int debug_instruction_counter = 0; // Contador post-activación
static const int DEBUG_INSTRUCTION_LIMIT = 100; // Límite post-activación (dirigido, más corto)
La lógica de activación permanece igual:
- Cuando el PC alcanza o supera
DEBUG_TRIGGER_PC, se imprime un mensaje de activación y se activa la banderadebug_trace_activated. - Mientras la traza esté activa y no hayamos alcanzado el límite, se registra cada instrucción con su PC y opcode.
- El contador se incrementa hasta alcanzar
DEBUG_INSTRUCTION_LIMIT.
Decisiones de Diseño
¿Por qué 0x0300? Esta dirección es una apuesta segura basada en el conocimiento de que las rutinas de limpieza típicamente ocupan las primeras 512-768 bytes de memoria. 0x0300 (768 decimal) está lo suficientemente lejos para estar fuera de estas rutinas, pero lo suficientemente cerca para capturar la configuración inicial de hardware.
¿Por qué reducir el límite a 100? Ahora que estamos capturando código más relevante, 100 instrucciones deberían ser suficientes para identificar el siguiente opcode no implementado que está bloqueando el renderizado. Si necesitamos más, podemos aumentar el límite en el futuro.
Archivos Afectados
src/core/cpp/CPU.cpp- Modificación de constantes de trazado (líneas 7-13). Cambio deDEBUG_TRIGGER_PCde 0x0100 a 0x0300 y reducción deDEBUG_INSTRUCTION_LIMITde 200 a 100.
Tests y Verificación
Este cambio no afecta la funcionalidad de la CPU, solo modifica el comportamiento del sistema de depuración. Los tests existentes deben seguir pasando sin modificaciones.
Comando Ejecutado
pytest -v
Resultado Esperado
Todos los tests deben pasar, ya que solo modificamos constantes de depuración que no afectan la lógica de emulación.
Validación del Trazado
Para validar que el nuevo sistema de trazado funciona correctamente, se ejecutará el emulador con una ROM de prueba:
python main.py roms/tetris.gb
Comportamiento esperado:
- La consola permanece en silencio mientras la CPU ejecuta los bucles de limpieza a toda velocidad.
- Cuando el PC alcanza 0x0300, aparece el mensaje:
--- [CPU TRACE TRIGGERED at PC: 0x0300] --- - Se registran las siguientes 100 instrucciones con su PC y opcode.
- Esta nueva traza debería revelar los opcodes de configuración de hardware (probablemente relacionados con LCDC, BGP, SCY, SCX).
Fuentes Consultadas
- Pan Docs: Game Boy Pan Docs - Documentación general sobre el proceso de inicialización del Game Boy.
- GBEDG: Game Boy Opcode Table - Referencia para identificar opcodes en la traza.
Nota: El conocimiento sobre las rutinas de inicialización proviene de la observación empírica de trazas anteriores y del conocimiento general de arquitectura de sistemas embebidos.
Integridad Educativa
Lo que Entiendo Ahora
- Rutinas de inicialización: Los juegos de Game Boy ejecutan rutinas de limpieza de memoria muy largas antes de configurar el hardware. Estas rutinas pueden consumir miles de ciclos de CPU.
- Trazado dirigido: En lugar de capturar todas las instrucciones desde el inicio, es más eficiente usar un sistema de "breakpoint" que se active solo cuando el PC alcanza una dirección específica.
- Optimización de depuración: Reducir el ruido en las trazas nos permite identificar más rápidamente los problemas reales.
Lo que Falta Confirmar
- Dirección óptima del trigger: 0x0300 es una estimación. Podría necesitar ajustarse si las ROMs tienen rutinas de limpieza más largas o más cortas.
- Opcodes no implementados: La nueva traza revelará qué opcodes faltan por implementar que están bloqueando el renderizado de gráficos.
Hipótesis y Suposiciones
Hipótesis principal: La dirección 0x0300 está lo suficientemente lejos de las rutinas de limpieza para capturar el código de configuración de hardware, pero lo suficientemente cerca para no perder información relevante.
Suposición: Las ROMs de prueba (Tetris, Mario) siguen un patrón similar de inicialización, por lo que 0x0300 debería funcionar para todas ellas.
Próximos Pasos
- [ ] Recompilar el módulo C++ con
.\rebuild_cpp.ps1 - [ ] Ejecutar el emulador con
python main.py roms/tetris.gby analizar la nueva traza - [ ] Identificar los opcodes de configuración de hardware en la traza (LCDC, BGP, SCY, SCX, etc.)
- [ ] Implementar los opcodes faltantes que están bloqueando el renderizado
- [ ] Verificar que el emulador avance más allá de las rutinas de inicialización