The Nintendo Entertainment System Picture Processing Unit was a quite advanced 2D accelerator for the time.

PPU registers exposed to CPU

$2000: PPU control (write)
||||||++- base nametable address
||||||    (00 = $2000; 01 = $2400; 02 = $2800; 03 = $2c00)
|||||+--- VRAM address increment
|||||     (0: increment by 1, going across; 1: increment by 32, going down)
||||+---- Sprite pattern table address for 8x8 sprites (0: $0000; 1: $1000)
|||+----- Background pattern table address (0: $0000; 1: $1000)
||+------ Sprite size (0: 8x8 sprites; 1: 8x16 sprites)
|+------- PPU layer select (should always be 0 in the NES; some Nintendo
|         arcade boards presumably had two PPUs)
+-------- Vertical blank NMI generation (0: off; 1: on)

$2001: PPU mask (write)
|||||||+- Color disable (0: normal color; 1: AND all palette entries
|||||||   with 110000, effectively producing a monochrome display)
||||||+-- Show leftmost 8 pixels of background
|||||+--- Show sprites in leftmost 8 pixels
||||+---- Show background
|||+----- Show sprites
||+------ Intensify greens (and darken other colors)
|+------- Intensify blues (and darken other colors)
+-------- Intensify reds (and darken other colors)

$2002: PPU status (read)
|||+++++- Least significant bits of the last byte written to a PPU register
||+------ Sprite overflow. The PPU can handle only eight sprites on one
||        scanline and sets this bit if it starts drawing sprites.
|+------- Sprite 0 overlap.  Set when a nonzero pixel of sprite 0 is drawn
|         overlapping a nonzero background pixel.  Used for raster timing.
+-------- Vertical blank start (0: has not started; 1: has started)
Reading from $2002 will clear D7 of $2002 and also the $2005/$2006 latch.

$2003: OAM address (write)
Write the address of OAM memory you want to access here.

$2004: OAM data (write)
Write OAM data here.

$2005: Scroll register (write)
After reading $2002, write the horizontal and vertical scroll offsets
here just before turning on the screen.

$2006: VRAM address register (write)
When the screen is turned off ($2001), write the address of PPU memory
you want to access here.

$2007: VRAM data register (r/w)
When the screen is turned off ($2001), read or write data from PPU
memory here.  Reads are delayed by one cycle; discard the first byte

Access to $2005 and $2006 during screen refresh produces interesting raster effects; the starting position of each scanline can be set to any pixel position in nametable memory. For more information, see "The Skinny on NES Scrolling" by loopy (who is not loopy), available from

Pattern tables

There are two pattern tables, one at $0000 and one at $1000. Each tile in the pattern table is 16 bytes, made of two planes. The first plane controls bit 0 of the color; the second plane controls bit 1. Any pixel whose color is 0 is transparent (represented by '.' in the following diagram):

Bit Planes            Pixel Pattern

$0xx0  01000001
$0xx1  11000010
$0xx2  01000100
$0xx3  01001000
$0xx4  00010000
$0xx5  00100000         .1.....3
$0xx6  01000000         11....3.
$0xx7  10000000  =====  .1...3..
$0xx8  00000001  =====  ...3.22.
$0xx9  00000010         ..3....2
$0xxA  00000100         .3....2.
$0xxB  00001000         3....222
$0xxC  00010110
$0xxD  00100001
$0xxE  01000010
$0xxF  10000111


Up to 64 sprite positions and attributes are stored in OAM. Each sprite's information occupies four bytes.

x+$00: Y position of top of sprite
Sprite data is delayed by one scanline; you must subtract 1 from the
sprite's Y coordinate before writing it here.  Hide a sprite by
writing $EF here.

x+$01: Tile index number
For 8x8 sprites, the tile number in VRAM of this sprite.
For 8x16 sprites:
|||||||+- Bank ($0000 or $1000) of tiles
+++++++-- Tile number (0 to 254) within bank

x+$02: Attributes
||||||++- Palette (4 to 7) of sprite
|||+++--- Unused
||+------ Priority (0: in front of background; 1: behind background)
|+------- Flip hoizontally?
+-------- Flip vertically?

x+$03: X position of left side of sprite
Sprites do NOT wrap around from one side to the other.


The NES has four nametables, arranged in a 2x2 pattern:
+-----------+-----------+  The PPU has enough VRAM
|           |           |  for only two nametables;
|           |           |  hardware on the cartridge
|   $2000   |   $2400   |  maps $2800 to $2000
|           |           |  and $2C00 to $2400
|           |           |  (V-mirroring; Super Mario
+-----------+-----------+  Bros.) or $2400 to $2000
|           |           |  and $2C00 to $2800
|           |           |  (H-mirroring; Legend of Zelda).
|   $2800   |   $2C00   |
|           |           |  See NES memory map
|           |           |  for more information.
Each byte in the nametable controls one character cell.

Attribute tables

This is admittedly one of the hardest things to grasp about the PPU. At $23C0, $27C0, $2BC0, and $2FC0, there is an "attribute table." Each byte in the attribute table controls the palette of a 4x4 cell (32x32 pixel) square,

+---+---+---+---+  and two bits of each byte control the
|   |   |   |   |  palette of a 2x2 cell (16x16 pixel) 
+ D1-D0 + D3-D2 +  area.  This is why most NES games used
|   |   |   |   |  16x16 game pieces (size of SMB ? block)
+---+---+---+---+  or 32x32 game pieces (width of SMB pipe).
|   |   |   |   |
+ D5-D4 + D7-D6 +
|   |   |   |   |

Nametable tiles are 8x8 pixels, and the $2001 mask is 8 pixels wide, but attribute table tiles are 16x16 pixels. This is why games that use the horizontal mirroring mode for diagonal scrolling often have color artifacts on one side of the screen (on the right side in Super Mario Brothers 3; on the trailing side of the scroll in Kirby's Adventure).


Unlike most display systems you run into nowadays, NES's palette is HSV. The palette for the background runs from VRAM $3F00 to $3F0F; the palette for the sprites runs from $3F10 to $3F1F. Each color is stored in one byte.

||||++++- Hue
||++----- Value
++------- Unused

Hue $0 is light gray; $1 to $C are blue to red to green to cyan; $D is dark gray. The canonical code for "black" is $0F. Note that VS Unisystem games have completely different palettes.

Stupid PPU tricks

More on raster effects: Each scanline takes 113 2/3 CPU cycles; setting the current display position (through some twiddling of $2005 and $2006) at the start of each scanline can create pseudo-parallax scrolling and image warping; this is how Rad Racer and all the other Pole Position clones work.

As CNROM games began to run up against the 32 KB program ROM size limit, they stored map data in a ROM connected to the PPU lines and read it through $2007. Super Mario Brothers also stored its title screen data in PPU ROM.

The palette contains 48 colors and 5 grays; you can normally display 25 of these on the same screen. However, mid-frame palette swapping combined with the intensity bits in $2001 can create hundreds of pretty colors.

Anonymous Coward: Yes this is cut-and-paste noding, but only because I wrote it in Emacs before pasting it into "Edit your writeup"; it is in my own words. Or should I take the alternate connotation "this sounds so professional I'm surprised you claim to have written this" as a compliment?

(return to) NES programming

Log in or register to write something here or to contact authors.