|
|
(81 intermediate revisions by 6 users not shown) |
Line 1: |
Line 1: |
| __TOC__
| | #REDIRECT [[PPU scrolling]] |
| == Preface ==
| |
| "[http://nesdev.parodius.com/loopyppu.zip 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 addresses written to [[PPU registers|its ports]].
| |
| 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 ==
| |
| | |
| In the following, ''d'' refers to the data written to the port, and ''A'' through ''H'' to individual bits of a value.
| |
| | |
| $2000 write:
| |
| t:....BA.. ........ = d:......BA
| |
| $2005 first write:
| |
| t:........ ...HGFED = d:HGFED...
| |
| x: CBA = 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 dot 256 of each scanline, if rendering is enabled, the PPU copies all bits related to horizontal position from ''t'' to ''v'':
| |
| v:.....H.. ...EDCBA = t:.....H.. ...EDCBA
| |
| And at dot 304 of the pre-render scanline, if rendering is enabled, the PPU copies all bits from ''t'' to ''v'':
| |
| v = t
| |
| | |
| Note: $2005 and $2006 share the toggle that selects between first/second writes.
| |
| Reading $2002 will clear it so that the next write is seen as a first write.
| |
| | |
| All of this info agrees with the tests Loopy has run on an NES console and Quietust's [[:File:Vramaddr.jpg|analysis of a micrograph of the PPU die]].
| |
| If there's something you don't agree with, please let [http://nesdev.parodius.com/bbs/viewtopic.php?t=8962 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.
| |
| | |
| == Examples ==
| |
| | |
| Below are examples using actual 6502 code, indicating (tracking) what happens to all relevant variables described above, both before and after the 6502 instructions are executed.
| |
| | |
| Individual bits written to a PPU register are colour-coded to reflect where they end up in ''t''.
| |
| | |
| Assume all 6502 code is run sequentially in the order shown, one instruction after the next.
| |
| | |
| Finally, note that the number of bits for ''v'' and ''t'' is 15, while further up the page the documentation shows them as 16-bit. The 16th bit is unused (always 0).
| |
| | |
| === Original skinny example ===
| |
| | |
| This is based completely off of the above [[#Stuff_that_affects_register_contents|Stuff that affects register contents]] section.
| |
| | |
| {| class="wikitable"
| |
| !colspan="3" style="background-color:palegreen" | Before
| |
| !rowspan="2" | Instructions
| |
| !colspan="3" style="background-color:lightcoral" | After
| |
| !rowspan="2" | Notes
| |
| |-
| |
| !style="background-color:palegreen" | t
| |
| !style="background-color:palegreen" | v
| |
| !style="background-color:palegreen" | x
| |
| !style="background-color:lightcoral" | t
| |
| !style="background-color:lightcoral" | v
| |
| !style="background-color:lightcoral" | x
| |
| |-
| |
| |style="font-family:monospace; white-space:nowrap;" | ....... ........
| |
| |style="font-family:monospace; white-space:nowrap;" | ....... ........
| |
| |style="font-family:monospace; white-space:nowrap;" | ...
| |
| |style="font-family:monospace; white-space:nowrap;" | LDA #$00 (%000000<span style="background-color:lime">00</span>)<br />STA $2000
| |
| |style="font-family:monospace; white-space:nowrap;" | ...<span style="background-color:lime">00</span>.. ........
| |
| |style="font-family:monospace; white-space:nowrap;" | ....... ........
| |
| |style="font-family:monospace; white-space:nowrap;" | ...
| |
| |
| |
| |-
| |
| |style="font-family:monospace; white-space:nowrap;" | ...00.. ........
| |
| |style="font-family:monospace; white-space:nowrap;" | ....... ........
| |
| |style="font-family:monospace; white-space:nowrap;" | ...
| |
| |style="font-family:monospace; white-space:nowrap;" | LDA #$7D (%<span style="background-color:lime">01111</span><span style="background-color:yellow">101</span>)<br />STA $2005
| |
| |style="font-family:monospace; white-space:nowrap;" | ...00.. ...<span style="background-color:lime">01111</span>
| |
| |style="font-family:monospace; white-space:nowrap;" | ....... ........
| |
| |style="font-family:monospace; white-space:nowrap;" | <span style="background-color:yellow">101</span>
| |
| |
| |
| |-
| |
| |style="font-family:monospace; white-space:nowrap;" | ...00.. ...01111
| |
| |style="font-family:monospace; white-space:nowrap;" | ....... ........
| |
| |style="font-family:monospace; white-space:nowrap;" | 101
| |
| |style="font-family:monospace; white-space:nowrap;" | LDA #$5E (%<span style="background-color:lime">01</span><span style="background-color:yellow">011</span><span style="background-color:cyan">110</span>)<br />STA $2005
| |
| |style="font-family:monospace; white-space:nowrap;" | <span style="background-color:cyan">110</span>00<span style="background-color:lime">01</span> <span style="background-color:yellow">011</span>01111
| |
| |style="font-family:monospace; white-space:nowrap;" | ....... ........
| |
| |style="font-family:monospace; white-space:nowrap;" | 101
| |
| |
| |
| |-
| |
| |style="font-family:monospace; white-space:nowrap;" | 1100001 01101111
| |
| |style="font-family:monospace; white-space:nowrap;" | ....... ........
| |
| |style="font-family:monospace; white-space:nowrap;" | 101
| |
| |style="font-family:monospace; white-space:nowrap;" | LDA #$3D (%00<span style="background-color:lime">111101</span>)<br />STA $2006
| |
| |style="font-family:monospace; white-space:nowrap;" | 0<span style="background-color:lime">111101</span> 01101111
| |
| |style="font-family:monospace; white-space:nowrap;" | ....... ........
| |
| |style="font-family:monospace; white-space:nowrap;" | 101
| |
| |Bit 15 of ''t'' gets set to zero
| |
| |-
| |
| |style="font-family:monospace; white-space:nowrap;" | 0111101 01101111
| |
| |style="font-family:monospace; white-space:nowrap;" | ....... ........
| |
| |style="font-family:monospace; white-space:nowrap;" | 101
| |
| |style="font-family:monospace; white-space:nowrap;" | LDA #$F0 (%<span style="background-color:lime">11110000</span>)<br />STA $2006
| |
| |style="font-family:monospace; white-space:nowrap;" | 0111101 <span style="background-color:lime">11110000</span>
| |
| |style="font-family:monospace; white-space:nowrap;" | 0111101 11110000
| |
| |style="font-family:monospace; white-space:nowrap;" | 101
| |
| |After ''t'' is updated, contents of ''t'' copied into ''v''
| |
| |}
| |
| | |
| === 2006-2005-2005-2006 example ===
| |
| | |
| This is based on [http://nesdev.parodius.com/bbs/viewtopic.php?p=78593#78593 Drag's example on the nesdev forum] where writes to PPU registers are done in the order of $2006, $2005, $2005, $2006, and what the effect is.
| |
| | |
| {{mbox
| |
| | type = warning
| |
| | text = To understand this example, you need to keep in mind the following note from the above section:<br />'''Note: $2005 and $2006 share the toggle that selects between first/second writes. Reading $2002 will clear it.'''
| |
| }}
| |
| | |
| {| class="wikitable"
| |
| !colspan="3" style="background-color:palegreen" | Before
| |
| !rowspan="2" | Instructions
| |
| !colspan="3" style="background-color:lightcoral" | After
| |
| !rowspan="2" | Notes
| |
| |-
| |
| !style="background-color:palegreen" | t
| |
| !style="background-color:palegreen" | v
| |
| !style="background-color:palegreen" | x
| |
| !style="background-color:lightcoral" | t
| |
| !style="background-color:lightcoral" | v
| |
| !style="background-color:lightcoral" | x
| |
| |-
| |
| |style="font-family:monospace; white-space:nowrap;" | ....... ........
| |
| |style="font-family:monospace; white-space:nowrap;" | ....... ........
| |
| |style="font-family:monospace; white-space:nowrap;" | ...
| |
| |style="font-family:monospace; white-space:nowrap;" | LDA #$27 (%00<span style="background-color:lime">100111</span>)<br />STA $2006
| |
| |style="font-family:monospace; white-space:nowrap;" | 0<span style="background-color:lime">100111</span> ........
| |
| |style="font-family:monospace; white-space:nowrap;" | ....... ........
| |
| |style="font-family:monospace; white-space:nowrap;" | ...
| |
| | Bit 15 of ''t'' set to zero
| |
| |-
| |
| |style="font-family:monospace; white-space:nowrap;" | 0100111 ........
| |
| |style="font-family:monospace; white-space:nowrap;" | ....... ........
| |
| |style="font-family:monospace; white-space:nowrap;" | ...
| |
| |style="font-family:monospace; white-space:nowrap;" | LDA #$3E (%<span style="background-color:lime">00</span><span style="background-color:yellow">111</span><span style="background-color:cyan">110</span>)<br />STA $2005
| |
| |style="font-family:monospace; white-space:nowrap;" | <span style="background-color:cyan">110</span>01<span style="background-color:lime">00</span> <span style="background-color:yellow">111</span>.....
| |
| |style="font-family:monospace; white-space:nowrap;" | ....... ........
| |
| |style="font-family:monospace; white-space:nowrap;" | ...
| |
| | Behaviour of 2nd $2005 write
| |
| |-
| |
| |style="font-family:monospace; white-space:nowrap;" | 1100100 111.....
| |
| |style="font-family:monospace; white-space:nowrap;" | ....... ........
| |
| |style="font-family:monospace; white-space:nowrap;" | ...
| |
| |style="font-family:monospace; white-space:nowrap;" | LDA #$7D (%<span style="background-color:lime">01111</span><span style="background-color:yellow">101</span>)<br />STA $2005
| |
| |style="font-family:monospace; white-space:nowrap;" | 1100100 111<span style="background-color:lime">01111</span>
| |
| |style="font-family:monospace; white-space:nowrap;" | ....... ........
| |
| |style="font-family:monospace; white-space:nowrap;" | <span style="background-color:yellow">101</span>
| |
| | Behaviour of 1st $2005 write
| |
| |-
| |
| |style="font-family:monospace; white-space:nowrap;" | 1100100 11101111
| |
| |style="font-family:monospace; white-space:nowrap;" | ....... ........
| |
| |style="font-family:monospace; white-space:nowrap;" | 101
| |
| |style="font-family:monospace; white-space:nowrap;" | LDA #$99 (<span style="background-color:lime">%10011001</span>)<br />STA $2006
| |
| |style="font-family:monospace; white-space:nowrap;" | 1100100 <span style="background-color:lime">10011001</span>
| |
| |style="font-family:monospace; white-space:nowrap;" | 1100100 10011001
| |
| |style="font-family:monospace; white-space:nowrap;" | 101
| |
| |After ''t'' is updated, contents of ''t'' copied into ''v''
| |
| |}
| |