268. Console step advances ppu

Make Console.step() advance PPU time from CPU cycles.

Lesson 268 of 356 · tests/chapter_04_ppu_timing_and_vblank/test_268_console_step_advances_ppu.py

References

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

File to update

emulator/console.py

Why this step exists

The emulator now has the pieces needed to connect CPU time to PPU time

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() is where those pieces become one machine-level step.

What is machine-level stepping? Machine-level stepping means advancing multiple emulated chips together according to their clock relationship.

Minimal NES timing rule

1 CPU cycle = 3 PPU cycles

Minimal example

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

Common misconception

Do not make CPU.step() call PPU.step(). The CPU should not own video timing. Console owns the coordination between chips.

Suggested implementation example

@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

Important limitation

This is still simplified timing. CPU.step() currently returns base cycles only.

Later improvements can account for

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

Out of scope

  • dynamic CPU cycle penalties
  • dot-accurate NMI latency
  • OAM DMA
  • rendering pixels
  • pygame/frontend loop
  • controller input

Run this lesson

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