⚠️ 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.

VRAM and Tilemap Diagnostics

Date:2025-12-25 StepID:0289 State: VERIFIED

Summary

Implemented three additional diagnostic monitors to verify what the VRAM PPU reads and what the tilemap contains. Step 0288 identified that VRAM is empty (only zeros), so these monitors will confirm whether the problem is in the PPU reading or in the data loading. The [VRAM-READ], [TILEMAP-INSPECT] and [TILEDATA-INSPECT] monitors were implemented.

Hardware Concept

Reading VRAM by the PPU:During the rendering of each scanline, the PPU reads data from VRAM to build the image. It first reads the Tile Map (0x9800-0x9FFF or 0x9C00-0x9FFF according to LCDC bit 6) to obtain the Tile ID that should be rendered at each position. It then reads the Tile Data (0x8000-0x97FF) using the Tile ID to get the pixel bytes. If VRAM is empty (only zeros), the PPU will read empty tiles and render a blank or single-color screen.

Tile Map and Tile IDs:The Tile Map is a table of 32x32 tiles that specifies which tile should be rendered at each position on the screen. Each byte of the Tile Map contains a Tile ID (0-255). The Tile ID is used to calculate the base address of the tile in Tile Data: if the addressing mode is signed (LCDC bit 4 = 0), Tile ID 0 is at 0x9000 and signed arithmetic is used; if unsigned (bit 4 = 1), Tile ID 0 is at 0x8000 and is multiplied directly by 16.

Tile Data and 2bpp Format:Each tile occupies 16 bytes (2 bytes per line, 8 lines). Each line is made up of 2 bytes: the low byte contains the low bits of each pixel (bit 7 = pixel 0, bit 6 = pixel 1, etc.), and the high byte contains the high bits. The pixel color is calculated as (bit_high<< 1) | bit_bajo, resultando en un índice de color (0-3). Si ambos bytes son 0x00, el tile está vacío (todos los píxeles son color 0).

Fountain:Pan Docs - "Video RAM (VRAM)", "Tile Data", "Tile Map", "LCD Control Register (LCDC)"

Implementation

1. Monitor [VRAM-READ] in MMU.cpp

Implemented a monitor in the functionMMU::read()which captures all VRAM reads (0x8000-0x9FFF). The monitor reports the address read, the value obtained, the PC that originated the read, and the current ROM bank. It has a limit of 100 readings to avoid log saturation.

// --- Step 0289: VRAM Reads Monitor ([VRAM-READ]) ---
if (addr >= 0x8000 && addr<= 0x9FFF) {
    uint8_t vram_value = memory_[addr];
    static int vram_read_count = 0;
    if (vram_read_count < 100) {
        printf("[VRAM-READ] Read %04X ->%02X (PC:0x%04X Bank:%d)\n",
               addr, vram_value, debug_current_pc, current_rom_bank_);
        vram_read_count++;
    }
    return vram_value;
}

2. Inspector [TILEMAP-INSPECT] in PPU.cpp

Improved the existing Tile Map inspector (from Step 0263) to run at the start of each frame (LY=0) instead of just once. The inspector prints the first 32 bytes of the tilemap (full first row), calculates a checksum of the entire tilemap (1024 bytes), and reports the LCDC configuration. It is only executed in the first 5 frames to avoid saturating the logs.

// --- Step 0289: Tilemap Inspector ([TILEMAP-INSPECT]) ---
static int frame_count = 0;
if (ly_ == 0) {
    frame_count++;
    if (frame_count<= 5) {
        printf("[TILEMAP-INSPECT] Frame %d | LCDC: %02X | BG Map Base: %04X | BG Data Base: %04X\n",
               frame_count, lcdc, tile_map_base, tile_data_base);
        
        // Imprimir las primeras 32 bytes (primera fila completa del tilemap)
        printf("[TILEMAP-INSPECT] First 32 bytes (row 0) of Map at %04X:\n", tile_map_base);
        for(int i=0; i<32; i++) {
            printf("%02X ", mmu_->read(tile_map_base + i));
            if ((i + 1) % 16 == 0) printf("\n");
        }
        printf("\n");
        
        // Calculate tilemap checksum to detect changes
        uint16_t checksum = 0;
        for(int i=0; i<1024; i++) {
            checksum += mmu_->read(tile_map_base + i);
        }
        printf("[TILEMAP-INSPECT] Tilemap checksum (first 1024 bytes): 0x%04X\n", checksum);
    }
}

3. Inspector [TILEDATA-INSPECT] in PPU.cpp

Implemented an inspector in the render loop that checks if tiles contain valid data when read. The inspector runs only in the center of the screen (LY=72, X=80) and in the first 3 frames to avoid saturating the logs. If it detects that a tile contains only zeros (byte1 == 0x00 && byte2 == 0x00), it issues a warning.

// --- Step 0289: Tile Data Inspector ([TILEDATA-INSPECT]) ---
static int tiledata_inspect_count = 0;
if (ly_ == 72 && x == 80 && tiledata_inspect_count< 3) {
    printf("[TILEDATA-INSPECT] LY:72 X:80 | TileID:%02X | TileAddr:%04X | Byte1:%02X Byte2:%02X\n",
           tile_id, tile_addr, byte1, byte2);
    if (byte1 == 0x00 && byte2 == 0x00) {
        printf("[TILEDATA-INSPECT] WARNING: Tile %02X contains only zeros (empty tile)\n", tile_id);
    }
    tiledata_inspect_count++;
}

Monitors' Objectives

  • [VRAM-READ]:Verify which addresses the VRAM PPU reads and what values ​​it obtains. This allows you to confirm whether the PPU is reading only zeros or valid data.
  • [TILEMAP-INSPECT]:Check which Tile IDs the tilemap contains. If all Tile IDs are 0x00, it means that the tilemap is empty or points only to empty tiles.
  • [TILEDATA-INSPECT]:Verify if the tiles referenced by the tilemap contain valid data. If the tiles are empty (only zeros), it will confirm that the problem is with the tile data loading.

Affected Files

  • src/core/cpp/MMU.cpp- Implementation of the monitor [VRAM-READ]
  • src/core/cpp/PPU.cpp- Implementation of the [TILEMAP-INSPECT] and [TILEDATA-INSPECT] inspectors

Tests and Verification

The monitors will be activated during the emulator execution and will generate logs with the corresponding prefixes. To verify that they work correctly:

  • Command executed: python setup.py build_ext --inplace
  • Result:C++ module compiled without errors
  • Command executed: python main.py roms/pkmn.gb > debug_step_0289.log 2>&1
  • Expected result:The logs should contain:
    • Up to 100 lines with prefix[VRAM-READ]
    • Up to 5 blocks with prefix[TILEMAP-INSPECT](one for each of the first 5 frames)
    • Up to 3 lines with prefix[TILEDATA-INSPECT](one for each of the first 3 frames in the center of the screen)

Validation:The monitors will run during rendering and provide valuable information about what the PPU reads from VRAM and what the tilemap contains. This will allow you to confirm if the problem is in the reading of the PPU or in the loading of data.

Sources consulted

Educational Integrity

What I Understand Now

  • Reading VRAM by the PPU:The PPU reads VRAM during rendering to obtain Tile IDs from the Tile Map and then pixel data from Tile Data. If VRAM is empty, the PPU will read empty tiles and render a blank screen.
  • Tile Map and Tile IDs:The Tile Map is a table that specifies which tile to render at each position. If all Tile IDs are 0x00, the tilemap is empty or points only to empty tiles.
  • 2bpp format:Each tile occupies 16 bytes (2 bytes per line). If both bytes of a line are 0x00, all pixels on that line are color 0 (white/green).

What remains to be confirmed

  • What does the PPU read from VRAM?The implemented monitors will allow you to verify if the PPU is reading only zeros or valid data.
  • What does the tilemap contain?The tilemap inspector will allow you to check if the tilemap is empty or contains valid Tile IDs.
  • Do the tiles have valid data?The tile data inspector will allow you to verify if the tiles referenced by the tilemap contain valid data.

Hypotheses and Assumptions

Hypothesis:Based on the analysis of Step 0288, we expect the monitors to confirm that:

  • The PPU is reading only zeros from VRAM (confirming that VRAM is empty)
  • The tilemap contains valid Tile IDs (possibly all 0x00 or pointing to empty tiles)
  • The referenced tiles are empty (only zeros), confirming that the problem is in the data loading

Next Steps

  • [ ] Run the emulator with Pokémon Red and analyze the logs generated by the new monitors
  • [ ] Check if [VRAM-READ] confirms that the PPU is reading only zeros
  • [ ] Check if [TILEMAP-INSPECT] shows that the tilemap is empty or contains valid Tile IDs
  • [ ] Check if [TILEDATA-INSPECT] confirms that the tiles are empty
  • [ ] Step 0290: Implement LCDC and palette monitors ([LCDC-CHANGE], [PALETTE-APPLY])
  • [ ] Step 0291: Apply corrections based on all findings