222. CPU 总线 cartridge 集成 第 2 部分
通过 CpuBus 读取 cartridge PRG ROM,第 2 部分。
第 222 / 356 · tests/chapter_02_rom_loading/test_222_cpu_bus_cartridge_integration_part2.py
前置条件
在这里修改读取路径之前,第 221 课的 cartridge 字段、__post_init__ 以及 mapper 工厂的接线必须已经完成。
待更新文件
emulator/bus/cpu_bus.py待更新符号
emulator.bus.cpu_bus.CpuBus.read本部分实现的内容
- CpuBus routes CPU reads in $8000-$FFFF to mapper.read_prg(addr) when a
cartridge-backed mapper exists
- CpuBus keeps the old program_rom=FakeROM path working
- CpuBus fails loudly if no PRG source is attached该步骤存在的原因
CPU 在以下 CPU 地址处看到 cartridge 程序 ROM
$8000-$FFFF但 cartridge PRG ROM 字节是从偏移 0 开始存储的。mapper 拥有该转换关系。CpuBus 只应判断 $8000-$FFFF 范围内的地址属于 cartridge PRG 区域,然后将其委托给 mapper。
正确的 cartridge 路径
CpuBus.read($8000)
-> mapper.read_prg($8000)
-> Mapper000 translates to PRG offset $0000与旧的 FakeROM 路径的重要区别
program_rom.read(addr - $8000)使用偏移量,因为 FakeROM 是一个简单的测试设备,而不是 mapper。
mapper.read_prg(addr)使用完整的 CPU 地址,因为 mapper 实现了 CPU 地址转换。
常见错误
不要调用 mapper.read_prg(addr - 0x8000)。那样会将一个偏移量传给期望接收 $8000-$FFFF 范围内 CPU 地址的代码。
建议的实现
def read(self, addr: int) -> int:
# Read from CPU Bus.
if 0x0 <= addr <= 0x1FFF:
return self.ram.read(addr & 0x07FF)
if 0x8000 <= addr <= 0xFFFF:
if self.mapper is not None:
return self.mapper.read_prg(addr)
if self.program_rom is not None:
return self.program_rom.read(addr - 0x8000)
raise ValueError("No program ROM or cartridge attached")
raise ValueError(f"Unsupported CPU bus read: {addr:04X}")原理与不变量
CpuBus 负责地址范围路由,而 Mapper000.read_prg 负责 NROM 地址转换。内部 RAM 镜像保持不变。cartridge 读取接收完整的 CPU 地址;遗留的 MemoryDevice 接收从零开始的偏移量;并且在整个 $8000-$FFFF 包含范围内始终需要一个已配置的数据源。mapper 路径之所以具有优先级,仅仅是因为第 1 部分禁止同时配置两种数据源,因此永远不会出现有歧义的合法构造。
另一个常见误解是把 Mapper000 的 16KB 取模规则复制进 CpuBus。$C000 镜像断言恰恰证明了委托机制是有效的,而不是意味着总线需要承担新的职责。
范围之外
1. PPU register reads at $2000-$3FFF belong to Chapter 3.
2. PRG and mapper writes are not added here.
3. PPU/CHR routing, timing, and later behavior must not be anticipated.运行本课
uv run pytest tests/chapter_02_rom_loading/test_222_cpu_bus_cartridge_integration_part2.py -v