288. APU 音频寄存器空操作

在 CpuBus 上为 APU/音频寄存器添加明确的空操作行为。

288 / 356 · tests/chapter_06_rom_startup_preparation/test_288_apu_audio_register_noop.py

待更新文件

emulator/bus/cpu_bus.py

为什么需要这一步

真实的 NES ROM 在启动期间通常会访问 APU/音频寄存器。音频超出了本教程当前的范围,但如果每次访问音频寄存器都崩溃,手动 ROM 实验就会在我们能够观察到 CPU/PPU/手柄行为之前提前停止。

这一步教授的是有意为之的空操作

recognized address + documented out-of-scope behavior

而不是一套广泛的伪硬件实现。

什么是 APU?APU,即音频处理单元,是 NES 中负责声音生成的硬件模块。CPU 通过内存映射寄存器控制它。

最小示例

CPU writes $4000
    real NES: configure pulse channel audio
    this tutorial for now: accept the write and produce no sound

常见的误解

"If the emulator accepts APU writes, APU is implemented."

不是。在这一步中,APU/音频被明确视为超出范围。模拟器只是避免在这些地址上崩溃。

建议的实现示例

def read(self, addr: int) -> int:
    ...

    # APU/audio registers are intentionally out of scope.
    if 0x4000 <= addr <= 0x4013:
        return 0
    if addr == 0x4015:
        return 0

    # Controller port 2 / expansion input is also out of scope for now.
    # Returning 0 means "no controller-2 buttons pressed" in this simplified model.
    if addr == 0x4017:
        return 0

    ...

def write(self, addr: int, value: int) -> None:
    ...

    # APU/audio registers are intentionally out of scope.
    if 0x4000 <= addr <= 0x4013:
        return
    if addr == 0x4015:
        return

    # $4017 writes control the APU frame counter on the NES.
    # Audio/APU timing is intentionally out of scope, so this is a no-op.
    if addr == 0x4017:
        return

    ...

为什么是这些地址

$4000-$4013
    APU sound-channel registers

$4015
    APU status/control register

$4017
    Writes: APU frame counter register, intentionally no-op for now.
    Reads: controller port 2 / expansion input, intentionally returns 0 for now.

重要的排除项

$4014 is OAMDMA, not audio.
    Writing $02 to $4014 should later copy CPU $0200-$02FF into PPU OAM.
    Do not swallow it as an APU no-op.

$4016 is controller port 1, not audio.
    It will be implemented intentionally in the controller chapter.
    Do not fake it by returning 0 here.

范围之外

  • 实际的声音生成
  • APU 定时器/包络/扫频/长度计数器
  • IRQ/帧计数器时序
  • OAMDMA $4014
  • 手柄 $4016
  • 从 $4017 读取的手柄端口 2/扩展输入
  • 对所有不支持的 I/O 地址进行广泛的兜底处理

运行本课

uv run pytest tests/chapter_06_rom_startup_preparation/test_288_apu_audio_register_noop.py -v