Skip to content

PPU 5C77 5C78

DoubleGate edited this page Jul 8, 2026 · 1 revision

PPU — PPU1 (5C77) + PPU2 (5C78)

References: docs/ppu.md, docs/scheduler.md. Sources cited inline in the underlying spec: SNESdev PPU registers / Backgrounds / Sprites / Mode 7, Fullsnes.

Purpose

The SNES's video subsystem is two physical chips sharing the CPU's B-bus ($2100$21FF) with a common 16-bit data bus but separate address buses:

  • PPU1 (5C77): OAM/sprites and the background/sprite rendering pipeline, including the Mode 7 hardware-multiply result (MPYL/M/H, $2134$2136). STAT77 ($213E) reports version plus the sprite over/time flags.
  • PPU2 (5C78): CGRAM (palette) access, color math/output, and the timing/status flags — the H/V counters, VBlank/HBlank, and the counter-latch. STAT78 ($213F) reports version, NTSC/PAL, and the counter-latch flag.

crates/rustysnes-ppu models both chips together as one crate; the PPU only sees a narrow PpuBus trait for VRAM/CGRAM/OAM access, keeping it decoupled from the rest of the machine.

Background modes

Mode BG1 BG2 BG3 BG4 Layers Notes
0 2bpp 2bpp 2bpp 2bpp 4 separate palette region per BG
1 4bpp 4bpp 2bpp 3 BG3 priority-bit selectable
2 4bpp 4bpp (OPT) 2 offset-per-tile
3 8bpp 4bpp 2 high-color
4 8bpp 2bpp (OPT) 2 offset-per-tile
5 4bpp 2bpp 2 forced 512-px hi-res
6 4bpp (OPT) 1 hi-res + offset-per-tile
7 8bpp 1 affine; +EXTBG adds BG2

BGMODE ($2105) selects the mode plus per-BG tile size (8×8 vs 16×16). Offset-per-tile is Modes 2/4/6 only; hi-res Modes 5/6 force a 512-pixel-wide horizontal output.

Mode 7 affine transform

Mode 7 is a single 128×128-tile map (256 distinct 8×8 tiles), 8bpp, transformed by a 2×2 affine matrix. The matrix coefficients (M7AM7D, $211B$211E) are each 16-bit signed 8.8 fixed-point, written low-byte-then-high-byte; the center point (M7X/M7Y, $211F/$2120) is 13-bit signed, also written twice. The PPU reuses its general-purpose hardware multiplier for the Mode 7 matrix multiply, exposing the signed 24-bit product at MPYL/M/H. EXTBG (a SETINI bit) turns Mode 7's high color bit into a per-pixel priority bit instead, letting BG2 layer under BG1 in Mode 7.

OAM / sprites

128 sprites, backed by 544 bytes of OAM (a 512-byte low table plus a 32-byte high table). Each low-table entry is 4 bytes: X (low), Y, tile index (low), and a flags byte (name high bit, 3-bit palette, 2-bit priority, V/H flip); the high table adds the X high bit and a per-sprite size toggle. OBSEL ($2101) selects the sprite name-table bases and one of eight size pairs.

Hardware enforces per-scanline limits: 32 sprites per line (Range Over, STAT77 bit 6) and 34 sprite-tiles per line (Time Over, STAT77 bit 7), both reset at the end of VBlank. Because tile fetch happens in reverse OAM-index order, lower-indexed sprites survive an overflow and higher-indexed ones drop — a detail that affects which sprites visibly disappear when a scene exceeds the limit.

Dot-clock timeline and H/V counters

Per docs/scheduler.md's binding numbering convention: 341 dots per line, each dot nominally 4 master clocks. A normal line is 1364 master clocks; a short line (NTSC non-interlace alternating frames) is 1360; a long line (PAL interlace) is 1368. NTSC runs 262 lines per frame, PAL 312 (plus one extra when interlaced). Active picture output spans dots 22–277 on the visible lines; VBlank begins at line 225 (or 240 in overscan mode); HBlank spans dot 274 through dot 1 of the next line.

Reading SLHV ($2137) latches the current H/V position; OPHCT/OPVCT ($213C/$213D) expose the latched 9-bit values via a read-twice protocol; reading STAT78 clears the latch and resets the read-toggle.

CGRAM, VRAM, color math, windows

  • CGRAM holds 256 15-bit BGR colors (5 bits each for R/G/B). CGADD ($2121) selects the entry; CGDATA ($2122) is written twice (low byte, then high byte) to set one color.
  • VRAM is 64 KiB (32K words), word-addressed. VMAIN ($2115) configures the address auto-increment step (1/32/128 words), address remapping, and which half-write (low/high) triggers the increment.
  • Color math blends the main and sub screens per CGWSEL/CGADSUB (add/subtract, half, per-layer enable, a fixed addend color) inside window regions defined by CGWSEL, W1/W2, and their boolean combination mode. TM/TS ($212C/$212D) select which layers appear on the main and sub screens respectively.

docs/ppu.md documents two specific hardware-accurate details the implementation follows closely, since getting them wrong visibly breaks real games: the subscreen color-math addend must fall back to the fixed COLDATA color (not CGRAM entry 0) when the subscreen pixel at that column is backdrop, and a background tile's CGRAM index must fold in its 3-bit palette-group field — both confirmed against ares pixel-diffs on Super Mario World.

Frame structure and resolutions

Standard output is 256×224; overscan mode extends this to 256×239. Forced hi-res (Modes 5/6) doubles the horizontal resolution to 512; interlace mode can double the vertical line count. SETINI ($2133) controls interlace, OBJ interlace, overscan, pseudo-hires, the EXTBG bit, and external sync.

Rendering model

rustysnes-ppu composites each visible scanline per-line, at the end of that line, into a 256×239 15-bit framebuffer — rather than rendering pixel-by-pixel at true dot resolution. This is bit-identical to a per-dot renderer in the finished frame (the project's determinism contract only requires the completed frame be reproducible) and considerably simpler to implement. Per docs/ppu.md's current implementation status, BG modes 0–7 tile fetch, per-mode priority tables, 16×16 tiles, mosaic, the full Mode 7 affine pipeline, the 128-sprite OAM pipeline with its range/time limits, color math, windows, and master brightness are all implemented this way.

Not yet wired to true dot resolution (and explicitly flagged as deferred, not silently approximated): hi-res Modes 5/6 currently render at 256 wide rather than the doubled 512, offset-per-tile (Modes 2/4/6), pseudo-hires, and interlace field doubling. Multi-split-per-line raster effects (more than one HDMA-driven register change within a single scanline) also need true dot-resolution rendering — a single split per line already works correctly with the per-line compositor, since it samples end-of-line register state.

Validation

The committable PPU/DMA/HDMA layer is undisbeliever's snes-test-roms suite, covering HDMA timing, force-blank mid-frame behavior, mid-scanline VRAM access, and OAM dropout. Visual validation uses deterministic framebuffer hashes against a canonical set of commercial titles. See Testing-Strategy for the full accuracy battery this fits into, and Lockstep-Scheduler for how the PPU's dot timeline integrates with the CPU and DMA/HDMA on the shared master clock.

Clone this wiki locally