265. Console 消费 PPU 的 NMI 请求
把 Console 建模为消费 PPU NMI 请求的协调者。
第 265 / 356 · tests/chapter_04_ppu_timing_and_vblank/test_265_console_consumes_ppu_nmi_request.py
参考资料
https://www.nesdev.org/wiki/CPU_interrupts
https://www.nesdev.org/wiki/PPU_registers#Vblank_NMI需要创建的文件
emulator/console.py此步骤存在的原因
模拟器现在已经具备了实现 VBlank NMI 所需的两个独立机制
PPU mechanism:
PPU enters VBlank and sets ppu.nmi_requested = True
CPU mechanism:
CPU.interrupt_nmi() pushes PC/status and jumps through $FFFA/$FFFB现在我们需要一个小型协调器,把这两个机制连接起来,同时不让 CPU 和 PPU 直接相互依赖。
什么是 Console?Console 是未来最顶层的模拟 NES 主机。随着时间推移,它可以拥有并协调如下子系统:
CPU
PPU
cartridge/mapper
controllers
APU/audio
frame stepping在当前阶段,Console 从很小的规模开始。它的第一个任务只是消费一次 PPU 的 NMI 请求,并调用 CPU.interrupt_nmi()。
正确的职责划分
PPU:
produces nmi_requested
CPU:
implements interrupt_nmi()
Console:
connects the PPU signal to the CPU mechanism重要的架构规则
不要把 PPU 的所有权放进 CPU 里。CPU 不应该去询问 PPU 是否有 NMI 待处理,那会在 CPU 执行和视频硬件之间造成隐藏的耦合。
最小实现示例
from dataclasses import dataclass
from emulator.cpu.cpu import CPU
from emulator.ppu.ppu import PPU
@dataclass
class Console:
cpu: CPU
ppu: PPU
def consume_nmi_if_requested(self) -> None:
if not self.ppu.nmi_requested:
return
self.ppu.nmi_requested = False
self.cpu.interrupt_nmi()在之后的时序测试中会引入的未来形态
class Console:
...
def step(self) -> None:
cpu_cycles = self.cpu.step()
self.ppu.step(cpu_cycles * 3)
self.consume_nmi_if_requested()本文件不测试 Console.step()。CPU/PPU 的时序集成会在 CPU.step() 返回指令周期数之后,作为单独的一步进行测试。
常见误解
由于 NMI 是由 PPU 引起的,可能会觉得让 CPU 拥有 PPU 是很自然的做法。请避免这样做。CPU 只是接收中断信号,不应该拥有视频设备。
不在本步骤范围内
- CPU/PPU 的周期比例
- Console.step()
- 渲染
- 手柄
- APU/音频
- 精确的 NMI 延迟
运行本课
uv run pytest tests/chapter_04_ppu_timing_and_vblank/test_265_console_consumes_ppu_nmi_request.py -v