223. 验证 CPU 执行微小生成的 iNES ROM

验证测试:CPU 执行一个微小的生成 iNES ROM。

223 / 356 · tests/chapter_02_rom_loading/test_223_VALIDATION_cpu_executes_tiny_ines_rom.py

这不是一个学生实现步骤。

受验证的前置条件

- this file: make_tiny_ines_rom and
  test_cpu_executes_tiny_generated_ines_rom_from_reset_vector
- emulator/cartridge/ines.py: PRG_ROM_BANK_SIZE, CHR_ROM_BANK_SIZE,
  parse_ines_header, and parse_ines_rom
- emulator/cartridge/cartridge.py: Cartridge.from_ines_bytes
- emulator/cartridge/mapper_factory.py: create_mapper
- emulator/cartridge/mapper000.py: Mapper000.read_prg
- emulator/bus/cpu_bus.py: CpuBus.__post_init__ and CpuBus.read
- emulator/cpu/cpu.py: CPU.reset, CPU.fetch_byte, and CPU.step
- emulator/cpu/opcodes.py: OPCODE_TABLE entry $A9
- emulator/cpu/instructions.py: lda

验证流程

1. make_tiny_ines_rom builds a 16-byte iNES header declaring mapper 0, one
   16KB PRG bank, one 8KB CHR bank, and no trainer.
2. It writes A9 42 (LDA #$42) and EA (NOP) at PRG offsets $0000-$0002,
   corresponding to CPU $8000-$8002.
3. It writes reset-vector bytes 00 80 at PRG offsets $3FFC-$3FFD. Mapper000's
   16KB mirror makes CPU reads $FFFC-$FFFD observe those bytes.
4. Cartridge.from_ines_bytes parses and slices the generated bytes.
5. CpuBus(cartridge=cartridge) creates Mapper000 through create_mapper.
6. CPU.reset reads the vector through CpuBus and Mapper000, producing
   PC=$8000.
7. One CPU.step fetches $A9 through the same path, dispatches lda, fetches
   operand $42, and leaves A=$42 and PC=$8002. The trailing NOP is fixture
   data and is not executed by this test.

使用以下命令精确运行此验证

pytest -q tests/chapter_02_rom_loading/test_223_VALIDATION_cpu_executes_tiny_ines_rom.py

受验证的完整边界是

iNES bytes
    -> Cartridge.from_ines_bytes(data)
    -> CpuBus(cartridge=cartridge)
    -> create_mapper(cartridge)
    -> Mapper000
    -> CPU.reset()
    -> CPU.step()

该步骤存在的原因

之所以存在验证,是因为单元测试可以单独证明每一部分,但缺陷仍可能出现在组件之间的边界上。此测试不添加任何生产实现;它证明第 212-222 课的内容能够协同工作,使 CPU 能够通过 reset 向量从 cartridge 支持的 PRG ROM 中取指并执行指令。

该 ROM 是在测试内部生成的。它被刻意设计得非常小,不使用 PPU、APU、控制器、中断或当前模拟器范围之外的任何功能。

放置在 CPU 地址 $8000 处的微小程序:

A9 42    LDA #$42
EA       NOP

Reset 向量

CPU $FFFC = $00
CPU $FFFD = $80

因此在 reset 之后

PC = $8000

重要的 Mapper000 细节

对于一个 16KB 的 NROM cartridge,CPU 地址 $FFFC-$FFFD 会映射到 PRG ROM 偏移 $3FFC-$3FFD,因为 $C000-$FFFF 镜像了这 16KB 的 PRG 存储体。

不变量

iNES 数据载荷是完整且已加载到内存中的;mapper 编号和存储体大小在解析后保持不变;reset 向量的读取以及操作码/操作数的取值都使用 cartridge 支持的 PRG 路由;reset 会选中 $8000;并且一条指令恰好消耗两个字节。一个常见误解是像 32KB/FakeROM 布局那样把向量放在偏移 $7FFC 处,或者认为只执行了一步就断定 NOP 已经通过验证。

范围之外

不要为这次验证添加生产代码。PPU 的构造/寄存器路由(即将到来的第 3 章过渡)、APU、控制器、中断、时序、PRG 写入、CHR/PPU 读取、外部 ROM 文件以及后续的主机/PPU 集成在此均不涉及,也不做任何暗示。

运行本课

uv run pytest tests/chapter_02_rom_loading/test_223_VALIDATION_cpu_executes_tiny_ines_rom.py -v