241. Ppustatus 读取会重置第二次写入开关

读取 PPUSTATUS ($2002) 会重置 PPU 的第二次写入开关。

241 / 356 · tests/chapter_03_ppu_memory_and_graphics_data/test_241_ppustatus_read_resets_second_write_toggle.py

要更新的文件

emulator/ppu/ppu.py

要更新的方法

PPU.read_register(addr)

为什么需要这一步

NES PPU 有一些需要写入两次的寄存器,尤其是

$2005 PPUSCROLL
$2006 PPUADDR

这些寄存器使用一个内部的第一次写入/第二次写入开关。在本教程中,该状态被命名为:

second_write_toggle

含义

False -> the next two-write register access is the first write
True  -> the next two-write register access is the second write

重要的 PPUSTATUS 行为

读取 $2002 会将该开关重置回 False。

原因

游戏通常会在写入 PPUSCROLL 或 PPUADDR 之前先读取 PPUSTATUS,以确保 PPU 重新处于等待第一次写入的状态。

示例

write $23 to $2006
    second_write_toggle = True

read $2002
    second_write_toggle = False

write $20 to $2006
    treated as first write / high byte, not as low byte

建议的实现伪代码

def read_register(self, addr: int) -> int:
    match addr:
        case 0x2002:
            value = self.status
            self.status &= ~VBLANK_STARTED
            self.second_write_toggle = False
            return value

        case 0x2004:
            return self.oam_data

        case 0x2007:
            return self.data

        case _:
            raise ValueError(...)

重要不变量

读取 $2002 应当先返回旧的状态值,然后再施加副作用。

当前已建模的副作用

  • 清除 VBLANK_STARTED
  • 重置 second_write_toggle

尚未建模的副作用

  • 完整的滚动锁存行为
  • 时序/VBlank 生成
  • NMI 行为

运行本课

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