NES TDD

252. Отображение памяти палитры шины PPU

Реализуйте отображение адресов памяти палитры PpuBus.

Урок 252 из 356 · tests/chapter_03_ppu_memory_and_graphics_data/test_252_ppu_bus_palette_ram_mapping.py

Справка

https://www.nesdev.org/wiki/PPU_palettes
https://www.nesdev.org/wiki/PPU_memory_map

Файл для обновления

emulator/bus/ppu_bus.py

Константы для добавления

PALETTE_START = 0x3F00
PALETTE_END = 0x3FFF
PALETTE_SIZE = 0x20

Почему этот шаг существует

Диапазон адресов PPU $3F00-$3FFF — это память палитры. Память палитры не хранит графику плиток. Она хранит индексы цветов NES, используемые позже при рендеринге фона и спрайтов.

Примечание об оборудовании

На оригинальном PPU NES память палитры — это небольшая отдельная область памяти размером 32 байта внутри PPU. Это не CHR ROM и не VRAM таблицы имён. Она имеет собственное поведение зеркалирования в диапазоне адресов PPU $3F00-$3FFF.

Упрощение в учебнике

На этом этапе нам не нужно разделять хранилище Python на отдельный объект PaletteRAM. Мы можем продолжать использовать существующий большой массив резервного VRAM в качестве физического места хранения, если PpuBus нормализует адреса палитры перед чтением или записью.

Ментальная модель

PPU.write_register($2007, value)
    -> ppu_bus.write(vram_addr, value)
    -> PpuBus detects whether vram_addr is palette memory
    -> PpuBus normalizes palette mirrors
    -> backing storage receives the byte

Правила зеркалирования палитры для этого шага

$3F00-$3F1F is the 32-byte palette window
$3F20-$3FFF mirrors $3F00-$3F1F

Специальные зеркала фона

$3F10 mirrors $3F00
$3F14 mirrors $3F04
$3F18 mirrors $3F08
$3F1C mirrors $3F0C

Предлагаемый пример реализации

PALETTE_START = 0x3F00
PALETTE_END = 0x3FFF
PALETTE_SIZE = 0x20

def normalize_palette_addr(self, addr: int) -> int:
    index = (addr - PALETTE_START) % PALETTE_SIZE

    if index in (0x10, 0x14, 0x18, 0x1C):
        index -= 0x10

    return PALETTE_START + index

def read(self, addr: int) -> int:
    addr = addr & PPU_ADDRESS_MASK

    if CHR_START <= addr <= CHR_END:
        ...

    if PALETTE_START <= addr <= PALETTE_END:
        return self.vram.read(self.normalize_palette_addr(addr))

    return self.vram.read(addr)

def write(self, addr: int, value: int) -> None:
    addr = addr & PPU_ADDRESS_MASK

    if CHR_START <= addr <= CHR_END:
        ...
        return

    if PALETTE_START <= addr <= PALETTE_END:
        self.vram.write(self.normalize_palette_addr(addr), value)
        return

    self.vram.write(addr, value)

Важный выбор дизайна

Логика зеркалирования палитры принадлежит PpuBus, а не PPU. PPU обрабатывает поведение регистров; PpuBus обрабатывает маршрутизацию адресов памяти PPU.

Вне области действия

  • фактические цвета RGB
  • рендеринг
  • выбор палитры фона/спрайта
  • декодирование атрибутов таблицы имён
  • разделение памяти палитры в собственный класс хранилища

Запустить этот урок

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