This project is educational and Open Source. No code is copied from other emulators. Implementation based solely on technical documentation and permitted tests.
Investigation of VRAM Initialization and Initial Data Discrepancy
Summary
Implemented VRAM initialization and initial data discrepancy investigation to understand why the initial state of VRAM has so little data (40 non-zero bytes, 0.65%) when Step 0353 reported 92-98% non-zero bytes. We checked how VRAM is initialized in the constructor, if initial data is loaded from ROM, when the initial state is measured in different steps, and if there are non-zero writes that load initial data at the start of CPU execution.
Hardware Concept
VRAM initialization:VRAM is initialized when MMU is built. On real hardware, VRAM can contain random data at startup. Some emulators initialize VRAM with zeros, others with random values. In our emulator, VRAM is initialized with zeros in the MMU constructor (memory_(MEMORY_SIZE, 0)), which means that VRAM is also initialized with zeros.
Loading Data from ROM:Game Boy ROMs generally have no initial data in VRAM. Initial data is generated during game initialization. Some games load initial tiles from ROM during initialization. However, in our emulator, the ROM is loaded after the MMU is built, so VRAM is already initialized with zeros when the ROM is loaded.
Measurement Timing:The state of VRAM can change rapidly during initialization. Measuring at different times can give different results. It is important to measure at the correct time to obtain accurate results. Step 0353 measured the initial state when the ROM is loaded, while Step 0354 measured the initial state when the first erase is detected. This difference in timing may explain the discrepancy between the two steps.
Initial Data Generation:Initial data can be generated during game initialization. The game can write initial tiles to VRAM during initialization, before we start monitoring. If these writes happen before we start monitoring, we won't detect them, but VRAM will have data when we start monitoring.
Implementation
5 main diagnostic tasks were implemented according to the Step 0355 plan:
1. Verification of VRAM Initialization in the Builder (MMU.cpp)
Added code in the MMU constructor to check VRAM status immediately after initialization. The code counts non-zero bytes in VRAM and generates a log with statistics.
// --- Step 0355: VRAM Initialization Check ---
// Check VRAM status immediately after initialization
int non_zero_bytes = 0;
for (uint16_t addr = 0x8000; addr< 0x9800; addr++) {
uint8_t byte = memory_[addr];
if (byte != 0x00) {
non_zero_bytes++;
}
}
printf("[MMU-VRAM-INIT] VRAM initialized | Non-zero bytes: %d/6144 (%.2f%%)\n",
non_zero_bytes, (non_zero_bytes * 100.0) / 6144);
if (non_zero_bytes < 200) {
printf("[MMU-VRAM-INIT] ⚠️ ADVERTENCIA: VRAM está vacía después de la inicialización!\n");
}
// -----------------------------------------
2. Verification of Loading Initial Data from ROM (MMU.cpp)
Added code inMMU::load_rom()to check VRAM status after loading ROM. The code counts non-zero bytes in VRAM and generates a log with statistics.
// --- Step 0355: Initial Data Load Verification from ROM ---
// Check VRAM status after loading ROM
int non_zero_bytes = 0;
for (uint16_t addr = 0x8000; addr< 0x9800; addr++) {
uint8_t byte = memory_[addr];
if (byte != 0x00) {
non_zero_bytes++;
}
}
printf("[MMU-VRAM-AFTER-ROM-LOAD] VRAM after ROM load | Non-zero bytes: %d/6144 (%.2f%%)\n",
non_zero_bytes, (non_zero_bytes * 100.0) / 6144);
if (non_zero_bytes < 200) {
printf("[MMU-VRAM-AFTER-ROM-LOAD] ⚠️ ADVERTENCIA: VRAM está vacía después de cargar la ROM!\n");
}
// -----------------------------------------
3. Verification of Discrepancy in the Measurement of the Initial State (MMU.cpp)
Function addedMMU::check_vram_state_at_point()to check VRAM status at different times. This function is called at multiple points (builder, after loading ROM, when CPU starts) to identify the discrepancy.
// --- Step 0355: Multipoint VRAM Status Check ---
void MMU::check_vram_state_at_point(const char* point_name) {
static std::map<std::string, bool> checked_points;
if (checked_points.find(point_name) == checked_points.end()) {
checked_points[point_name] = true;
int non_zero_bytes = 0;
int complete_tiles = 0;
for (uint16_t addr = 0x8000; addr< 0x9800; addr += 16) {
bool tile_has_data = false;
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++;
tile_has_data = true;
}
}
if (tile_non_zero >= 8) {
complete_tiles++;
}
}
printf("[MMU-VRAM-STATE-POINT] Point: %s | Non-zero bytes: %d/6144 (%.2f%%) | "
"Complete tiles: %d/384 (%.2f%%)\n",
point_name,
non_zero_bytes, (non_zero_bytes * 100.0) / 6144,
complete_tiles, (complete_tiles * 100.0) / 384);
}
}
// -----------------------------------------
4. Verification of Writes from the Start of CPU Execution (MMU.cpp)
Added code inMMU::write()to monitor all writes to VRAM from the first CPU cycle. The code detects when the CPU starts executing and checks the state of VRAM at that time. It also logs the first 500 non-zero writes with detailed information.
// --- Step 0355: Monitoring Writes from the Start of Execution ---
// Monitor all writes to VRAM from the first CPU cycle
static int vram_write_from_cpu_start_count = 0;
static int vram_write_non_zero_from_cpu_start = 0;
static bool cpu_started = false;
// Detect when the CPU starts executing (first write to any address)
if (!cpu_started) {
cpu_started = true;
printf("[MMU-VRAM-CPU-START] CPU started executing | Monitoring VRAM writes from now\n");
// Check VRAM status when CPU starts
int non_zero_bytes = 0;
for (uint16_t check_addr = 0x8000; check_addr< 0x9800; check_addr++) {
uint8_t byte = memory_[check_addr];
if (byte != 0x00) {
non_zero_bytes++;
}
}
printf("[MMU-VRAM-CPU-START] VRAM state when CPU starts | Non-zero bytes: %d/6144 (%.2f%%)\n",
non_zero_bytes, (non_zero_bytes * 100.0) / 6144);
// Verificar estado de VRAM en este punto
check_vram_state_at_point("CPU Start");
}
if (addr >= 0x8000 && addr< 0x9800) {
vram_write_from_cpu_start_count++;
if (value != 0x00) {
vram_write_non_zero_from_cpu_start++;
// Loggear todas las escrituras no-cero (no solo las primeras 100)
if (vram_write_non_zero_from_cpu_start <= 500) {
// Obtener PC del CPU (ya está disponible en debug_current_pc)
uint16_t pc = debug_current_pc;
// Obtener frame desde PPU (si está disponible)
uint64_t frame = 0;
if (ppu_ != nullptr) {
frame = ppu_->get_frame_counter();
}
printf("[MMU-VRAM-WRITE-FROM-CPU-START] Non-zero write #%d | Addr=0x%04X | Value=0x%02X | "
"PC=0x%04X | Frame %llu | Total writes=%d\n",
vram_write_non_zero_from_cpu_start, addr, value, pc,
static_cast<unsigned long long>(frame),
vram_write_from_cpu_start_count);
}
}
//Log statistics every 1000 writes
if (vram_write_from_cpu_start_count > 0 && vram_write_from_cpu_start_count % 1000 == 0) {
printf("[MMU-VRAM-WRITE-FROM-CPU-START-STATS] Total writes=%d | Non-zero writes=%d | "
"Non-zero ratio=%.2f%%\n",
vram_write_from_cpu_start_count, vram_write_non_zero_from_cpu_start,
(vram_write_non_zero_from_cpu_start * 100.0) / vram_write_from_cpu_start_count);
}
}
// -----------------------------------------
Modified Files
src/core/cpp/MMU.cpp- Added VRAM initialization check in constructor, initial data loading check from ROM, functioncheck_vram_state_at_point(), and monitoring of writes from the start of CPU executionsrc/core/cpp/MMU.hpp- Added declarationcheck_vram_state_at_point()
Tests and Verification
Tests were run with the 5 ROMs in parallel (~2.5 minutes total) to analyze the VRAM initialization and the discrepancy in initial data:
- Tested ROMs:pkmn.gb, tetris.gb, mario.gbc, pkmn-amarillo.gb, Oro.gbc
- Command executed:
timeout 150 python3 main.py roms/<rom>.gb 2>&1 | tee logs/test_<rom>_step0355.log &(5 parallel processes) - Log analysis:commands were used
grepandheadto extract specific information without cluttering the context
Key Results
- VRAM Initialization (Constructor):✅ 0 non-zero bytes (0.00%) - as expected
- After loading ROM (Immediate):✅ 0 non-zero bytes (0.00%) - confirms that the ROMs have no initial data
- After load_test_tiles():⚠️ 95.49% non-zero bytes (5867/6144) - load test tiles
- Status when CPU starts:⚠️ 0.65% non-zero bytes (40/6144) - game deletes test tiles
- Writes from CPU Start:❌ 0% non-zero writes - all writes are zeros (0x00)
Analysis Commands
# Verificar inicialización de VRAM
grep "\[MMU-VRAM-INIT\]" logs/test_*_step0355.log | head -n 10
# Verificar estado después de cargar ROM
grep "\[MMU-VRAM-AFTER-ROM-LOAD\]" logs/test_*_step0355.log | head -n 10
# Verificar estado en múltiples puntos
grep "\[MMU-VRAM-STATE-POINT\]" logs/test_*_step0355.log | head -n 30
# Verificar escrituras desde el inicio del CPU
grep "\[MMU-VRAM-CPU-START\]" logs/test_*_step0355.log | head -n 20
grep "\[MMU-VRAM-WRITE-FROM-CPU-START\]" logs/test_*_step0355.log | head -n 50
Log Example
[MMU-VRAM-INIT] VRAM initialized | Non-zero bytes: 0/6144 (0.00%)
[MMU-VRAM-AFTER-ROM-LOAD] VRAM after ROM load | Non-zero bytes: 0/6144 (0.00%)
[MMU-VRAM-INITIAL-STATE] VRAM initial state | Non-zero bytes: 5867/6144 (95.49%)
[LOAD-TEST-TILES] Called function
[LOAD-TEST-TILES] Loading test tiles into VRAM...
[MMU-VRAM-CPU-START] VRAM state when CPU starts | Non-zero bytes: 40/6144 (0.65%)
[MMU-VRAM-ERASE-DETECTION] Erase #1 | Addr=0x8010 | Old value=0xAA | PC=0x36E3 | Frame 6
Native Validation
Compiled C++ module validation: The code was compiled successfully without errors (only minor warnings about unused variables). Added#include <string>to usestd::stringincheck_vram_state_at_point().
Findings and Conclusions
Main Findings
- VRAM initialization:✅ VRAM is correctly initialized with zeros in the MMU constructor (0 non-zero bytes, 0.00%).
- Loading Data from ROM:✅ After loading the ROM, VRAM still has 0 non-zero bytes (0.00%), which confirms that the ROMs have no initial data in VRAM.
- Cause of Identified Discrepancy:⚠️ The discrepancy between Step 0353 (92-98% non-zero bytes) and Step 0354 (40 non-zero bytes, 0.65%) is due to:
load_test_tiles()load test tiles into VRAM (95.49% non-zero bytes)- The game deletes these test tiles when it starts running (Frame 6, PC=0x36E3)
- When the CPU starts, VRAM has only 0.65% non-zero bytes (40/6144)
- The Game Delete Test Tiles:⚠️ The game deletes the test tiles that
load_test_tiles()post. Blanks occur in Frame 6, PC=0x36E3, when the LCD is on (not VBLANK). - Writes from CPU Start:❌ 0% non-zero writes from CPU startup. All writes to VRAM are zeros (0x00), which means the game is not loading new tiles, it is just deleting test tiles.
Complete Sequence Identified
- Builder:VRAM = 0 non-zero bytes (0.00%) ✅
- load_rom():VRAM = 0 non-zero bytes (0.00%) ✅ - ROMs have no initial data
- load_test_tiles():VRAM = 95.49% non-zero bytes (5867/6144) ⚠️ - Load test tiles
- CPU starts:VRAM = 0.65% non-zero bytes (40/6144) ⚠️ - The game has already deleted most of the tiles
- Frame 6, PC=0x36E3:The game deletes the remaining test tiles ⚠️
Conclusions
The initial state of VRAM is correctly initialized with zeros in the MMU constructor. ROMs have no initial data in VRAM, so VRAM remains empty after loading the ROM. The discrepancy between the steps is due to the fact thatload_test_tiles()loads test tiles that the game then deletes when it starts running.
Identified problem:The game deletes the test tiles thatload_test_tiles()loads, and then does not load new tiles (all writes are zeros). This explains why VRAM is never filled with tiles during execution.
Sources consulted
- Bread Docs:VRAM (Video RAM)
- Bread Docs:Power Up Sequence
- Bread Docs:Memory Map
Educational Integrity
What I Understand Now
- VRAM initialization:VRAM is correctly initialized with zeros in the MMU constructor (0 non-zero bytes, 0.00%).
- Loading Data from ROM:ROMs do not have initial data in VRAM. Initial data is generated during game initialization, not from ROM.
- Cause of Discrepancy:The discrepancy between the steps is due to the fact that
load_test_tiles()loads test tiles (95.49% non-zero bytes) which the game then deletes when it starts running (Frame 6, PC=0x36E3). - The Game Delete Test Tiles:The game deletes the test tiles that
load_test_tiles()loads, and then does not load new tiles (all writes are zeros). - Monitoring from Start:We can monitor all writes to VRAM from the first CPU cycle to identify when and how initial data is loaded.
What remains to be confirmed
- Why doesn't the game load new tiles?All writes to VRAM since CPU startup are zeros (0x00). We need to investigate why the game doesn't load new tiles after deleting the test tiles.
- Is there a problem with the tile loading timing?The game clears test tiles when the LCD is on (not VBLANK). We need to check if there is a problem with the tile loading timing or if the game expects specific conditions that are not being met.
- Is the behavior different without load_test_tiles()?We need to consider disabling
load_test_tiles()for future testing and see real game behavior without test tiles.
Hypotheses and Assumptions
Main hypothesis:The initial state of VRAM is correctly initialized with zeros. ROMs do not have initial data in VRAM. The discrepancy between the steps is due to the fact thatload_test_tiles()loads test tiles that the game then deletes when it starts running.
Confirmed assumption:The discrepancy between the steps is due to the fact that it was measured at different times. Step 0353 measured afterload_test_tiles()(95.49% non-zero bytes), while Step 0354 measured when the CPU starts (0.65% non-zero bytes), after the game deleted most of the test tiles.
New hypothesis:The game does not load new tiles because all writes to VRAM since CPU startup are zeros (0x00). This suggests that there is a problem with the tile loading timing or that the game expects specific conditions that are not being met.
Next Steps
- [x]Run tests:✅ The tests were executed with the 5 ROMs in parallel and the logs were analyzed.
- [x]Analyze logs:✅ Identified the cause of the discrepancy:
load_test_tiles()loads test tiles that the game then deletes. - [ ] Step 0356:Investigate why the game does not load new tiles after deleting test tiles. Check if there is a problem with the tile loading timing or if the game expects specific conditions that are not being met.
- [ ] Consider disabling load_test_tiles():For future testing, consider disabling
load_test_tiles()and see real game behavior without test tiles.