240. Ppuaddr 的第二次写入切换

实现 PPUADDR($2006)的两次写入地址行为。

240 / 356 · tests/chapter_03_ppu_memory_and_graphics_data/test_240_ppuaddr_second_write_toggle.py

参考资料

https://www.nesdev.org/wiki/PPU_registers#PPUADDR

要更新的文件

emulator/ppu/ppu.py

要添加的状态

vram_addr: int = 0
temp_vram_addr: int = 0
second_write_toggle: bool = False

为什么需要这一步

PPUADDR 是位于 $2006 的 CPU 可见寄存器。CPU 用它来设置 PPU 内部的 VRAM 地址。之后 PPUDATA($2007)会使用该内部地址来读写 PPU 内存。

重要概念

$2006 不只是一个简单的单字节存储寄存器,它是一个两次写入的端口。

CPU 分两部分写入地址

first write  -> high byte
second write -> low byte

示例

write $20 to $2006
write $00 to $2006

两次写入之后的结果

vram_addr == $2000

重要的内部寄存器模型

第一次写入 $2006 会更新 temp_vram_addr,而不是 vram_addr。第二次写入 $2006 会完成 temp_vram_addr 的构建,并将其复制到 vram_addr 中。

为什么需要 second_write_toggle:PPU 必须记住下一次对 $2006 的写入是第一次写入还是第二次写入。在本教程中,second_write_toggle 表示:

False -> next $2006 write is the first write / high byte
True  -> next $2006 write is the second write / low byte

为什么高字节要用 0x3F 掩码

PPU 地址空间是 14 位的

$0000-$3FFF

对于这个地址范围,高字节只有低 6 位是有用的。所以第一次写入应使用:

(value & 0x3F) << 8

建议实现的伪代码

@dataclass
class PPU:
    ...
    addr: int = 0
    vram_addr: int = 0
    temp_vram_addr: int = 0
    second_write_toggle: bool = False

    def write_register(self, addr: int, value: int) -> None:
        value = value & 0xFF

        match addr:
            ...
            case 0x2006:
                # Keep old simple register-field behavior for test compatibility.
                self.addr = value

                if not self.second_write_toggle:
                    self.temp_vram_addr = (
                        (self.temp_vram_addr & 0x00FF)
                        | ((value & 0x3F) << 8)
                    )
                    self.second_write_toggle = True
                else:
                    self.temp_vram_addr = (
                        (self.temp_vram_addr & 0x3F00)
                        | value
                    )
                    self.vram_addr = self.temp_vram_addr
                    self.second_write_toggle = False

            ...

不在本步范围内

  • 通过 PpuBus 进行的 PPUDATA($2007)写入
  • PPUSTATUS($2002)重置写入切换标志
  • PPUSCROLL($2005)使用同一个切换标志
  • PPUCTRL 带来的按 32 递增行为

运行本课

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