252. PPU 总线的调色板 RAM 地址映射

实现 PpuBus 的调色板 RAM 地址映射。

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 颜色索引。

硬件说明

在原版 NES 的 PPU 中,调色板 RAM 是 PPU 内部一块独立的 32 字节小内存区域。它既不是 CHR ROM,也不是名称表 VRAM。它在 PPU 地址范围 $3F00-$3FFF 中有自己的镜像规则。

教程简化

在这个阶段,我们还不需要把 Python 存储拆分成独立的 PaletteRAM 对象。只要 PpuBus 在读写之前先对调色板地址做归一化处理,我们仍然可以继续使用现有的大型 VRAM 后备数组作为物理存储位置。

思维模型

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 颜色
  • 渲染
  • 背景/精灵调色板的选择
  • 名称表属性解码
  • 把调色板 RAM 拆分成独立的存储类

运行本课

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