265. La consola consume la solicitud NMI de la PPU

Modelar Console como el coordinador que consume las solicitudes NMI de la PPU.

Lección 265 de 356 · tests/chapter_04_ppu_timing_and_vblank/test_265_console_consumes_ppu_nmi_request.py

Referencias

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

Archivo a crear

emulator/console.py

Por qué existe este paso

El emulador ahora tiene los dos mecanismos separados necesarios para el NMI de 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

Ahora necesitamos un pequeño coordinador que conecte esos mecanismos sin hacer que la CPU y la PPU dependan directamente entre sí.

¿Qué es Console? Console es la futura máquina NES emulada de nivel superior. Con el tiempo puede poseer y coordinar subsistemas como:

CPU
PPU
cartridge/mapper
controllers
APU/audio
frame stepping

En esta etapa, Console comienza siendo muy pequeño. Su primer trabajo es solo consumir una solicitud NMI de la PPU y llamar a CPU.interrupt_nmi().

Separación correcta de responsabilidades

PPU:
    produces nmi_requested

CPU:
    implements interrupt_nmi()

Console:
    connects the PPU signal to the CPU mechanism

Regla de arquitectura importante

No pongas la propiedad de la PPU dentro de la CPU. La CPU no debería preguntar a la PPU si hay una NMI pendiente. Eso crearía un acoplamiento oculto entre la ejecución de la CPU y el hardware de vídeo.

Ejemplo de implementación mínimo

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

Forma futura, introducida en un paso de temporización posterior

class Console:
    ...

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

Este archivo no prueba Console.step(). La integración de temporización CPU/PPU se prueba como un paso separado después de que CPU.step() devuelva ciclos de instrucción.

Concepto erróneo común

Dado que la PPU causa NMI, puede parecer natural que la CPU posea la PPU. Evita eso. La CPU recibe señales de interrupción; no debería poseer el dispositivo de vídeo.

Fuera de alcance

  • Ratio de ciclos CPU/PPU
  • Console.step()
  • renderizado
  • controladores
  • APU/audio
  • latencia exacta de NMI

Ejecutar esta lección

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