265. Console обрабатывает запрос NMI от PPU

Смоделировать Console как координатор, обрабатывающий запросы NMI от PPU.

Урок 265 из 356 · tests/chapter_04_ppu_timing_and_vblank/test_265_console_consumes_ppu_nmi_request.py

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

https://www.nesdev.org/wiki/CPU_interrupts
https://www.nesdev.org/wiki/PPU_registers#Vblank_NMI

Файл для создания

emulator/console.py

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

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

PPU mechanism:
    PPU enters VBlank and sets ppu.nmi_requested = True

CPU mechanism:
    CPU.interrupt_nmi() pushes PC/status and jumps through $FFFA/$FFFB

Теперь нужен небольшой координатор, который соединит эти механизмы, не заставляя CPU и PPU напрямую зависеть друг от друга.

Что такое Console? Console — это будущая эмулируемая машина NES верхнего уровня. Со временем она сможет владеть и координировать такие подсистемы, как:

CPU
PPU
cartridge/mapper
controllers
APU/audio
frame stepping

На этом этапе Console начинается очень маленькой. Её первая задача — только обработать запрос NMI от PPU и вызвать CPU.interrupt_nmi().

Корректное разделение ответственности

PPU:
    produces nmi_requested

CPU:
    implements interrupt_nmi()

Console:
    connects the PPU signal to the CPU mechanism

Важное архитектурное правило

Не помещайте владение PPU внутрь CPU. CPU не должен спрашивать у PPU, есть ли ожидающий NMI. Это создало бы скрытую связанность между выполнением CPU и видеоаппаратурой.

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

from dataclasses import dataclass

from emulator.cpu.cpu import CPU
from emulator.ppu.ppu import PPU


@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()

Будущий вид, вводится в одном из последующих тестов синхронизации

class Console:
    ...

    def step(self) -> None:
        cpu_cycles = self.cpu.step()
        self.ppu.step(cpu_cycles * 3)
        self.consume_nmi_if_requested()

Этот файл не тестирует Console.step(). Интеграция синхронизации CPU/PPU тестируется отдельным шагом после того, как CPU.step() возвращает такты инструкции.

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

Поскольку NMI исходит от PPU, может показаться естественным сделать PPU собственностью CPU. Избегайте этого. CPU получает сигналы прерываний; он не должен владеть видеоустройством.

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

  • соотношение тактов CPU/PPU
  • Console.step()
  • рендеринг
  • контроллеры
  • APU/звук
  • точная задержка NMI

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

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