262. CPU 栈辅助函数与标志位导入

围绕共享标志位和栈辅助函数重构 CPU 的中断支持。

262 / 356 · tests/chapter_04_ppu_timing_and_vblank/test_262_cpu_stack_helpers_and_flag_imports.py

需要更新的文件

emulator/cpu/cpu.py
emulator/cpu/flags_handler.py

此步骤存在的原因

在实现并测试 CPU.interrupt_nmi() 之前,CPU 核心需要先完成两处小的清理工作:

1. CPU should import status flag constants from flags_handler.py.
2. CPU should expose stack helpers for interrupt code:
    push_stack(value)
    pop_stack() -> int

这是一次重构/辅助函数的准备步骤,还不是 NMI 中断行为本身的测试。

重要背景

早期的 CPU 测试常常各自声明局部的标志位常量。在 CPU 章节逐步搭建的阶段,这样做没有问题。但现在中断需要正确处理状态字节,共享的唯一可信来源应该是:

emulator/cpu/flags_handler.py

正确的状态位布局

CARRY_FLAG      = 1 << 0
ZERO_FLAG       = 1 << 1
INTERRUPT_FLAG  = 1 << 2
DECIMAL_FLAG    = 1 << 3
B_FLAG          = 1 << 4
ONE_FLAG        = 1 << 5
OVERFLOW_FLAG   = 1 << 6
NEGATIVE_FLAG   = 1 << 7

栈操作规则

push:
    write to $0100 | S
    decrement S

pop/pull:
    increment S
    read from $0100 | S

建议的实现示例

from emulator.cpu.flags_handler import (
    B_FLAG,
    INTERRUPT_FLAG,
    NEGATIVE_FLAG,
    ONE_FLAG,
    ZERO_FLAG,
)

STACK_BASE = 0x0100

class CPU:
    ...

    def push_stack(self, value: int) -> None:
        self.bus.write(STACK_BASE | self.s, value & 0xFF)
        self.s = (self.s - 1) & 0xFF

    def pop_stack(self) -> int:
        self.s = (self.s + 1) & 0xFF
        return self.bus.read(STACK_BASE | self.s)

为什么现在要加入 pop_stack:CPU.interrupt_nmi() 本身只需要 push_stack,但配套的测试以及后续 CPU 中断相关的清理工作也需要一个清晰的出栈辅助函数。把这两个辅助函数一起加入,可以在测试 NMI 行为之前就明确栈的不变量。

不在本步骤范围内

  • CPU.interrupt_nmi() 的行为
  • 消费 PPU 的 NMI 请求
  • IRQ/APU/映射器中断
  • 把所有旧的栈相关指令都重构为使用这些辅助函数

运行本课

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