263. CPU 总线 PRG 写入路由

在 CPU 的 NMI 测试之前,让 CPU 对 PRG 空间的写入经由映射器路由。

263 / 356 · tests/chapter_04_ppu_timing_and_vblank/test_263_cpu_bus_prg_write_routing.py

需要更新的文件

emulator/cartridge/mapper_interface.py
emulator/cartridge/mapper000.py
emulator/bus/cpu_bus.py

此步骤存在的原因

接下来的 CPU 中断测试需要把 NMI 向量的字节放在

$FFFA = NMI vector low byte
$FFFB = NMI vector high byte

这些地址位于 CPU 的 PRG 区域内

$8000-$FFFF

在测试 CPU.interrupt_nmi() 之前,我们需要让 CPU 总线在这个地址范围内的写入行为变得明确且安全。

什么是 PRG 写入路由?PRG 写入路由是指 CpuBus 不会直接修改卡带 ROM 的字节。相反,$8000-$FFFF 范围内的写入会被转发给卡带的映射器:

CpuBus.write($8000-$FFFF, value)
    -> mapper.write_prg(addr, value)

最小示例

CPU writes $8000 = $12
CpuBus calls mapper.write_prg($8000, $12)
Mapper000/NROM ignores it because it has no PRG write registers
Later mappers may treat the same write as a bank-switch command

常见误解

写入 $8000 并不意味着 CPU 改变了 ROM 的字节。在真实卡带上,这个范围内的写入通常会被简单板卡忽略,或者被更高级的板卡解释为映射器控制信号。

重要的职责划分

Real cartridge path:
    CpuBus.write($8000-$FFFF) -> mapper.write_prg(...)

Tutorial FakeROM path:
    CpuBus.write($8000-$FFFF) -> FakeROM.write(addr - $8000, value)

FakeROM 路径只是一种测试搭建上的便利手段。它让接下来的中断测试可以在 $FFFA/$FFFB 处安装向量字节,而不必让真正的 Mapper000 PRG ROM 变成可写的。

建议的实现示例

class MapperInterface(Protocol):
    def read_prg(self, addr: int) -> int:
        ...

    def write_prg(self, addr: int, value: int) -> None:
        ...

    def read_chr(self, addr: int) -> int:
        ...

    def write_chr(self, addr: int, value: int) -> None:
        ...

class Mapper000:
    def write_prg(self, addr: int, value: int) -> None:
        if not (PRG_ROM_START <= addr <= PRG_ROM_END):
            raise ValueError(f"Address out of PRG ROM range: {addr:04X}")

        # Mapper000/NROM has no writable PRG registers.
        # Real hardware ignores writes to PRG ROM space, so we ignore them too.
        return

class CpuBus:
    def write(self, addr: int, value: int) -> None:
        ...

        if 0x8000 <= addr <= 0xFFFF:
            if self.mapper is not None:
                self.mapper.write_prg(addr, value)
                return

            if self.program_rom is not None:
                self.program_rom.write(addr - 0x8000, value)
                return

        raise ValueError(f"Unsupported CPU bus write: {addr:04X}")

不在本步骤范围内

  • CPU.interrupt_nmi() 的行为
  • 由 CPU 消费 PPU 发起的 NMI 请求
  • Mapper001/MMC 的存储体切换
  • 让真正的 PRG ROM 变为可写

运行本课

uv run pytest tests/chapter_04_ppu_timing_and_vblank/test_263_cpu_bus_prg_write_routing.py -v