289. $4014 处的 OAMDMA
在 CPU 地址 $4014 处实现 OAMDMA。
第 289 / 356 · tests/chapter_06_rom_startup_preparation/test_289_oamdma_4014.py
待更新文件
emulator/bus/cpu_bus.py为什么需要这一步
真实的 NES 游戏通常会先在 CPU 内存中准备好精灵字节,然后触发 OAMDMA 把这些字节复制到 PPU OAM 中。即使在还没有实现精灵渲染的情况下,这个复制机制也很重要,因为它能让真实 ROM 的启动代码继续运行,并为未来的精灵渲染器提供真实的 OAM 数据。
什么是 OAM?OAM 指对象属性内存(Object Attribute Memory),它是 PPU 中 256 字节的精灵内存:
64 sprites * 4 bytes each = 256 bytes每个精灵条目的形态是
byte 0: Y position
byte 1: tile index
byte 2: attributes
byte 3: X position什么是 OAMDMA?OAMDMA 是一种 CPU 总线写入机制。向 $4014 写入一个字节会选中一个 CPU 内存页,并把该页中全部 256 个字节复制到 PPU OAM 中。
最小示例
CPU writes $02 to $4014这意味着
copy CPU $0200-$02FF -> PPU.oam[0x00-0xFF]建议的实现示例
def write(self, addr: int, value: int) -> None:
...
# OAMDMA: copy one CPU page into PPU OAM.
if addr == 0x4014:
page_start = (value & 0xFF) << 8
for offset in range(256):
self.ppu.oam[offset] = self.read(page_start + offset)
return
...重要细节
OAMDMA 应该通过 CpuBus.read() 读取,而不是直接读原始 RAM。数据源是 CPU 地址空间。大多数游戏使用像 $0200-$02FF 这样的页,但使用总线读取可以保证机制正确,并避免以后的重构。
常见的误解
"OAMDMA means sprites are rendered."不是。这一步只是把字节复制到 PPU.oam 中,精灵的解码/渲染留待以后实现。
读取行为
在本教程中,$4014 是一个仅由写入触发的寄存器。CpuBus.read($4014) 目前应保持不支持。
范围之外
- 精灵渲染
- 精灵优先级
- 精灵透明度
- 精灵 0 命中
- 精灵溢出
- 513/514 个 CPU 周期的 DMA 停顿时序
- 手柄 $4016
运行本课
uv run pytest tests/chapter_06_rom_startup_preparation/test_289_oamdma_4014.py -v