249. Ppudata 读取缓冲区
实现 PPUDATA ($2007) 的缓冲读取行为。
第 249 / 356 · tests/chapter_03_ppu_memory_and_graphics_data/test_249_ppudata_read_buffer.py
参考资料
https://www.nesdev.org/wiki/PPU_registers#PPUDATA要更新的文件
emulator/ppu/ppu.py要添加的状态
ppu_data_buffer: int = 0为什么需要这一步
PPUDATA 不是普通的简单寄存器。它是一个通向 PPU 内存的 CPU 可见端口。对于大多数 PPU 内存读取,NES 会返回旧的内部读取缓冲区,然后再从当前的 PPU 内存地址重新加载该缓冲区。
正常的缓冲读取行为
read $2007:
value = ppu_data_buffer
ppu_data_buffer = ppu_bus.read(vram_addr)
vram_addr += increment
return value这意味着第一次读取通常返回的是过时/陈旧的缓冲区数据,而第二次读取返回的是由第一次读取所加载的那个字节。
示例
ppu_bus[$2000] = $AA
ppu_bus[$2001] = $BB
ppu_data_buffer = $00
vram_addr = $2000
read $2007 -> returns $00, buffer becomes $AA, vram_addr becomes $2001
read $2007 -> returns $AA, buffer becomes $BB, vram_addr becomes $2002兼容/调试字段
较早的教程模型曾把 data 当作一个简单的 PPUDATA 值。请把 self.data 保留为一个兼容/调试字段,其中包含最近一次
PPUDATA 读取所返回的值,但真实的行为应当使用
ppu_data_buffer
ppu_bus.read(vram_addr)建议的实现伪代码
@dataclass
class PPU:
...
data: int = 0 # Compatibility/debug: last PPUDATA value
ppu_data_buffer: int = 0
def read_register(self, addr: int) -> int:
match addr:
...
case 0x2007:
value = self.ppu_data_buffer
self.ppu_data_buffer = self.ppu_bus.read(self.vram_addr)
increment = 32 if self.ctrl & CTRL_VRAM_INCREMENT_BY_32 else 1
self.vram_addr = (self.vram_addr + increment) & 0x3FFF
# Preserve old compatibility/debug field.
self.data = value
return self.data关于未来的重要说明
在真实硬件上,从 $3F00-$3FFF 进行的调色板读取是一个例外。这类读取会立即返回调色板字节,而不是返回延迟的缓冲区值。本测试有意不实现该例外。
超出范围
- 调色板读取例外
- 调色板 RAM 精度
- PPUDATA 写入,此前已测试过
运行本课
uv run pytest tests/chapter_03_ppu_memory_and_graphics_data/test_249_ppudata_read_buffer.py -v