⚠️ 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: Re-activación del Trazado para Analizar Bucle Lógico

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

Resumen

El diagnóstico del Step 0168 confirmó que la CPU no está encontrando opcodes desconocidos. El deadlock de LY=0 persiste porque la CPU está atrapada en un bucle infinito compuesto por instrucciones válidas. Se revirtió la estrategia "fail-fast" y se re-activó el sistema de trazado disparado (triggered) con un trigger en 0x02A0 y un límite de 200 instrucciones para capturar y analizar el bucle lógico en el que está atrapada la CPU.

Concepto de Hardware: Depuración de Lógica vs. Opcodes

Existen dos tipos principales de errores que causan deadlocks en un emulador en desarrollo:

  1. Error de Opcode Faltante: La CPU encuentra una instrucción que no conoce. Nuestra estrategia "fail-fast" del Step 0168 es perfecta para esto, ya que termina la ejecución inmediatamente y reporta el opcode desconocido.
  2. Error de Lógica de Bucle: La CPU ejecuta un bucle (ej: DEC B -> JR NZ) pero la condición de salida nunca se cumple (ej. el flag Z nunca se activa). Esto requiere observar el estado de los registros y flags dentro del bucle para entender por qué la condición nunca se cumple.

El diagnóstico del Step 0168 descartó el primer tipo de error: el hecho de que el bucle principal de Python siga ejecutándose (mostrando los mensajes 💓 Heartbeat) y que nunca veamos el mensaje fatal del default case confirma que todos los opcodes que la CPU está ejecutando ya están implementados.

Por lo tanto, el problema es del segundo tipo: un bucle lógico infinito. La ROM de Tetris ejecuta varias rutinas de limpieza de memoria en secuencia. Hemos arreglado el bucle DEC B del Step 0165, pero es casi seguro que la ROM ha entrado inmediatamente en otro bucle similar, por ejemplo, uno que usa DEC C o DEC DE.

Para analizar este tipo de error, necesitamos una herramienta de observación (una traza) en lugar de un detector de minas (fail-fast). El trazado disparado nos permite capturar el patrón de opcodes que se ejecutan repetidamente, permitiéndonos identificar el bucle y deducir qué condición de salida no se está cumpliendo.

Implementación

Se modificó src/core/cpp/CPU.cpp para revertir el comportamiento "fail-fast" del Step 0168 y re-activar el sistema de trazado disparado con una configuración optimizada para capturar bucles lógicos.

Componentes modificados

  • src/core/cpp/CPU.cpp: Revertido el caso default del switch para devolver 0 ciclos silenciosamente, y ajustado el sistema de trazado disparado.

Cambios realizados

1. Ajuste del Trigger y Límite de Trazado:

// 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 (0x02A0)
// para capturar el código crítico de configuración de hardware y bucles lógicos
static const uint16_t DEBUG_TRIGGER_PC = 0x02A0; // 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 = 200;  // Límite post-activación (Step 0169: aumentado para capturar bucles)

El trigger se cambió de 0x0300 a 0x02A0 para capturar el código que se ejecuta justo después del primer bucle de limpieza conocido. El límite se aumentó de 100 a 200 instrucciones para capturar bucles completos.

2. Reversión del Default Case:

default:
    // --- Step 0169: Revertido a comportamiento silencioso ---
    // El diagnóstico del Step 0168 confirmó que no hay opcodes desconocidos.
    // El deadlock es causado por un bucle lógico con instrucciones válidas.
    // Volvemos a devolver 0 ciclos para permitir que el trazado capture el bucle.
    cycles_ += 0;
    return 0;

Se eliminó el exit(1) y se volvió a devolver 0 ciclos silenciosamente. Esto permite que el emulador continúe ejecutándose y que el trazado capture el bucle lógico.

3. Eliminación de Include Innecesario:

Se eliminó el #include <cstdlib> ya que ya no se usa exit().

Archivos Afectados

  • src/core/cpp/CPU.cpp - Modificado el sistema de trazado disparado (trigger ajustado a 0x02A0, límite aumentado a 200) y revertido el default case a comportamiento silencioso.

Tests y Verificación

La verificación se realizará ejecutando el emulador y observando la salida del trazado:

  • Comando ejecutado: python main.py roms/tetris.gb
  • Resultado esperado: El emulador debería ejecutarse en silencio hasta que el PC alcance 0x02A0, momento en el que debería aparecer el mensaje --- [CPU TRACE TRIGGERED at PC: 0x02A0] --- seguido de 200 líneas de traza mostrando el patrón de opcodes del bucle lógico.
  • Validación Nativa: Validación de módulo compilado C++ con trazado activado.

Nota: Este Step no incluye tests unitarios porque es una modificación de depuración temporal. La validación se realiza mediante observación directa de la traza de ejecución.

Fuentes Consultadas

  • Implementación basada en conocimiento general de técnicas de depuración de emuladores y análisis de bucles infinitos.
  • Pan Docs: CPU Instruction Set - Para referencia de instrucciones válidas.

Integridad Educativa

Lo que Entiendo Ahora

  • Diferencia entre Error de Opcode y Error de Lógica: Un error de opcode se detecta inmediatamente cuando la CPU intenta ejecutar una instrucción desconocida. Un error de lógica requiere observar el comportamiento repetitivo de instrucciones válidas para identificar por qué una condición nunca se cumple.
  • Estrategias de Depuración Complementarias: "Fail-fast" es excelente para detectar opcodes faltantes, pero inútil para analizar bucles lógicos. El trazado disparado es la herramienta correcta para observar patrones repetitivos de ejecución.
  • El Diagnóstico del Step 0168 fue Exitoso: Aunque no resolvió el deadlock, confirmó que no hay opcodes desconocidos, lo cual es información valiosa que nos permite enfocar la depuración en la lógica de bucles.

Lo que Falta Confirmar

  • Patrón del Bucle: Necesitamos ejecutar el emulador y analizar la traza para identificar el patrón exacto de opcodes que se repite, lo cual nos permitirá deducir qué instrucción o condición está causando el bucle infinito.
  • Condición de Salida: Una vez identificado el bucle, necesitamos analizar por qué la condición de salida (ej. flag Z en JR NZ) nunca se cumple.

Hipótesis y Suposiciones

Hipótesis Principal: El bucle lógico es similar al del Step 0165 (un bucle de decremento que nunca termina), pero probablemente usa un registro diferente (ej. DEC C en lugar de DEC B) o una instrucción de 16 bits (ej. DEC DE). La traza nos confirmará o refutará esta hipótesis.

Próximos Pasos

  • [ ] Ejecutar el emulador y capturar la traza del bucle lógico
  • [ ] Analizar el patrón de opcodes repetitivos en la traza
  • [ ] Identificar la instrucción o condición que causa el bucle infinito
  • [ ] Implementar la corrección necesaria (probablemente similar a la del Step 0165)