265. Console consumes ppu nmi request

Model Console as the coordinator that consumes PPU NMI requests.

Lesson 265 of 356 · tests/chapter_04_ppu_timing_and_vblank/test_265_console_consumes_ppu_nmi_request.py

References

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

File to create

emulator/console.py

Why this step exists

The emulator now has the two separate mechanisms needed for VBlank NMI

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

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

Now we need a small coordinator that connects those mechanisms without making CPU and PPU depend directly on each other.

What is Console? Console is the future top-level emulated NES machine. Over time it can own and coordinate subsystems such as:

CPU
PPU
cartridge/mapper
controllers
APU/audio
frame stepping

At this stage, Console starts very small. Its first job is only to consume a PPU NMI request and call CPU.interrupt_nmi().

Correct responsibility split

PPU:
    produces nmi_requested

CPU:
    implements interrupt_nmi()

Console:
    connects the PPU signal to the CPU mechanism

Important architecture rule

Do not put PPU ownership inside CPU. The CPU should not ask the PPU whether an NMI is pending. That would create hidden coupling between CPU execution and video hardware.

Minimal implementation example

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

Future shape, introduced in a later timing test

class Console:
    ...

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

This file does not test Console.step(). CPU/PPU timing integration is tested as a separate step after CPU.step() returns instruction cycles.

Common misconception

Because the PPU causes NMI, it may feel natural to make CPU own PPU. Avoid that. The CPU receives interrupt signals; it should not own the video device.

Out of scope

  • CPU/PPU cycle ratio
  • Console.step()
  • rendering
  • controllers
  • APU/audio
  • exact NMI latency

Run this lesson

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