268. Console.step() продвигает PPU

Заставить Console.step() продвигать время PPU на основе тактов CPU.

Урок 268 из 356 · tests/chapter_04_ppu_timing_and_vblank/test_268_console_step_advances_ppu.py

Справочные материалы

https://www.nesdev.org/wiki/CPU
https://www.nesdev.org/wiki/PPU_rendering#Line-by-line_timing

Файл для обновления

emulator/console.py

Зачем нужен этот шаг

В эмуляторе теперь есть части, необходимые для связи времени CPU со временем PPU

CPU.step() returns base CPU cycles
PPU.step(cycles) advances PPU timing counters
Console.consume_nmi_if_requested() connects PPU NMI requests to CPU NMI

Console.step() — это то место, где эти части становятся одним шагом на уровне машины.

Что такое шаг на уровне машины? Шаг на уровне машины означает совместное продвижение нескольких эмулируемых микросхем согласно их тактовой взаимосвязи.

Минимальное правило синхронизации NES

1 CPU cycle = 3 PPU cycles

Минимальный пример

CPU executes NOP
NOP takes 2 CPU cycles
PPU advances 2 * 3 = 6 PPU cycles

Распространённое заблуждение

Не заставляйте CPU.step() вызывать PPU.step(). CPU не должен владеть видеосинхронизацией. Console владеет координацией между микросхемами.

Пример предлагаемой реализации

@dataclass
class Console:
    cpu: CPU
    ppu: PPU

    def consume_nmi_if_requested(self) -> None:
        if not self.ppu.nmi_requested:
            return

        self.ppu.nmi_requested = False
        self.cpu.interrupt_nmi()

    def step(self) -> int:
        # This is base timing only. Later steps can add branch penalties,
        # NMI latency, DMA stalls, and other timing details.
        cpu_cycles = self.cpu.step()
        self.ppu.step(cpu_cycles * 3)
        self.consume_nmi_if_requested()
        return cpu_cycles

Важное ограничение

Это по-прежнему упрощённая синхронизация. CPU.step() сейчас возвращает только базовые такты.

Последующие улучшения могут учитывать

branch taken extra cycles
branch page-cross extra cycles
indexed addressing page-cross extra cycles
NMI latency
OAM DMA stalls

Выходит за рамки

  • динамические штрафы тактов CPU
  • задержка NMI с точностью до точки
  • OAM DMA
  • рендеринг пикселей
  • цикл pygame/фронтенда
  • ввод с контроллеров

Запустить этот урок

uv run pytest tests/chapter_04_ppu_timing_and_vblank/test_268_console_step_advances_ppu.py -v