⚠️ 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 Final: Reactivación de la Traza de CPU para Cazar el Bucle Lógico

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

Resumen

El "Sensor de VRAM" del Step 0194 ha confirmado con certeza que la CPU nunca intenta escribir en la VRAM. A pesar de que el emulador corrió durante varios segundos y cientos de fotogramas, el mensaje [VRAM WRITE DETECTED!] nunca apareció.

Dado que todos los deadlocks de hardware han sido resueltos (LY cicla correctamente), la única explicación posible es que la CPU está atrapada en un bucle lógico infinito en el propio código de la ROM, antes de llegar a la rutina que copia los gráficos a la VRAM.

Este Step reactiva el sistema de trazado de la CPU en C++ para capturar la secuencia de instrucciones que componen el bucle infinito, identificar el patrón y deducir la condición de salida que no se está cumpliendo.

Concepto de Ingeniería: Aislamiento del Bucle de Software

Hemos pasado de depurar nuestro emulador a depurar la propia ROM que se ejecuta en él. Necesitamos ver el código ensamblador que está corriendo para entender su lógica. Una traza de las últimas instrucciones ejecutadas nos mostrará un patrón repetitivo de direcciones de PC.

Al analizar los opcodes en esas direcciones, podremos deducir qué está comprobando el juego. ¿Está esperando un valor específico en un registro de I/O que no hemos inicializado correctamente? ¿Está comprobando un flag que nuestra ALU calcula de forma sutilmente incorrecta en un caso límite? La traza nos lo dirá.

Principio del Trazado Disparado: En lugar de trazar desde el inicio (lo cual generaría demasiado ruido), activamos el trazado cuando el PC alcanza 0x0100 (inicio del código del cartucho). Esto nos da una ventana clara de la ejecución del código del juego, sin el ruido del código de inicialización de la BIOS.

Límite de Instrucciones: Configuramos el trazado para capturar las primeras 200 instrucciones después de la activación. Esto es suficiente para ver un patrón de bucle claro. Si el bucle es más largo, podemos aumentar el límite, pero 200 suele ser suficiente para identificar el patrón.

Implementación

Hemos reintroducido la lógica de trazado que usamos en los Steps 0169-0170, pero esta vez configurada para activarse al inicio del código del cartucho y darnos una ventana clara de la ejecución.

Componentes modificados

  • src/core/cpp/CPU.cpp: Añadido include <cstdio> y sistema de trazado en el método step()

Código del Trazado

El trazado utiliza variables estáticas para mantener el estado entre llamadas:

// --- Variables para el Trazado de CPU (Step 0195) ---
static bool debug_trace_activated = false;
static int debug_instruction_counter = 0;
// Un límite de 200 es suficiente para ver un patrón de bucle.
static const int DEBUG_INSTRUCTION_LIMIT = 200;

// En el constructor de la CPU, resetea el estado del trazado
CPU::CPU(MMU* mmu, CoreRegisters* registers) : /*...*/ {
    debug_trace_activated = false;
    debug_instruction_counter = 0;
}

// En el método step(), antes de fetch_byte():
uint16_t current_pc = regs_->pc;

// --- Lógica del Trazado (Step 0195) ---
// Empezamos a trazar desde el principio para cazar el bucle.
if (!debug_trace_activated && current_pc >= 0x0100) {
    debug_trace_activated = true;
    printf("--- [CPU TRACE ACTIVATED at PC: 0x%04X] ---\n", current_pc);
}

if (debug_trace_activated && debug_instruction_counter < DEBUG_INSTRUCTION_LIMIT) {
    uint8_t opcode_for_trace = mmu_->read(current_pc);
    printf("[CPU TRACE %d] PC: 0x%04X | Opcode: 0x%02X\n", debug_instruction_counter, current_pc, opcode_for_trace);
    debug_instruction_counter++;
}
// --- Fin del Trazado ---

Decisiones de diseño

  • Activación en 0x0100: El código del cartucho comienza en 0x0100. Activamos el trazado aquí para evitar el ruido del código de inicialización de la BIOS.
  • Límite de 200 instrucciones: Es suficiente para ver un patrón de bucle. Si el bucle es más largo, podemos aumentar el límite fácilmente.
  • Lectura del opcode antes de fetch: Leemos el opcode directamente de la memoria usando mmu_->read(current_pc) antes de que fetch_byte() lo consuma. Esto nos da el opcode exacto que se va a ejecutar.

Archivos Afectados

  • src/core/cpp/CPU.cpp - Añadido include <cstdio> y sistema de trazado en el método step()
  • docs/bitacora/entries/2025-12-20__0195__debug-final-reactivacion-traza-cpu-cazar-bucle-logico.html - Nueva entrada de bitácora
  • docs/bitacora/index.html - Actualizado con la nueva entrada
  • INFORME_FASE_2.md - Actualizado con el Step 0195

Tests y Verificación

La verificación de este Step es principalmente de compilación y ejecución del emulador. El resultado esperado es que la traza de la CPU muestre un patrón repetitivo de direcciones de PC que forman el bucle infinito.

Proceso de Verificación

  1. Recompilar el módulo C++: .\rebuild_cpp.ps1
    • Resultado: ✅ Compilación exitosa (con warnings menores esperados)
  2. Ejecutar el emulador: python main.py roms/tetris.gb
    • El emulador debe ejecutarse normalmente. El usuario debe presionar una tecla para pasar el bucle del Joypad.
  3. Observar la consola: La traza buscará el mensaje [CPU TRACE ACTIVATED at PC: 0xXXXX] seguido de las primeras 200 instrucciones ejecutadas.

Validación de módulo compilado C++

El emulador utiliza el módulo C++ compilado (viboy_core), que contiene el sistema de trazado implementado en CPU::step(). Cada instrucción ejecutada pasará a través de este método y será trazada si corresponde.

Resultado Esperado

La traza de la CPU nos mostrará el bucle. Por ejemplo, podríamos ver algo como:

[CPU TRACE 195] PC: 0x00A5 | Opcode: 0xE0
[CPU TRACE 196] PC: 0x00A7 | Opcode: 0xE6
[CPU TRACE 197] PC: 0x00A8 | Opcode: 0x20
[CPU TRACE 198] PC: 0x00A5 | Opcode: 0xE0
[CPU TRACE 199] PC: 0x00A7 | Opcode: 0xE6

Este patrón nos dirá que las instrucciones en 0x00A5, 0x00A7 y 0x00A8 forman el bucle. Al mirar qué hacen esos opcodes (por ejemplo, LDH, AND, JR NZ), podremos deducir la condición exacta que está fallando y aplicar la corrección final.

Fuentes Consultadas

  • Pan Docs: CPU Instruction Set
  • Implementación basada en conocimiento general de arquitectura LR35902 y técnicas de depuración de emuladores.

Integridad Educativa

Lo que Entiendo Ahora

  • Trazado de CPU: El trazado de instrucciones es una herramienta fundamental para depurar emuladores. Nos permite ver exactamente qué instrucciones está ejecutando la CPU y en qué orden.
  • Patrones de Bucle: Los bucles infinitos en código de bajo nivel se manifiestan como patrones repetitivos de direcciones de PC. Identificar estos patrones es el primer paso para entender qué condición está fallando.
  • Aislamiento del Problema: Hemos eliminado todas las causas de hardware (deadlocks, sincronización, etc.). El problema restante es un bucle de software puro, que requiere análisis del código ensamblador.

Lo que Falta Confirmar

  • Patrón del Bucle: Necesitamos ejecutar el emulador y analizar la traza para identificar el patrón exacto del bucle.
  • Condición de Salida: Una vez identificado el bucle, necesitamos deducir qué condición está esperando el juego y por qué no se está cumpliendo.
  • Corrección Final: Una vez identificada la condición, necesitaremos aplicar la corrección correspondiente (inicialización de registro, corrección de flag, etc.).

Hipótesis y Suposiciones

Hipótesis Principal: El juego está esperando un valor específico en un registro de I/O que no hemos inicializado correctamente, o está comprobando un flag que nuestra ALU calcula de forma sutilmente incorrecta en un caso límite.

Suposición: El bucle es lo suficientemente corto (menos de 200 instrucciones) para que podamos capturarlo completamente con nuestro límite actual. Si el bucle es más largo, podemos aumentar el límite fácilmente.

Próximos Pasos

  • [ ] Ejecutar el emulador y analizar la traza de la CPU
  • [ ] Identificar el patrón repetitivo de direcciones de PC que forman el bucle
  • [ ] Analizar los opcodes en esas direcciones para deducir qué condición está esperando el juego
  • [ ] Aplicar la corrección correspondiente (inicialización de registro, corrección de flag, etc.)
  • [ ] Verificar que el juego sale del bucle y comienza a escribir en la VRAM