232. 读取 Ppu 状态会清除 vblank

读取 PPUSTATUS 会清除 VBlank 标志。

232 / 356 · tests/chapter_03_ppu_memory_and_graphics_data/test_232_ppu_status_read_clears_vblank.py

要更新的文件

emulator/ppu/ppu.py

要更新的方法

PPU.read_register(addr)

为什么需要这一步

位于 $2002 的 PPUSTATUS 并非一个被动的值。在真实的 NES 硬件上,读取 PPUSTATUS 会返回当前状态字节,并清除 PPU 内部的 VBlank-started 标志。

重要行为

read_register($2002):
    1. save the old status value
    2. clear only VBLANK_STARTED, bit 7
    3. return the old status value

顺序很重要。

正确做法

value = self.status
self.status &= ~VBLANK_STARTED
return value

错误做法

self.status &= ~VBLANK_STARTED
return self.status

为什么是错误的

CPU 必须能够观察到 VBlank 已被置位。如果模拟器在返回之前就清除了第 7 位,CPU 的轮询循环将会错过 VBlank。

位级示例

status before read:      0b1110_0000
~VBLANK_STARTED:         0b0111_1111
status after AND:        0b0110_0000

只有第 7 位会被清除。sprite 0 命中和精灵溢出标志保持不变。

建议实现的伪代码

def read_register(self, addr: int) -> int:
    match addr:
        case 0x2002:
            value = self.status
            self.status &= ~VBLANK_STARTED
            return value
        case 0x2004:
            return self.oam_data
        case 0x2007:
            return self.data
        case _:
            raise ValueError(...)

不在本步范围内

  • 根据 PPU 时序自动设置 VBlank
  • NMI 的产生
  • 扫描线/周期
  • sprite 0 命中行为
  • 精灵溢出行为

运行本课

uv run pytest tests/chapter_03_ppu_memory_and_graphics_data/test_232_ppu_status_read_clears_vblank.py -v