266. 操作码周期表

在修改 CPU.step() 之前,先验证操作码的周期元数据。

266 / 356 · tests/chapter_04_ppu_timing_and_vblank/test_266_opcode_cycles_table.py

参考资料

https://www.nesdev.org/wiki/Visual6502wiki/6502_all_256_Opcodes

需要更新的文件

emulator/cpu/opcodes.py

这个中间步骤存在的原因

模拟器已经通过以下方式实现了可用的操作码分发

OPCODE_TABLE[opcode] -> handler(cpu)

我们暂时不想重构那张表,因为 CPU 章节前面的旧测试已经依赖于当前的指令执行路径。

相反,这一步把时序作为独立的元数据引入

OPCODE_CYCLES[opcode] -> base CPU cycle count

这样一来,未来的 CPU.step() 就可以返回周期数,而不需要修改每一个指令处理函数,也不需要替换现有的操作码分发表。

建议的实现示例

把这张表复制到 emulator/cpu/opcodes.py 中。它是根据上面链接的操作码时序参考资料整理出来的:

OPCODE_CYCLES = [

7,6,0,8,3,3,5,5,3,2,2,2,4,4,6,6,
3,5,0,8,4,4,6,6,2,4,2,7,4,4,7,7,
6,6,0,8,3,3,5,5,4,2,2,2,4,4,6,6,
2,5,0,8,4,4,6,6,2,4,2,7,4,4,7,7,
6,6,0,8,3,3,5,5,3,2,2,2,3,4,6,6,
3,5,0,8,4,4,6,6,2,4,2,7,4,4,7,7,
6,6,0,8,3,3,5,5,4,2,2,2,5,4,6,6,
2,5,0,8,4,4,6,6,2,4,2,7,4,4,7,7,
2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4,
3,6,0,6,4,4,4,4,2,5,2,5,5,5,5,5,
2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4,
2,5,0,5,4,4,4,4,2,4,2,4,4,4,4,4,
2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,6,
3,5,0,8,4,4,6,6,2,4,2,7,4,4,7,7,
2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,6,
2,5,0,8,4,4,6,6,2,4,2,7,4,4,7,7

]

重要修正

第一个值必须是 7,因为操作码 $00 是 BRK,而 BRK 需要 7 个 CPU 周期。如果你复制的表中索引 $00 处是 0,请把它改为 7,因为本模拟器已经实现了 BRK。

什么是操作码周期表?操作码周期表是一张包含 256 个条目的查找表,其中每个索引是一个操作码字节,每个值是该操作码所需的基本 CPU 周期数。

最小示例

OPCODE_CYCLES[0xEA] == 2   # NOP implied
OPCODE_CYCLES[0xA9] == 2   # LDA immediate
OPCODE_CYCLES[0x20] == 6   # JSR absolute

常见误解

周期数与指令长度或取指次数并不是一回事。例如,JSR 占 3 个字节,但需要 6 个 CPU 周期。

这在模拟器中的体现方式

Current step shape:
    opcode = cpu.fetch_byte()
    handler = OPCODE_TABLE[opcode]
    handler(cpu)

Future step shape, without refactoring OPCODE_TABLE:
    opcode = cpu.fetch_byte()
    handler = OPCODE_TABLE[opcode]
    handler(cpu)
    return OPCODE_CYCLES[opcode]

重要限制

这些只是基本周期数。有些指令之后还需要动态的额外周期,例如:

branch taken
branch crosses page
indexed load crosses page

不要在这一步解决这些动态惩罚周期的问题。先让基础的时序元数据变得明确且可测试。

不在本步骤范围内

  • 修改 CPU.step() 使其返回周期数
  • 把 OPCODE_TABLE 的条目重构为数据类
  • 修改旧的 CPU 指令测试
  • 动态的跨页或分支周期惩罚
  • Console.step() 按 CPU 周期数 * 3 来推进 PPU

运行本课

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