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 Por Qué VRAM No Se Llena con Tiles
Resumen
Se implementó código de diagnóstico detallado para investigar por qué VRAM no se llena con tiles durante la ejecución de los juegos. Se agregaron logs para verificar todas las escrituras a VRAM, el estado periódico de VRAM, el timing de escrituras (LCD apagado vs encendido), y la detección de borrado de tiles. Los resultados muestran que los juegos están escribiendo SOLO CEROS (0x00) a VRAM durante la ejecución, lo que explica por qué VRAM nunca se llena con tiles. Todas las escrituras ocurren cuando el LCD está encendido, lo cual es inusual ya que normalmente los juegos escriben tiles cuando el LCD está apagado.
Concepto de Hardware
Acceso a VRAM: VRAM (0x8000-0x97FF) contiene datos de tiles y tilemaps. Los juegos escriben tiles en VRAM durante la inicialización o cuando el LCD está apagado. El acceso a VRAM está restringido cuando el LCD está encendido (solo durante VBLANK o cuando el LCD está apagado). En hardware real, escribir a VRAM cuando el LCD está encendido puede causar corrupción de datos o ser ignorado.
Timing de LCD: Los juegos suelen escribir tiles cuando el LCD está apagado para evitar conflictos con el renderizado. Escribir a VRAM cuando el LCD está encendido puede causar problemas o ser ignorado. 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 de forma segura.
Escritura de Tiles: Cada tile es 16 bytes (8x8 píxeles, 2 bytes por línea). Los tiles se escriben en secuencias de 16 bytes consecutivos. Los juegos pueden borrar tiles escribiendo 0x00 en VRAM. Si un juego escribe solo 0x00 a VRAM, no se están cargando tiles reales, solo se está limpiando VRAM.
Detección de Tiles Completos: Un tile completo tiene 16 bytes consecutivos con datos no-cero. Si un juego escribe tiles, deberíamos ver secuencias de 16 bytes con datos válidos. Si solo vemos escrituras de 0x00, el juego está limpiando VRAM, no cargando tiles.
Implementación
Se implementaron 4 tareas principales de diagnóstico según el plan Step 0352:
1. Verificación Detallada de Escrituras a VRAM (MMU.cpp)
Se agregó código en MMU::write() para loggear todas las escrituras a VRAM (0x8000-0x97FF). El código cuenta el total de escrituras, escrituras no-cero, y detecta secuencias de 16 bytes consecutivos (tiles completos). Se loggean las primeras 100 escrituras detalladamente con dirección, valor, PC, y estadísticas.
// --- Step 0352: Verificación Detallada de Escrituras a VRAM ---
static int vram_write_detailed_count = 0;
static int vram_write_total_count = 0;
static int vram_write_non_zero_count = 0;
static int vram_write_tile_sequences = 0;
if (addr >= 0x8000 && addr < 0x9800) {
vram_write_total_count++;
if (value != 0x00) {
vram_write_non_zero_count++;
}
// Loggear primeras 100 escrituras detalladamente
// Detectar secuencias de 16 bytes (tiles completos)
// Loggear estadísticas cada 1000 escrituras
}
// -------------------------------------------
2. Verificación Periódica del Estado de VRAM (PPU.cpp)
Se agregó código en PPU::step() para verificar el estado de VRAM cada 100 frames. El código cuenta bytes no-cero en VRAM, verifica si hay tiles completos (16 bytes consecutivos con datos no-cero), y genera advertencias si VRAM está vacía después de muchos frames.
// --- Step 0352: Verificación Periódica del Estado de VRAM ---
if (frame_counter_ % 100 == 0 && vram_state_check_count < 50) {
// Contar bytes no-cero en VRAM
int non_zero_bytes = 0;
int complete_tiles = 0;
for (uint16_t addr = 0x8000; addr < 0x9800; addr += 16) {
// Verificar si el tile tiene datos
// Si tiene al menos 8 bytes no-cero, considerarlo completo
}
printf("[PPU-VRAM-STATE-PERIODIC] Frame %llu | Non-zero bytes: %d/6144 ...\n", ...);
}
// -------------------------------------------
3. Verificación de Timing de Escrituras (LCD Apagado vs Encendido)
Se agregó método PPU::is_lcd_on() para verificar el estado del LCD, y se agregó código en MMU::write() para loggear el estado del LCD cuando se escribe a VRAM. Esto permite identificar si las escrituras ocurren cuando el LCD está encendido o apagado.
// --- Step 0352: Verificación de Timing de Escrituras a VRAM ---
bool lcd_is_on = false;
if (ppu_ != nullptr) {
lcd_is_on = ppu_->is_lcd_on();
}
printf("[MMU-VRAM-WRITE-LCD-TIMING] Write #%d | Addr=0x%04X | Value=0x%02X | "
"LCD=%s | PC=0x%04X\n", ...);
if (lcd_is_on && value != 0x00) {
printf("[MMU-VRAM-WRITE-LCD-TIMING] ⚠️ ADVERTENCIA: Escritura a VRAM cuando LCD está encendido!\n");
}
// -------------------------------------------
4. Detección de Borrado de Tiles
Se agregó código en MMU::write() para detectar cuando se borran tiles (escritura de 0x00 después de que se escribieron datos no-cero). El código mantiene un mapa de los últimos valores escritos en cada dirección y detecta cuando se escribe 0x00 después de un valor no-cero.
// --- Step 0352: Detección de Borrado de Tiles ---
static std::map<uint16_t, uint8_t> vram_last_value;
static int vram_erase_count = 0;
if (vram_last_value.find(addr) != vram_last_value.end()) {
uint8_t last_value = vram_last_value[addr];
if (last_value != 0x00 && value == 0x00) {
// Detectar borrado
vram_erase_count++;
printf("[MMU-VRAM-ERASE] Erase #%d | Addr=0x%04X | Last value=0x%02X ...\n", ...);
}
}
vram_last_value[addr] = value;
// -------------------------------------------
Componentes Creados/Modificados
src/core/cpp/MMU.cpp: Agregado código de diagnóstico de escrituras a VRAM, timing de LCD, y detección de borradosrc/core/cpp/PPU.cpp: Agregado código de verificación periódica del estado de VRAMsrc/core/cpp/PPU.hpp: Agregado métodois_lcd_on()para verificar el estado del LCD
Archivos Afectados
src/core/cpp/MMU.cpp- Agregado código de diagnóstico de escrituras a VRAM, timing de LCD, y detección de borradosrc/core/cpp/PPU.cpp- Agregado código de verificación periódica del estado de VRAM y métodois_lcd_on()src/core/cpp/PPU.hpp- Agregado método públicois_lcd_on()
Tests y Verificación
Se ejecutaron pruebas con las 5 ROMs en paralelo durante ~2.5 minutos cada una:
- ROMs probadas: pkmn.gb, tetris.gb, mario.gbc, pkmn-amarillo.gb, Oro.gbc
- Comando:
timeout 150 python3 main.py roms/[ROM].gb 2>&1 | tee logs/test_[ROM]_step0352.log - Análisis de logs: Se usaron comandos
greppara extraer información específica sin saturar el contexto
Hallazgos Principales
- Escrituras a VRAM: Miles de escrituras detectadas, pero TODAS son 0x00 (Non-zero writes=0, Non-zero ratio=0.00%)
- Estado de VRAM: Siempre tiene solo 40 bytes no-cero (0.65%), por debajo del umbral de 200
- Timing de escrituras: Todas las escrituras ocurren cuando LCD=ON y todas son 0x00
- Borrado de tiles: No se detectan borrados (Total erases=0), lo que significa que no se están escribiendo tiles primero para luego borrarlos
Ejemplo de Logs
[MMU-VRAM-WRITE-STATS] Total writes=1000 | Non-zero writes=0 | Tile sequences=3 | Non-zero ratio=0.00%
[MMU-VRAM-WRITE-STATS] Total writes=2000 | Non-zero writes=0 | Tile sequences=3 | Non-zero ratio=0.00%
[PPU-VRAM-STATE-PERIODIC] Frame 0 | Non-zero bytes: 40/6144 (0.65%) | Complete tiles: 3/384 (0.78%) | Empty: YES
[MMU-VRAM-WRITE-LCD-TIMING] Write #1 | Addr=0x8000 | Value=0x00 | LCD=ON | PC=0x1679
[MMU-VRAM-ERASE-STATS] Total writes=1000 | Total erases=0 | Erase ratio=0.00%
Conclusión
Los juegos están escribiendo SOLO CEROS (0x00) a VRAM durante la ejecución. No se están escribiendo tiles reales. Esto explica por qué VRAM nunca se llena con tiles. El problema no está en la detección o generación del framebuffer, sino en que los juegos no están cargando tiles en VRAM durante la ejecución.
Fuentes Consultadas
- Pan Docs: Memory Map, VRAM, LCD Timing
- Pan Docs: LCD Control Register (LCDC)
- Pan Docs: Tile Data, Tile Map
Integridad Educativa
Lo que Entiendo Ahora
- Escrituras a VRAM: Los juegos escriben miles de veces a VRAM durante la ejecución, pero todas las escrituras son 0x00 (ceros). Esto significa que los juegos están limpiando VRAM, no cargando tiles.
- Timing de LCD: Todas las escrituras ocurren cuando el LCD está encendido, lo cual es inusual. Normalmente los juegos escriben tiles cuando el LCD está apagado para evitar conflictos.
- Estado de VRAM: VRAM siempre tiene solo 40 bytes no-cero (0.65%), que es muy poco. Esto sugiere que los juegos no están cargando tiles durante la ejecución, o que los tiles se cargan muy temprano y luego se borran.
Lo que Falta Confirmar
- ¿Por qué los juegos escriben solo ceros? Necesitamos investigar si los juegos esperan que VRAM esté vacía al inicio, o si hay algún problema con el timing de inicialización.
- ¿Cuándo se cargan los tiles? Los tiles podrían cargarse muy temprano (durante la inicialización) y luego borrarse, o podrían no cargarse en absoluto si hay algún problema con la inicialización del juego.
- ¿Hay restricciones de acceso a VRAM? En hardware real, escribir a VRAM cuando el LCD está encendido puede causar problemas. Necesitamos verificar si nuestro emulador está respetando estas restricciones correctamente.
Hipótesis y Suposiciones
Hipótesis principal: Los juegos están limpiando VRAM al inicio de la ejecución, pero no están cargando tiles después. Esto podría deberse a que:
- Los juegos esperan que VRAM esté vacía al inicio y cargan tiles más tarde (después de los 2.5 minutos de prueba)
- Hay algún problema con la inicialización del juego que impide que se carguen tiles
- Los tiles se cargan desde otro lugar (no desde VRAM directamente) o se cargan de forma diferente
Próximos Pasos
- [ ] Investigar por qué los juegos escriben solo ceros a VRAM
- [ ] Verificar si los tiles se cargan muy temprano (durante la inicialización) y luego se borran
- [ ] Investigar si hay restricciones de acceso a VRAM que no estamos respetando
- [ ] Verificar si los juegos cargan tiles desde otro lugar o de forma diferente
- [ ] Implementar corrección basada en los hallazgos (Step 0353)