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.
Investigación de Escrituras Tempranas y Restricciones de Acceso a VRAM
Resumen
Se implementó monitoreo de VRAM desde el inicio de la ejecución para investigar por qué los juegos escriben solo ceros (0x00) a VRAM durante la ejecución. Se verificó el estado inicial de VRAM cuando se carga la ROM, se investigaron restricciones de acceso a VRAM cuando LCD=ON, y se monitorearon cambios de estado del LCD. Los resultados muestran que los juegos SÍ tienen datos iniciales en VRAM (92-98% de bytes no-cero), pero todas las escrituras durante la ejecución son ceros (0% de escrituras no-cero). El LCD se apaga y se enciende durante la ejecución, pero cuando se apaga, VRAM ya tiene solo 40 bytes no-cero (0.65%), sugiriendo que los datos iniciales se borran antes de que el juego pueda cargar tiles nuevos.
Concepto de Hardware
Restricciones de Acceso a VRAM: En el hardware real de Game Boy, el acceso a VRAM está restringido cuando el LCD está encendido. Solo se puede acceder a VRAM durante VBLANK (cuando el LCD está en modo VBLANK, LY >= 144) o cuando el LCD está completamente apagado (LCDC bit 7 = 0). Escribir a VRAM cuando el LCD está encendido (fuera de VBLANK) puede causar problemas o ser ignorado por el hardware. Esta restricción es crítica porque la PPU está leyendo VRAM constantemente durante el renderizado, y escribir mientras se lee puede causar corrupción de datos.
Timing de LCD: Los juegos suelen apagar el LCD para cargar tiles en VRAM de forma segura. Después de cargar los tiles, los juegos encienden el LCD. El LCD se puede apagar/encender mediante el registro LCDC (bit 7). Cuando el LCD está apagado, la PPU se detiene y LY se mantiene en 0, permitiendo que el juego modifique VRAM sin restricciones. Si un juego intenta escribir tiles cuando el LCD está encendido, las escrituras pueden ser ignoradas o causar corrupción.
Carga de Tiles: Los tiles se cargan en VRAM durante la inicialización del juego o durante la ejecución cuando el LCD está apagado. Los tiles se escriben en secuencias de 16 bytes consecutivos (cada tile es 8x8 píxeles, 2 bytes por línea, 8 líneas = 16 bytes). Si un juego escribe solo 0x00 a VRAM, no se están cargando tiles reales, solo se está limpiando VRAM. Si un juego tiene datos iniciales en VRAM (desde la ROM o desde la inicialización), pero luego escribe solo ceros, los datos iniciales se están borrando.
Estado Inicial de VRAM: Algunos juegos pueden tener datos iniciales en VRAM desde la ROM o desde la inicialización del juego. Estos datos pueden ser tiles de prueba, tiles del logo de Nintendo, o tiles básicos del juego. Si estos datos se borran (escribiendo 0x00) antes de que el juego cargue tiles nuevos, VRAM quedará vacía y no se mostrará nada en pantalla.
Implementación
Se implementaron 4 tareas principales de diagnóstico según el plan Step 0353:
1. Monitoreo de VRAM Desde el Inicio de la Ejecución (MMU.cpp)
Se agregó código en MMU::write() para monitorear VRAM desde el inicio de la ejecución, no solo después de activar logs. El código inicializa el monitoreo en la primera escritura a VRAM y loggea todas las escrituras no-cero (hasta 200) con información sobre el estado del LCD y el PC.
// --- Step 0353: Monitoreo Desde el Inicio ---
static int vram_write_from_start_count = 0;
static int vram_write_non_zero_from_start = 0;
static bool vram_monitoring_initialized = false;
if (!vram_monitoring_initialized) {
vram_monitoring_initialized = true;
printf("[MMU-VRAM-MONITOR-INIT] Monitoreo de VRAM inicializado desde el inicio\n");
}
if (addr >= 0x8000 && addr < 0x9800) {
vram_write_from_start_count++;
if (value != 0x00) {
vram_write_non_zero_from_start++;
// Loggear todas las escrituras no-cero (hasta 200)
if (vram_write_non_zero_from_start <= 200) {
uint16_t pc = debug_current_pc;
bool lcd_is_on = false;
if (ppu_ != nullptr) {
lcd_is_on = ppu_->is_lcd_on();
}
printf("[MMU-VRAM-WRITE-FROM-START] Non-zero write #%d | Addr=0x%04X | Value=0x%02X | "
"PC=0x%04X | LCD=%s | Total writes=%d\n",
vram_write_non_zero_from_start, addr, value, pc,
lcd_is_on ? "ON" : "OFF", vram_write_from_start_count);
}
}
// Loggear estadísticas cada 1000 escrituras
if (vram_write_from_start_count % 1000 == 0) {
printf("[MMU-VRAM-WRITE-FROM-START-STATS] Total writes=%d | Non-zero writes=%d | "
"Non-zero ratio=%.2f%%\n",
vram_write_from_start_count, vram_write_non_zero_from_start,
(vram_write_non_zero_from_start * 100.0) / vram_write_from_start_count);
}
}
// -------------------------------------------
2. Verificación del Estado Inicial de VRAM (MMU.cpp)
Se agregó la función MMU::check_initial_vram_state() que se llama desde MMU::load_rom() para verificar el estado inicial de VRAM cuando se carga la ROM. La función cuenta bytes no-cero y tiles completos en VRAM, y genera un log con estadísticas.
// --- Step 0353: Verificación del Estado Inicial de VRAM ---
void MMU::check_initial_vram_state() {
int non_zero_bytes = 0;
int complete_tiles = 0;
for (uint16_t addr = 0x8000; addr < 0x9800; addr += 16) {
int tile_non_zero = 0;
for (int i = 0; i < 16; i++) {
uint8_t byte = memory_[addr - 0x8000 + i];
if (byte != 0x00) {
non_zero_bytes++;
tile_non_zero++;
}
}
if (tile_non_zero >= 8) {
complete_tiles++;
}
}
printf("[MMU-VRAM-INITIAL-STATE] VRAM initial state | Non-zero bytes: %d/6144 (%.2f%%) | "
"Complete tiles: %d/384 (%.2f%%)\n",
non_zero_bytes, (non_zero_bytes * 100.0) / 6144,
complete_tiles, (complete_tiles * 100.0) / 384);
if (non_zero_bytes > 200) {
printf("[MMU-VRAM-INITIAL-STATE] ✅ VRAM tiene datos iniciales (posiblemente desde ROM)\n");
} else {
printf("[MMU-VRAM-INITIAL-STATE] ⚠️ VRAM está vacía al inicio\n");
}
}
// -------------------------------------------
3. Verificación de Restricciones de Acceso a VRAM Cuando LCD=ON (MMU.cpp)
Se agregó código en MMU::write() para verificar si hay restricciones de acceso a VRAM cuando LCD=ON. El código detecta escrituras no-cero a VRAM cuando el LCD está encendido y no estamos en VBLANK, y loggea estas escrituras (hasta 50) con información sobre el estado del LCD.
// --- Step 0353: Verificación de Restricciones de Acceso a VRAM ---
bool lcd_is_on = false;
bool in_vblank = false;
if (ppu_ != nullptr) {
lcd_is_on = ppu_->is_lcd_on();
uint8_t ly = ppu_->get_ly();
in_vblank = (ly >= 144);
}
bool access_restricted = lcd_is_on && !in_vblank;
if (access_restricted && value != 0x00) {
static int restricted_access_count = 0;
restricted_access_count++;
if (restricted_access_count <= 50) {
uint16_t pc = debug_current_pc;
printf("[MMU-VRAM-ACCESS-RESTRICTED] Write #%d | Addr=0x%04X | Value=0x%02X | "
"PC=0x%04X | LCD=ON, not in VBLANK | Access should be restricted\n",
restricted_access_count, addr, value, pc);
}
}
// TODO: Implementar restricciones de acceso a VRAM cuando LCD=ON (excepto VBLANK)
// -------------------------------------------
4. Verificación de Cambios de Estado del LCD (PPU.cpp)
Se agregó código en PPU::step() para verificar cambios de estado del LCD durante la ejecución. El código detecta cuando el LCD se apaga o se enciende, y cuando se apaga, verifica el estado de VRAM (cuántos bytes no-cero tiene).
// --- Step 0353: Verificación de Cambios de Estado del LCD ---
static bool last_lcd_state_step0353 = false;
static int lcd_state_change_count = 0;
bool current_lcd_state = lcd_enabled;
if (current_lcd_state != last_lcd_state_step0353) {
lcd_state_change_count++;
printf("[PPU-LCD-STATE-CHANGE] Change #%d | Frame %llu | LY: %d | "
"LCD changed from %s to %s\n",
lcd_state_change_count,
static_cast(frame_counter_),
ly_,
last_lcd_state_step0353 ? "ON" : "OFF",
current_lcd_state ? "ON" : "OFF");
// Si el LCD se apaga, verificar el estado de VRAM
if (!current_lcd_state) {
int non_zero_bytes = 0;
for (uint16_t addr = 0x8000; addr < 0x9800; addr++) {
uint8_t byte = mmu_->read(addr);
if (byte != 0x00) {
non_zero_bytes++;
}
}
printf("[PPU-LCD-STATE-CHANGE] LCD OFF | VRAM non-zero bytes: %d/6144 (%.2f%%)\n",
non_zero_bytes, (non_zero_bytes * 100.0) / 6144);
}
last_lcd_state_step0353 = current_lcd_state;
}
// -------------------------------------------
Archivos Afectados
src/core/cpp/MMU.cpp- Agregado monitoreo desde el inicio, verificación de estado inicial de VRAM, y verificación de restricciones de accesosrc/core/cpp/MMU.hpp- Agregada declaración decheck_initial_vram_state()src/core/cpp/PPU.cpp- Agregada verificación de cambios de estado del LCD
Tests y Verificación
Se ejecutaron pruebas con las 5 ROMs en paralelo (~2.5 minutos total) para analizar el comportamiento de VRAM:
- ROMs probadas: pkmn.gb, tetris.gb, mario.gbc, pkmn-amarillo.gb, Oro.gbc
- Comando ejecutado:
timeout 150 python3 main.py roms/<rom>.gb 2>&1 | tee logs/test_<rom>_step0353.log - Análisis de logs: Se usaron comandos
grepyheadpara extraer información específica sin saturar el contexto
Resultados Clave
- Monitoreo desde el inicio: ✅ Inicializado correctamente en todas las ROMs
- Escrituras no-cero: ❌ 0 escrituras no-cero en todas las ROMs (0% de escrituras no-cero)
- Estado inicial de VRAM: ✅ Los juegos SÍ tienen datos iniciales (92-98% de bytes no-cero)
- Restricciones de acceso: ⚠️ No se detectaron accesos restringidos (no hay escrituras no-cero cuando LCD=ON)
- Cambios de estado del LCD: ✅ El LCD se apaga y se enciende, pero cuando se apaga, VRAM tiene solo 40 bytes no-cero (0.65%)
Ejemplo de Logs
[MMU-VRAM-MONITOR-INIT] Monitoreo de VRAM inicializado desde el inicio
[MMU-VRAM-INITIAL-STATE] VRAM initial state | Non-zero bytes: 5867/6144 (95.49%) | Complete tiles: 374/384 (97.40%)
[MMU-VRAM-INITIAL-STATE] ✅ VRAM tiene datos iniciales (posiblemente desde ROM)
[MMU-VRAM-WRITE-FROM-START-STATS] Total writes=1000 | Non-zero writes=0 | Non-zero ratio=0.00%
[PPU-LCD-STATE-CHANGE] Change #1 | Frame 0 | LY: 0 | LCD changed from OFF to ON
[PPU-LCD-STATE-CHANGE] Change #2 | Frame 1 | LY: 148 | LCD changed from ON to OFF
[PPU-LCD-STATE-CHANGE] LCD OFF | VRAM non-zero bytes: 40/6144 (0.65%)
Fuentes Consultadas
- Pan Docs: LCD Timing, VRAM Access Restrictions
- Pan Docs: LCD Control Register (LCDC)
- Pan Docs: VRAM (Video RAM)
Integridad Educativa
Lo que Entiendo Ahora
- Restricciones de acceso a VRAM: En hardware real, el acceso a VRAM está restringido cuando el LCD está encendido (excepto durante VBLANK). Esto previene corrupción de datos cuando la PPU está leyendo VRAM durante el renderizado.
- Estado inicial de VRAM: Los juegos pueden tener datos iniciales en VRAM desde la ROM o desde la inicialización. Estos datos pueden ser tiles de prueba, tiles del logo, o tiles básicos del juego.
- Problema identificado: Los juegos tienen datos iniciales en VRAM (92-98% de bytes no-cero), pero todas las escrituras durante la ejecución son ceros (0% de escrituras no-cero). Esto sugiere que los datos iniciales se están borrando antes de que el juego pueda cargar tiles nuevos.
- Timing del LCD: El LCD se apaga y se enciende durante la ejecución, pero cuando se apaga, VRAM ya tiene solo 40 bytes no-cero (0.65%), sugiriendo que los datos iniciales se borraron antes de que el LCD se apagara.
Lo que Falta Confirmar
- ¿Por qué se borran los datos iniciales? Necesitamos investigar si hay escrituras de ceros que borran los datos iniciales antes de que el juego pueda cargar tiles nuevos.
- ¿Cuándo se borran los datos iniciales? Necesitamos verificar si las escrituras de ceros ocurren durante la inicialización o durante la ejecución.
- ¿Los juegos esperan que VRAM esté vacía? Algunos juegos pueden esperar que VRAM esté vacía al inicio y cargar tiles desde cero. Necesitamos verificar si los juegos intentan cargar tiles pero fallan por alguna razón.
- ¿Hay restricciones de acceso implementadas? Actualmente no implementamos restricciones de acceso a VRAM cuando LCD=ON. Necesitamos verificar si esto es necesario o si el problema es otro.
Hipótesis y Suposiciones
Hipótesis principal: Los datos iniciales en VRAM se están borrando (escribiendo 0x00) antes de que el juego pueda cargar tiles nuevos. Esto podría ocurrir durante la inicialización del juego o durante la ejecución cuando el LCD está encendido. Si los juegos intentan cargar tiles cuando el LCD está apagado, pero los datos iniciales ya se borraron, VRAM quedará vacía y no se mostrará nada en pantalla.
Suposición: Los juegos pueden estar escribiendo ceros a VRAM durante la inicialización para limpiar VRAM antes de cargar tiles nuevos. Si esto ocurre antes de que empecemos a monitorear, no lo detectaríamos. Sin embargo, el monitoreo desde el inicio debería capturar estas escrituras.
Próximos Pasos
- [ ] Step 0354: Investigar por qué se borran los datos iniciales de VRAM. Verificar si hay escrituras de ceros que ocurren antes de que el juego pueda cargar tiles nuevos.
- [ ] Step 0355: Implementar restricciones de acceso a VRAM cuando LCD=ON (excepto VBLANK) si es necesario.
- [ ] Step 0356: Verificar si los juegos intentan cargar tiles pero fallan por alguna razón. Investigar si hay problemas con el timing o con el acceso a VRAM.