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
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úblicodebug_current_pcpara rastrear el PC actual de la CPU.src/core/cpp/MMU.cpp: Inicializadodebug_current_pcen 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 demmu_->debug_current_pcen el métodostep()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úblicodebug_current_pc(Step 0247)src/core/cpp/MMU.cpp- Inicializadodebug_current_pcen 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:
- Recompilación:
.\rebuild_cpp.ps1 - Ejecución con redirección:
python main.py roms/tetris.gb > timeline.log 2>&1(redirección necesaria porque el log será extenso) - 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 WRAMcon valor00, seguidos de[TIME] PC:YYYY -> Write SENTINELcon valorFD. 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 WRAMcon valor00. 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
- Pan Docs: CPU Registers and Flags - Sección "Program Counter (PC)"
- Pan Docs: Memory Map - Sección "Work RAM (WRAM)"
- Pan Docs: Memory Map - Sección "DMA"
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.logbuscando 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