Video summary

Gameboy Emulator Development - Part 11

Main summary

Key takeaways

Technology

Main focus: Starting PPU emulation (tiles, OAM/OEM, VRAM) and visual debugging

The video starts after verifying CPU emulation with standard CPU test ROMs, then transitions into PPU (Pixel Processing Unit) work. The goal is to implement PPU memory read/write behavior—specifically OAM/OEM and VRAM—and to render Game Boy tile data in a debug window so correctness can be visually confirmed.


1) Sprite attribute memory (OAM/OEM) structure and layout

  • OAM RAM stores sprite metadata for up to 40 sprites.
  • The code treats addresses >= 0xFE00 specially as the OAM base region in the Game Boy address map.
  • Each sprite entry is 4 bytes:
    1. Y position
    2. X position
    3. Tile index
    4. Attribute flags

Sprite attribute flags are bit-packed. The presenter highlights components such as:

  • Palette number (3 bits)
  • (Color Game Boy only) VRAM tile bank / palette-related bits (not fully used yet)
  • X flip / Y flip
  • Background priority

Implementation detail

  • A C struct is introduced to represent an OAM entry, with either:
    • a single byte of flags, or
    • bitfields for individual flag components.

2) PPU context: implementing memory read/write through the bus

A PPU context is introduced that contains:

  • OAM RAM (40 entries)
  • VRAM (0x2000 bytes)

Functions to implement:

  • ppu_oam_read/write
    • Addressed with the 0xFE00 offset subtracted
  • ppu_vram_read/write
    • Uses the 0x8000 offset logic

Next, the emulator’s bus integration is updated so PPU memory accesses go through these PPU functions.

Sanity checking

  • After refactoring, the presenter rebuilds and runs the zero/one special test ROM to ensure no regressions.

3) Tile data in VRAM: how tiles are stored

Key tile storage rule:

  • Tile data starts at VRAM 0x8000
  • Each tile occupies 16 bytes
  • Each tile is 8x8 pixels, using 4 possible colors (shades of gray)

Tile indexing behavior is discussed:

  • Tile IDs 0–127 and 128–255, with behavior influenced by flags (not fully implemented yet).

4) Debug rendering: displaying all tiles from VRAM

To validate tile decoding, the presenter adds an SDL debug window that renders decoded VRAM tiles.

  • A debug texture is created for a 16x8 tile grid, scaled by a configurable scale (default mentioned: 4).
  • A function like ui_update() is added:
    • Clears the screen to dark gray
    • Iterates through all tiles in VRAM (referred to as 384 tiles)
    • Reads from 0x8000 as the tile data start
    • Calls a helper function to render each tile

display_tile implementation (tile decoding logic)

For a given tile:

  • For each scanline, the code reads two bytes (because an 8x8 row needs 16 bytes total across the tile):
    • One byte contains the high bitplane
    • One byte contains the low bitplane
  • For each pixel (bit 7 down to bit 0):
    • Extract one bit from the high plane and one from the low plane
    • Combine them into a 2-bit color index (0–3)
    • Render the pixel via SDL fill_rect using the chosen color

Observation

  • The debug window shows tiles correctly “loaded into memory,” but some games (e.g., Tetris) remain blank initially because sprite/OAM writing behavior isn’t implemented as real hardware does it yet.

5) Implementing DMA for OAM transfers (needed for games like Tetris)

Typical games populate OAM via DMA.

DMA module

A DMA module is added with:

  • dma_start(value) triggered by writes to 0xFF46 (DMA start register)
  • dma_tick() called during CPU timing
  • A DMA context tracking:
    • active flag
    • current byte index
    • start_delay set to 2 cycles before transfer begins

DMA behavior

  • DMA reads source from memory:
    • Source base derived from the written value, described as 0x100 + currentByteIndex
  • Copies 160 bytes total
    • Stops when the byte index reaches 0xA0 (or related range)
  • Completion logs a “done” message for debugging.

Timing integration

The emulator’s cycle loop is refactored:

  • There is a nested per-cycle loop for timer ticks
  • DMA tick is performed once per CPU cycle (not the timer’s 4x rate).

Bus/PPU read restrictions during DMA

Rules are added during active DMA:

  • If DMA is transferring, OAM reads return 0xFF
  • If DMA is transferring, OAM writes are ignored (disallowed during DMA)

6) Progress and debugging stops

After DMA is implemented, the presenter runs Tetris again:

  • Initially it still doesn’t work fully due to incomplete emulated registers/hardware behavior.
  • To get more visibility, pragmatic hacks are introduced, such as a temporary response for LCD-related reads (e.g., LY via FF44).
  • Eventually logs show:
    • “dma done”
    • signs that Tetris intro screen data is being transferred/processed

The video concludes by positioning this as a foundation toward a full PPU, to be covered next.


Key deliverables / “tutorial steps” covered

  1. Define and implement OAM/OEM sprite attribute storage
    • 40 sprites, 4-byte entries
  2. Implement PPU context memory for OAM and VRAM, including correct offset handling:
    • OAM offset around 0xFE00
    • VRAM offset around 0x8000
  3. Integrate PPU read/write into bus.c
  4. Decode VRAM tile format
    • 16 bytes per 8x8 tile
    • 2-bitplanes → 2-bit color indices → 4 colors
  5. Add an SDL debug tile viewer to visually validate decoding
  6. Implement DMA to OAM via register 0xFF46, including:
    • source selection
    • DMA timing via dma_tick
    • read/write restrictions during active DMA

Main speakers / sources

  • Main speaker/source: The single presenter from the YouTube channel (referred to as “Low-level Devil” / “low-level dev…” in the intro).

Original video