PPU: The NES Picture Processor
Dedicated silicon for smooth scrolling and sprites
The Ricoh 2C02 Picture Processing Unit gave the NES hardware sprites, tile-based backgrounds, and smooth scrolling—defining the look of 8-bit console gaming.
The Picture Processing Unit (PPU) was Nintendo’s secret weapon. While home computers relied on the CPU for graphics, the NES offloaded rendering to dedicated hardware. This freed the 6502 for game logic and gave the console its characteristic smooth scrolling and fluid sprite animation.
Fast facts
- Resolution: 256×240 pixels (NTSC crops to ~256×224 visible).
- Colours: a 6-bit palette code, so 64 possible values — but only 52 distinct outputs (48 colours plus 4 greys). 25 on screen simultaneously (1 backdrop + 4 background palettes × 3 + 4 sprite palettes × 3). See the palette section below.
- Backgrounds: four logical nametables (32×30 tiles each), backed by 2 KB of CIRAM (two physical pages); cartridge mirroring (horizontal, vertical, single-screen, or four-screen) decides which logical maps where.
- Sprites: 64 sprites in OAM, 8 per scanline maximum (hardware-enforced).
- Tile size: 8×8 backgrounds; sprites are 8×8 or 8×16 (mode set globally via PPUCTRL bit 5).
The palette is generated, not tabulated
Most descriptions of the NES palette start from a table of 64 colours and then apologise for it — several entries look identical, a few behave oddly, and every emulator’s version differs slightly. All of that is a symptom of describing the wrong thing.
There is no table of colours in the 2C02. There is a generator. Each palette entry is six bits, split into two fields:
- Level — two bits, giving four brightness steps.
- Hue — four bits, selecting one of twelve phases. And “phase” is the exact word: the hue value sets the phase of the NTSC colour subcarrier, the signal a television uses to decide what colour a pixel is. Hue value zero is defined as none, which is what produces the greyscale column.
So the arithmetic is 12 phases × 4 levels = 48 colours, plus 4 greys at hue “none” = 52. Sixty-four is the size of the six-bit code, not the number of colours the chip can make. The “near-duplicates” everyone has to explain away are codes that fall outside the defined set.
This is why the twelve hues sit at even intervals around the colour wheel and not in an arbitrary order, and why no two emulators agree on the exact shades: the PPU does not output colours, it outputs a composite television signal, and turning that back into RGB is an act of interpretation. Ricoh’s development specification for the RP2C02, established 12 October 1982 and revised that December, documents the generator directly: it names the twelve phases, annotates the seventh as the burst position, and anchors them to the colour burst — the reference signal a television locks onto at the start of each scanline. The named anchors fall on every second phase, evenly spaced around the wheel.
The chip was specified nine months before the Famicom reached shops.
Memory architecture
| Address Range | Contents |
|---|---|
| $0000-$0FFF | Pattern table 0 (left, 256 tiles) |
| $1000-$1FFF | Pattern table 1 (right, 256 tiles) |
| $2000-$23FF | Nametable 0 |
| $2400-$27FF | Nametable 1 |
| $2800-$2BFF | Nametable 2 (mirror or RAM) |
| $2C00-$2FFF | Nametable 3 (mirror or RAM) |
| $3F00-$3F1F | Palette RAM |
CPU interface registers
| Address | Register | Purpose |
|---|---|---|
| $2000 | PPUCTRL | NMI enable, sprite size, pattern tables |
| $2001 | PPUMASK | Colour emphasis, sprite/BG enable |
| $2002 | PPUSTATUS | VBlank flag, sprite 0 hit, sprite overflow |
| $2003 | OAMADDR | Sprite memory address |
| $2004 | OAMDATA | Sprite memory data |
| $2005 | PPUSCROLL | Scroll position (write twice: X, Y) |
| $2006 | PPUADDR | VRAM address (write twice: high, low) |
| $2007 | PPUDATA | VRAM data read/write |
OAM DMA lives separately on the CPU bus:
| Address | Register | Purpose |
|---|---|---|
| $4014 | OAMDMA | 256-byte CPU→OAM DMA transfer; CPU stalls 513–514 cycles |
OAMDMA is the canonical mechanism for refreshing sprite RAM each frame (writing the 256 OAM bytes one at a time via OAMDATA would not fit in vblank).
Sprite attributes
Each of the 64 sprites uses 4 bytes in OAM (Object Attribute Memory):
| Byte | Purpose |
|---|---|
| 0 | Y position (sprite appears on next scanline) |
| 1 | Tile index |
| 2 | Attributes (palette, priority, flip H/V) |
| 3 | X position |
Key techniques
- VBlank timing: all VRAM updates must happen during the ~2270 cycles of vertical blank.
- Sprite 0 hit: detect when sprite 0’s opaque pixel overlaps background for mid-screen effects.
- Scroll splitting: change scroll mid-frame using sprite 0 hit or mapper IRQs.
- Attribute table tricks: work around the 16×16 pixel colour grid constraint.
Limitations and workarounds
- 8 sprites per scanline: exceeded sprites flicker; games cycle priority to spread flicker. This ceiling was designed in, not stumbled into — it appears in the chip’s own capability summary alongside the 64-sprite total. There is a status bit meant to report hitting it, but it does not work: NESdev records that the detection logic “does not work correctly”, checking the wrong OAM indices when it searches for a ninth sprite and producing “both false positives and false negatives”. Games use it as a second timing source rather than as a sprite counter.
- Colour granularity: 16×16 pixel attribute blocks; design around this constraint.
- No line-by-line scroll: requires sprite 0 hit or mapper assistance.
- Pattern table size: 512 tiles total; mappers add bank switching.
Sources
The register map, memory layout, sprite behaviour and timing above follow the NESdev Wiki’s PPU documentation, whose content is public domain and which remains the best place to check any of it. Everything above can be verified there.
⚠ The palette section is the exception. That the palette is generated rather than tabulated — twelve subcarrier phases by four levels, hue zero meaning none — is documented in Ricoh’s own development specification for the RP2C02, an internal document written for Nintendo and never published. We describe what it says and reproduce nothing from it. The community arrived at the same structure independently by measurement, so the section is checkable; what the specification adds is the confirmation that this was the design intent in 1982, and the reason the figure is 52 rather than the 64 that the six-bit code suggests.