⚠️ Clean-Room / Educational

This project is educational and Open Source. No code is copied from other emulators. Implementation based solely on technical documentation and permitted tests.

Step 0259: VRAM Write Monitor & MBC Check

Date:2025-12-23 StepID:0259 State: draft

Summary

This Step instruments the MMU to monitor writes to VRAM and analyzes the ROM read logic to confirm if there is MBC (Memory Bank Controllers) support. The goal is to determine if VRAM is empty because the game is trying to read graphics from unmapped ROM banks, which would explain why the CPU copies zeros to VRAM.

Main Hypothesis:If the VRAM is completely empty (all zeros), the PPU will render index 0 (green/white) pixels. With forced palette in C++ (Step 0257) and Python (Step 0256), if the screen remains green even with games that have the LCD on (such as Pokémon Red, `LCDC:E3`), the only logical explanation left is thatVRAM is full of zeros. If the VRAM is all 0, the PPU draws "Color 0" perfectly, which is... green.

MBC theory:If the CPU works (Mario and Pokémon run), why don't they copy the graphics? The main theory is thatmemory mapping (MBC) is not implemented. If the game tries to read graphics from Bank 2, 3, etc., but only Bank 0 was loaded, it will read garbage or zeros, and copy those zeros to VRAM.

Hardware Concept

VRAM (Video RAM)

The VRAM on the Game Boy occupies the range `0x8000-0x9FFF` (8KB) ​​and contains:

  • Tile Data (0x8000-0x97FF):Data from the tiles (graphics) used to render the background and sprites. Each tile occupies 16 bytes (2 bytes per line of 8 pixels).
  • Tile Map (0x9800-0x9FFF):Tile maps that indicate which tile is drawn at each background position. Each byte of the map points to a tile in the Tile Data.

MBC (Memory Bank Controllers)

Game Boy cartridges can have different ROM sizes:

  • ROM ONLY (32KB):It fits integer in the address space `0x0000-0x7FFF`. You don't need MBC.
  • MBC1/MBC3 (>32KB):They use a Memory Bank Controller to swap ROM banks. The space `0x0000-0x3FFF` always maps to Bank 0, but the space `0x4000-0x7FFF` can map to different banks (1, 2, 3, etc.) by writing to special registers of the MBC.

Critical Problem:If our C++ emulator (`MMU.cpp`)NOimplements MBC1/MBC3, the game tries to read graphics from Bank X, but reads Bank 1 (or garbage), or zeros. The CPU copies those "zeroes" to VRAM. Result: Green Screen.

Fountain:Pan Docs - "Memory Bank Controllers", "Cartridge Types", "Memory Map"

Implementation

1. VRAM Write Monitor (`MMU.cpp::write`)

Added a specific monitor for the VRAM range (`0x8000` - `0x9FFF`) that records the first 50 writes:

// --- Step 0259: VRAM WRITE MONITOR ---
// Monitor the first 50 writes to VRAM to see what data arrives
// If VRAM is empty (zeroes), the PPU will render index 0 (green/white) pixels.
// If we see non-zero values, it means that the CPU is copying data.
// If we only see zeros, the problem is in the loading of graphical data (possibly MBC).
static int vram_write_counter = 0;
if (addr >= 0x8000 && addr<= 0x9FFF) {
    if (vram_write_counter < 50) {
        printf("[VRAM] PC:%04X ->Write VRAM [%04X] = %02X\n", debug_current_pc, addr, value);
        vram_write_counter++;
    }
}
// -----------------------------------------

This monitor records:

  • PC:The Program Counter of the CPU when the write is executed (to know where the game writes from).
  • Addr:The VRAM address where it is written.
  • Value:The value that is written. If they are all `00`, the CPU is copying zeros. If they are `FF` or varied, there is data.

2. ROM Read Analysis (`MMU.cpp::read`)

Added a critical comment documenting the lack of MBC support:

// --- Step 0259: ROM READ ANALYSIS (MBC) ---
// IMPORTANT: The current implementation DOES NOT support MBC (Memory Bank Controllers).
// The ROM is loaded flat into memory_[0x0000-0x7FFF] using load_rom().
// 
// For large games (>32KB):
// - Bank 0 (0x0000-0x3FFF): Loaded successfully
// - Bank 1+ (0x4000-0x7FFF): If the game tries to change banks, it will read
// garbage or zeros because only bank 0 was loaded.
// 
// This may explain why VRAM is empty - the game tries to read graphics
// from bank 2, 3, etc., but reads zeros, and copies those zeros to VRAM.
// 
// Source: Pan Docs - "Memory Bank Controllers", "Cartridge Types"
// -----------------------------------------

3. Review of `load_rom()`

The `load_rom()` method simply copies the ROM directly to `memory_` starting at `0x0000`, without any bank handling:

void MMU::load_rom(const uint8_t* data, size_t size) {
    // Validate that the data does not exceed the memory size
    size_t copy_size = (size > MEMORY_SIZE) ? MEMORY_SIZE : size;
    
    // Copy the ROM data to memory, starting at 0x0000
    // We use memcpy for maximum speed
    std::memcpy(memory_.data(), data, copy_size);
}

Problem:If the ROM is larger than 32KB, only the first 32KB are loaded. If the game tries to read from bank 1, 2, etc. (addresses `0x4000-0x7FFF`), it will read garbage or zeros because only bank 0 was loaded.

Design Decisions

  • Monitor limited to 50 writes:It is limited to the first 50 writes so as not to saturate the log. This is enough to see if the CPU is copying zeros or real data.
  • Include PC in the log:Program Counter is included to know where the game writes from (probably an `LDI` or `LD` copy routine).
  • MBC Documentation:Added a critical comment documenting the lack of MBC support, explaining why VRAM may be empty.

Affected Files

  • src/core/cpp/MMU.cpp- Modified the methodwrite()to add VRAM write monitor (Step 0259).
  • src/core/cpp/MMU.cpp- Modified the methodread()to add comment about lack of MBC support (Step 0259).

Tests and Verification

VRAM Monitor Validation:

  1. Recompilation:Execute.\rebuild_cpp.ps1to recompile the C++ extension.
  2. Execution:Executepython main.py roms/pkmn.gb(Pokémon is ideal because we know it tries to draw).
  3. Log observation:Search the log for the lines[VRAM] PC:XXXX -> Write VRAM [XXXX] = XX.
  4. Interpretation:
    • If the values ​​are `00`:The CPU is copying zeros. This confirms the broken MBC theory (the game tries to read bank X charts, but reads zeros).
    • If the values ​​are `FF` or varied:There is data. Then the problem is again the PPU (it is not reading the tiles correctly from VRAM).
    • Look at the `PC`:Where do you write from? (Probably an `LDI` or `LD` copy routine).

Test Command:

.\rebuild_cpp.ps1
python main.py roms/pkmn.gb

Expected Result:The log should show the first 50 writes to VRAM in the format[VRAM] PC:XXXX -> Write VRAM [XXXX] = XX. If all values ​​are `00`, we confirm that the CPU is copying zeros, suggesting an MBC issue.

C++ Compiled Module Validation:The code runs in compiled C++, so you need to recompile the extension before running. The monitor runs in real time during emulation, recording writes to VRAM.

Sources consulted

Educational Integrity

What I Understand Now

  • MBC (Memory Bank Controllers):Large cartridges (>32KB) use MBC to swap ROM banks. The space `0x0000-0x3FFF` always maps to Bank 0, but the space `0x4000-0x7FFF` can map to different banks by writing to special registers of the MBC.
  • MBC not implemented issue:If the game tries to read graphics from bank 2, 3, etc., but only bank 0 was loaded, it will read garbage or zeros. The CPU copies those "zeros" to VRAM, resulting in a green screen.
  • Writing Monitor:By monitoring writes to VRAM, we can see what data the CPU is copying. If all are zeros, we confirm that the problem is in the graphical data loading (possibly MBC).
  • Negative Logic:If the PPU was working and had non-zero data, we would see gray or black pixels. If we only see green, it means that the PPU is only reading zeros. Therefore, VRAM is empty (or full of zeros).

What remains to be confirmed

  • Values ​​in VRAM:Run the diagnostic with Pokémon Red and verify the values ​​that are written to VRAM. If they are all `00`, we confirm that the CPU is copying zeros, suggesting an MBC issue.
  • MBC Implementation:If we confirm that the issue is MBC, we should implement basic MBC1/MBC3 support in the MMU to allow large games to load graphics from higher banks.
  • Load Timing:If the VRAM contains data but the screen is still green, we must investigate why the PPU is not correctly reading the tiles from VRAM or why the Tile Map points to empty tiles.

Hypotheses and Assumptions

Main Hypothesis:If the CPU works (Mario and Pokémon run), why don't they copy the graphics? The main theory is that memory mapping (MBC) is not implemented. If the game tries to read graphics from bank 2, 3, etc., but only bank 0 was loaded, it will read garbage or zeros, and copy those zeros to VRAM.

Assumption:We assume that the VRAM write monitor (first 50 writes) is sufficient to determine whether the CPU is copying zeros or real data. If all values ​​are `00`, we confirm that the problem is in the loading of graphical data (possibly MBC).

Next Steps

  • [ ] Recompile:.\rebuild_cpp.ps1
  • [ ] Execute:python main.py roms/pkmn.gb(Pokémon is ideal because we know it tries to draw).
  • [ ] Observe the logs[VRAM]:
    • Do you see logs for `[VRAM]`?If not, the CPU is not writing to VRAM (more serious problem).
    • Look at the values ​​(`Val`):
      • If they are `00`: The CPU is copying zeros. (Confirms theory of broken MBC).
      • If they are 'FF' or varied: There is data. Then the problem is again the PPU.
    • Look at the `PC`:Where do you write from? (Probably an `LDI` or `LD` copy routine).
  • [ ] If we confirm that the problem is MBC:
    • Implement basic MBC1/MBC3 support in the MMU.
    • Detect the cartridge type from the ROM header.
    • Implement bank switching when the game writes to `0x0000-0x7FFF` (MBC logs).
  • [ ] If the VRAM contains data but the screen is still green:
    • Investigate why the PPU is not correctly reading the tiles from VRAM.
    • Verify that the Tile Map points to valid tiles (not empty tiles).
    • Verify that the PPU is correctly decoding the tiles from VRAM.