260. Ppu vblank generation from timing

Implement VBlank generation from PPU timing.

Lesson 260 of 356 · tests/chapter_04_ppu_timing_and_vblank/test_260_ppu_vblank_generation_from_timing.py

Reference

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

File to update

emulator/ppu/ppu.py

Constants to add

PPU_VBLANK_START_SCANLINE = 241
PPU_PRE_RENDER_SCANLINE = 261

Why this step exists

Many NES programs wait for VBlank before writing PPU memory. They commonly poll PPUSTATUS ($2002) until bit 7 becomes set.

Conceptual behavior for this tutorial step

when the PPU enters scanline 241:
    set PPUSTATUS bit 7, VBLANK_STARTED

when the PPU enters scanline 261, the pre-render scanline:
    clear PPUSTATUS bit 7

This gives ROMs a basic frame signal without implementing full rendering yet.

Suggested implementation example

PPU_VBLANK_START_SCANLINE = 241
PPU_PRE_RENDER_SCANLINE = 261

def step(self, cycles: int = 1) -> None:
    for _ in range(cycles):
        self.cycle += 1

        if self.cycle >= PPU_CYCLES_PER_SCANLINE:
            self.cycle = 0
            self.scanline += 1

            if self.scanline >= PPU_SCANLINES_PER_FRAME:
                self.scanline = 0
                self.frame += 1

            # Simplified scanline-level timing event.
            if self.scanline == PPU_VBLANK_START_SCANLINE:
                self.status |= VBLANK_STARTED

            if self.scanline == PPU_PRE_RENDER_SCANLINE:
                self.status &= ~VBLANK_STARTED

Important simplification

This is not dot-accurate PPU timing. On real hardware, status changes happen at specific PPU dots/cycles, commonly modeled around dot/cycle 1 of the relevant scanline. For now, this tutorial uses scanline-entry behavior because the goal is to teach the frame/VBlank concept before full cycle accuracy.

Out of tutorial objectives for now

  • dot/cycle-accurate VBlank timing
  • odd-frame cycle skip
  • rendering pixels during visible scanlines
  • sprite 0 hit
  • sprite overflow
  • NMI generation/CPU interrupt handling

Run this lesson

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