⚠️ 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.

Memory Timeline & PC Tracker

Fecha: 2025-12-23 Step ID: 0247 Estado: Draft

Resumen

El Step 0246 confirmó que el juego sí está escribiendo en la WRAM, pero lo está haciendo de manera descendente (desde `DFFF` hacia abajo) y con valor `0x00` (ceros). Esto es una rutina de limpieza de memoria (Zero-Fill) que es normal y correcta durante la inicialización.

Sin embargo, aún falta la pieza clave: La Cronología. ¿En qué orden ocurren las operaciones y quién las ejecuta? Si el juego limpia toda la WRAM a ceros y luego busca `0xFD`... nunca lo va a encontrar. El `0xFD` debe escribirse DESPUÉS de la limpieza, o la limpieza no debería tocar esa zona.

Este Step implementa un Memory Timeline & PC Tracker que combina el tracking del Program Counter con las escrituras clave en memoria para reconstruir la secuencia temporal completa y determinar qué instrucción (PC) está provocando cada operación.

Concepto de Hardware

El Program Counter (PC) es un registro de 16 bits que contiene la dirección de memoria de la próxima instrucción a ejecutar. Cada vez que la CPU ejecuta una instrucción, el PC avanza al siguiente opcode (o salta a una dirección diferente en caso de saltos, llamadas, etc.).

Rastreo Temporal de Operaciones de Memoria: Para entender la secuencia de eventos en un programa, es crucial conocer no solo qué operaciones ocurren, sino también cuándo ocurren y desde dónde (qué instrucción las provocó). Esto permite reconstruir la "historia" o "timeline" de las operaciones de memoria.

El Problema de la Cronología: El Step 0246 confirmó que:

  • El juego escribe `0xFD` en HRAM (`FF8D`) - El Marcador
  • El juego limpia la WRAM a ceros (`0x00`) desde `DFFF` hacia abajo - La Limpieza
  • El juego busca `0xFD` en WRAM y se cuelga porque solo hay ceros - El Problema

Pero falta saber: ¿En qué orden ocurre esto? Si la limpieza ocurre después de escribir el marcador, entonces está borrando el marcador. Si la escritura del marcador ocurre después de la limpieza, entonces el problema está en otro lado (quizás la copia nunca ocurre).

Rastreo del PC en Operaciones de Memoria: Al registrar el PC actual en cada operación de memoria, podemos determinar qué instrucción (y por tanto, qué rutina del juego) está ejecutando cada operación. Esto nos permite correlacionar las operaciones con el flujo del programa y reconstruir la secuencia temporal.

Fuente: Pan Docs - "CPU Registers", "Memory Map"

Implementación

Se implementa un sistema de rastreo temporal que combina el Program Counter (PC) con las escrituras clave en memoria. Este sistema registra:

  • Escrituras en WRAM: Las primeras 200 escrituras en el rango `0xC000-0xDFFF` para ver la rutina de limpieza con su PC correspondiente.
  • Escrituras del Marcador (`0xFD`): Cualquier escritura del valor `0xFD` en cualquier dirección de memoria (el "sentinel" que el juego busca).
  • Escrituras en DMA (`0xFF46`): Actividad del registro DMA para detectar transferencias de memoria.

Componentes creados/modificados

  • src/core/cpp/MMU.hpp: Añadido miembro público debug_current_pc para rastrear el PC actual de la CPU.
  • src/core/cpp/MMU.cpp: Inicializado debug_current_pc en el constructor. Reemplazado el logging del Step 0246 con el nuevo Timeline Logger que incluye el PC en cada registro.
  • src/core/cpp/CPU.cpp: Añadida actualización de mmu_->debug_current_pc en el método step() antes de ejecutar cada instrucción.

Decisiones de diseño

Campo público debug_current_pc: Se añade como miembro público de la clase MMU para permitir que la CPU lo actualice fácilmente sin necesidad de métodos adicionales. Este campo es solo para diagnóstico y no afecta la lógica normal del emulador.

Actualización antes del fetch: El PC se actualiza en la MMU antes de ejecutar la instrucción (justo antes del fetch_byte()), para que el PC registrado corresponda a la dirección de la instrucción que está a punto de ejecutarse, no a la siguiente.

Formato de log unificado: Todos los logs usan el formato [TIME] PC:XXXX -> ... para facilitar el análisis temporal. El prefijo [TIME] permite filtrar fácilmente estos logs de otros mensajes del sistema.

Límite de 200 escrituras en WRAM: Se aumentó el límite de 100 a 200 escrituras para capturar más contexto de la rutina de limpieza, pero sigue siendo limitado para evitar saturar la salida.

Archivos Afectados

  • src/core/cpp/MMU.hpp - Añadido miembro público debug_current_pc (Step 0247)
  • src/core/cpp/MMU.cpp - Inicializado debug_current_pc en constructor. Reemplazado logging del Step 0246 con Timeline Logger (Step 0247)
  • src/core/cpp/CPU.cpp - Añadida actualización de PC en MMU antes de ejecutar instrucción (Step 0247)

Tests y Verificación

La verificación se realiza mediante ejecución del emulador con redirección de salida a un archivo para análisis:

  1. Recompilación: .\rebuild_cpp.ps1
  2. Ejecución con redirección: python main.py roms/tetris.gb > timeline.log 2>&1 (redirección necesaria porque el log será extenso)
  3. Análisis del log: Buscar líneas con [TIME] para reconstruir la secuencia temporal

Código clave modificado:

// En CPU::step(), antes de fetch_byte():
mmu_->debug_current_pc = regs_->pc;

// En MMU::write(), logging con PC:
if (addr >= 0xC000 && addr <= 0xDFFF && wram_log_count < 200) {
    printf("[TIME] PC:%04X -> Write WRAM [%04X] = %02X\n", debug_current_pc, addr, value);
    wram_log_count++;
}

if (value == 0xFD) {
    printf("[TIME] PC:%04X -> Write SENTINEL [%04X] = FD\n", debug_current_pc, addr);
}

if (addr == 0xFF46) {
    printf("[TIME] PC:%04X -> Write DMA [%04X] = %02X\n", debug_current_pc, addr, value);
}

Validación de módulo compilado C++: El Timeline Logger está implementado directamente en el código C++ de la MMU y CPU, por lo que requiere recompilación para activarse. La instrumentación se ejecuta en el bucle crítico de ejecución de instrucciones, pero el overhead es mínimo (solo una asignación y comparaciones condicionales).

Resultados esperados:

  • Escenario A (Limpieza antes del marcador): Se ven múltiples [TIME] PC:XXXX -> Write WRAM con valor 00, seguidos de [TIME] PC:YYYY -> Write SENTINEL con valor FD. Diagnóstico: La limpieza ocurre antes, lo cual es correcto. El problema está en que el marcador no se copia a WRAM después.
  • Escenario B (Marcador antes de la limpieza): Se ve [TIME] PC:XXXX -> Write SENTINEL, seguido de múltiples [TIME] PC:YYYY -> Write WRAM con valor 00. Diagnóstico: La limpieza está borrando el marcador después de escribirlo.
  • Escenario C (Nunca se escribe el marcador): No se ve ningún Write SENTINEL. Diagnóstico: El juego nunca escribe el marcador, o la rutina que lo escribe no se ejecuta.

Fuentes Consultadas

Integridad Educativa

Lo que Entiendo Ahora

  • Rastreo temporal de operaciones: Para diagnosticar problemas de sincronización o secuencia en un programa, es crucial reconstruir la "timeline" o cronología de las operaciones. Registrar el PC junto con las operaciones de memoria permite determinar qué instrucción (y por tanto, qué rutina) está ejecutando cada operación.
  • Instrumentación arquitectónica: Añadir campos de debug en las estructuras de datos principales (como la MMU) es una técnica común para permitir que componentes relacionados (como la CPU) actualicen información de contexto que luego puede usarse para logging o diagnóstico.
  • Análisis de secuencia: Al analizar los logs temporales, podemos determinar si las operaciones ocurren en el orden correcto y si hay operaciones que están interfiriendo entre sí (por ejemplo, una rutina de limpieza que borra datos antes de que otra rutina los use).

Lo que Falta Confirmar

  • Resultados del Timeline Logger: Necesitamos ejecutar el emulador y analizar los logs para determinar la secuencia temporal real de las operaciones. ¿Ocurre la limpieza antes o después de escribir el marcador?
  • Correlación con el código del juego: Una vez que conozcamos el PC de cada operación, podríamos potencialmente correlacionar estos PCs con el código del juego (ROM) para entender qué rutinas están ejecutando estas operaciones.
  • Efecto en el rendimiento: Aunque el overhead debería ser mínimo, necesitamos verificar que el emulador sigue ejecutándose a velocidad razonable con esta instrumentación activa.

Hipótesis y Suposiciones

Hipótesis principal: La secuencia temporal de las operaciones de memoria revelará si la limpieza de WRAM está ocurriendo antes o después de escribir el marcador `0xFD`. Si ocurre después, entonces la limpieza está borrando el marcador. Si ocurre antes, entonces el problema está en que el marcador nunca se copia a WRAM después de escribirlo en HRAM.

Suposición: Asumimos que el PC actualizado antes del fetch corresponde correctamente a la instrucción que está a punto de ejecutarse. En realidad, después del fetch el PC ya avanza, pero como actualizamos antes del fetch, el PC registrado debería corresponder a la instrucción que está ejecutando la operación de memoria.

Próximos Pasos

  • [ ] Ejecutar el emulador con redirección de salida: python main.py roms/tetris.gb > timeline.log 2>&1
  • [ ] Analizar el archivo timeline.log buscando líneas con [TIME]
  • [ ] Reconstruir la secuencia temporal: ¿En qué orden ocurren las escrituras en WRAM, el marcador y DMA?
  • [ ] Identificar el PC de cada operación para determinar qué rutinas están ejecutando estas operaciones
  • [ ] Determinar si la limpieza está borrando el marcador o si el marcador nunca se copia a WRAM
  • [ ] Si es necesario, correlacionar los PCs con el código del juego (ROM) para entender el flujo del programa