232. Ppu status read clears vblank

Reading PPUSTATUS clears the VBlank flag.

Lesson 232 of 356 · tests/chapter_03_ppu_memory_and_graphics_data/test_232_ppu_status_read_clears_vblank.py

File to update

emulator/ppu/ppu.py

Method to update

PPU.read_register(addr)

Why this step exists

PPUSTATUS at $2002 is not a passive value. On real NES hardware, reading PPUSTATUS returns the current status byte and clears the VBlank-started flag inside the PPU.

Important behavior

read_register($2002):
    1. save the old status value
    2. clear only VBLANK_STARTED, bit 7
    3. return the old status value

The order matters.

Correct

value = self.status
self.status &= ~VBLANK_STARTED
return value

Incorrect

self.status &= ~VBLANK_STARTED
return self.status

Why incorrect

The CPU must be able to observe that VBlank was set. If the emulator clears bit 7 before returning, CPU polling loops will miss VBlank.

Bit-level example

status before read:      0b1110_0000
~VBLANK_STARTED:         0b0111_1111
status after AND:        0b0110_0000

Only bit 7 clears. Sprite 0 hit and sprite overflow remain set.

Suggested implementation pseudocode

def read_register(self, addr: int) -> int:
    match addr:
        case 0x2002:
            value = self.status
            self.status &= ~VBLANK_STARTED
            return value
        case 0x2004:
            return self.oam_data
        case 0x2007:
            return self.data
        case _:
            raise ValueError(...)

Out of scope

  • automatically setting VBlank from PPU timing
  • NMI generation
  • scanlines/cycles
  • sprite 0 hit behavior
  • sprite overflow behavior

Run this lesson

uv run pytest tests/chapter_03_ppu_memory_and_graphics_data/test_232_ppu_status_read_clears_vblank.py -v