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