⚠️ Clean-Room / Educativo

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

Fecha: 2025-12-20 Step ID: 0166 Estado: 🔍 DRAFT

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:

  1. Desactivar interrupciones: Para evitar que eventos externos interrumpan la secuencia crítica de inicialización.
  2. Configurar el puntero de pila (Stack Pointer): Establecer un área de memoria válida para las operaciones de pila.
  3. 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.
  4. Configurar los registros de hardware: PPU (LCDC, BGP, SCY, SCX), APU, Timer, etc.
  5. Copiar los datos gráficos (tiles): Transferir los tiles desde la ROM a la VRAM.
  6. 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 0x0100 a 0x0300. Esta dirección está más allá de las rutinas de limpieza de memoria típicas.
  • DEBUG_INSTRUCTION_LIMIT: Reducido de 200 a 100. 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:

  1. Cuando el PC alcanza o supera DEBUG_TRIGGER_PC, se imprime un mensaje de activación y se activa la bandera debug_trace_activated.
  2. Mientras la traza esté activa y no hayamos alcanzado el límite, se registra cada instrucción con su PC y opcode.
  3. 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 de DEBUG_TRIGGER_PC de 0x0100 a 0x0300 y reducción de DEBUG_INSTRUCTION_LIMIT de 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

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.gb y 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