264. CPU 的 NMI 中断
实现 CPU 端的 NMI 中断机制。
第 264 / 356 · tests/chapter_04_ppu_timing_and_vblank/test_264_cpu_interrupt_nmi.py
参考资料
https://www.nesdev.org/wiki/CPU_interrupts
https://www.nesdev.org/wiki/PPU_registers#Vblank_NMI需要更新的文件
emulator/cpu/cpu.py此步骤存在的原因
PPU 已经可以产生 nmi_requested 信号。在通过系统/主机协调器把这个信号接入 CPU 之前,CPU 本身必须先知道如何独立完成 NMI 序列。
什么是 NMI?NMI 意为不可屏蔽中断(Non-Maskable Interrupt)。在 NES 上,PPU 可以在 VBlank 时请求 NMI,从而让游戏代码运行其垂直消隐处理程序。
直观模型
NMI 就像一次硬件级的紧急跳转。CPU 暂停当前的执行路径,保存足够的状态以便之后返回,然后跳转到 NMI 向量中保存的地址。
机制模型
当 NMI 被接受时,CPU 会执行以下序列
1. Push PC high byte
2. Push PC low byte
3. Push status with:
ONE_FLAG set
B_FLAG clear
4. Set INTERRUPT_FLAG in CPU status
5. Read low byte from $FFFA
6. Read high byte from $FFFB
7. Set PC = high << 8 | low重要区别
NMI 不会写入 $FFFA/$FFFB。CPU 只是读取这两个地址。在真实的 ROM 中,向量字节本来就存在于 PRG ROM 中。在这些测试里,FakeROM 让我们可以把这些字节作为测试搭建的一部分预先准备好。
实现示例
NMI_VECTOR_LOW = 0xFFFA
NMI_VECTOR_HIGH = 0xFFFB
class CPU:
...
def interrupt_nmi(self) -> None:
# Save the current PC so RTI can restore it later.
pc_high = (self.pc >> 8) & 0xFF
pc_low = self.pc & 0xFF
self.push_stack(pc_high)
self.push_stack(pc_low)
# Hardware interrupts push status with B clear and bit 5 set.
status_to_push = self.p | ONE_FLAG
status_to_push &= ~B_FLAG
self.push_stack(status_to_push)
# After accepting an interrupt, set the interrupt-disable flag.
self.p |= INTERRUPT_FLAG
# NMI vector bytes are read from PRG space. NMI does not write them.
low = self.bus.read(NMI_VECTOR_LOW)
high = self.bus.read(NMI_VECTOR_HIGH)
self.pc = low | (high << 8)具体的运行时示例
FakeROM setup:
$FFFA = $00
$FFFB = $C0
CPU before NMI:
PC = $8123
S = $FD
CPU.interrupt_nmi()
CPU after NMI:
stack contains return PC/status
PC = $C000以 S = $FD、PC = $8123 为例的栈示例:
write $81 to $01FD, S becomes $FC
write $23 to $01FC, S becomes $FB
write status to $01FB, S becomes $FA常见误解
硬件中断不会设置 B 标志位。BRK/PHP 压栈时状态字节会设置 B,但 NMI 压栈时状态字节的 B 是清零的。压入状态字节中的第 5 位 ONE_FLAG 仍然会被置位。
不在本步骤范围内
- 由 PPU 调用 CPU.interrupt_nmi()
- 清除 ppu.nmi_requested
- 精确的中断延迟/周期数
- IRQ/APU/映射器中断
- RTI 行为,这在 CPU 章节前面已经测试过
运行本课
uv run pytest tests/chapter_04_ppu_timing_and_vblank/test_264_cpu_interrupt_nmi.py -v