233. Vram memory device

Create a simple VRAM memory device.

Lesson 233 of 356 · tests/chapter_03_ppu_memory_and_graphics_data/test_233_vram_memory_device.py

File to create

emulator/memory/vram.py

Class to implement

VRAM

Why this step exists

The PPU has its own address space, separate from the CPU address space. CPU RAM is not where background/sprite graphics state normally lives. The PPU needs video-facing memory that it can access through PPUADDR/PPUDATA and, later, through rendering logic.

Important mental model

CPU address space: $0000-$FFFF
    handled by CpuBus

PPU address space: $0000-$3FFF
    will be handled by PpuBus

For this early step, VRAM is a simple writable backing store for the PPU-side address space. Later, PpuBus will decide how PPU addresses map to CHR ROM, nametable VRAM, palette RAM, and mirrors.

Why VRAM is separate from PPU registers

PPU registers such as $2006 and $2007 are CPU-visible control/data ports. They are not the memory itself.

Example flow later

CPU writes $20 to $2006
CPU writes $00 to $2006
CPU writes $AA to $2007

Meaning

PPU internal address becomes $2000
PPUDATA writes $AA into PPU-side memory at $2000

The CPU touches register $2007, but the actual video memory address is the PPU's internal address, not CPU address $2007.

Current scope

  • VRAM is a MemoryDevice
  • VRAM stores 0x4000 bytes
  • VRAM.read(addr) returns stored byte at addr
  • VRAM.write(addr, value) stores only the low byte of value

Important responsibility split

VRAM should not apply addr & 0x3FFF. Address normalization/routing belongs to PpuBus, because PpuBus owns the PPU address map. VRAM is only storage.

Suggested implementation pseudocode

from dataclasses import dataclass, field
from emulator.memory.memory_device import MemoryDevice

VRAM_SIZE = 0x4000


@dataclass
class VRAM(MemoryDevice):
    _data: bytearray = field(
        default_factory=lambda: bytearray(VRAM_SIZE),
        init=False,
    )

    def write(self, addr: int, value: int) -> None:
        self._data[addr] = value & 0xFF

    def read(self, addr: int) -> int:
        return self._data[addr]

Out of scope

  • PpuBus routing
  • CHR ROM mapping
  • nametable mirroring
  • palette RAM
  • rendering

Run this lesson

uv run pytest tests/chapter_03_ppu_memory_and_graphics_data/test_233_vram_memory_device.py -v