The skinny on NES scrolling
"The skinny on NES scrolling" was posted by loopy on 1999-04-13 to what eventually became the NESdev Yahoo! Group. It was the first to publicly tell how exactly how the PPU uses the address. After over a decade, it is still believed accurate. Some people get turned off by the fact that it's provided as monospaced text inside a zipfile, that addresses have nothing to distinguish them from years, and that the diagrams of what bits get copied where are allegedly difficult to read. What follows is this document, reformatted to web standards, with a few minor things made slightly clearer.
PPU registers
Games using complex raster effects require a complete understanding of how the various address registers inside the PPU work. Here are the related registers:
- v
- Current VRAM address (15 bits)
- t
- Temporary VRAM address (15 bits)
- x
- Fine X scroll (3 bits)
Registers v and t are 15 bits, but because emulators commonly store them in 16-bit machine words, they are shown with an extra bit that's never used.
The PPU uses the current VRAM address for both reading and writing PPU memory thru $2007, and for fetching nametable data to draw the background. As it's drawing the background, it updates the address to point to the nametable data currently being drawn. Bits 10-11 hold the nametable address minus $2000. Bits 12-14 are the Y offset of a scanline within a tile.
Stuff that affects register contents
(sorry for the shorthand logic but i think it's easier to see this way)
$2000 write:
t:....BA.. ........ = d:......BA
$2005 first write:
t:........ ...HGFED = d:HGFED... x = d:.....CBA
$2005 second write:
t:......HG FED..... = d:HGFED... t:.CBA.... ........ = d:.....CBA
$2006 first write:
t:..FEDCBA ........ = d:..FEDCBA t:.G...... ........ = 0
$2006 second write:
t:........ HGFEDCBA = d:HGFEDCBA v = t
At the end of each scanline, if rendering is turned on, all bits related to horizontal position get copied from t to v:
v:.....X.. ...EDCBA = t:.....X.. ...EDCBA
And at the end of the pre-render scanline, if rendering is turned on, all bits get copied from t to v:
v = t
Note: $2005 and $2006 share the toggle that selects between first/second writes. Reading 2002 will clear it.
Note: All of this info agrees with the tests Loopy has run on an NES console. If there's something you don't agree with, please let the BBS know so that a member can verify it.
Wrapping around
You can think of bits 4-0 of the VRAM address as the "coarse x scroll"(*8) that the PPU increments as it draws. As it wraps from 31 to 0, bit 10 is switched. You should see how this causes horizontal wrapping between nametables (0,1) and (2,3).
You can think of bits 9-5 as the "coarse y scroll"(*8). This functions slightly different from the X. It wraps to 0 and bit 11 is switched when it's incremented from 29 instead of 31. There are some odd side effects from this. If you manually set the value above 29 (from either $2005 or $2006), the wrapping from 29 obviously won't happen, and attribute data will be used as nametable data. The "y scroll" still wraps to 0 from 31, but without switching bit 11. This explains why writing 240+ to 'Y' in $2005 appeared as a negative scroll value.