PPU glitches: Difference between revisions

From NESdev Wiki
Jump to navigationJump to search
(→‎PPUMASK: $06 bits)
(→‎PPUADDR, PPUSCROLL: forgot (??) to mention two more early write failure modes)
Line 55: Line 55:


On the 2C02G, any write starting on dot 257 and ending on dot 258 that updates coarse X in "t" can cause the same symptoms as writes to PPUCTRL. [https://forums.nesdev.org/viewtopic.php?p=230503#p230503]
On the 2C02G, any write starting on dot 257 and ending on dot 258 that updates coarse X in "t" can cause the same symptoms as writes to PPUCTRL. [https://forums.nesdev.org/viewtopic.php?p=230503#p230503]
==[[PPUADDR]]==
Any second write to PPUADDR will immediately change update the lower three bits of coarse X and five bits of coarse Y to the value of open bus, and will then (on the next pixel, except as covered by the dot 257/258 glitch mentioned above) write the correct value. This is usually invisible but can cause an incorrect sliver.
==[[PPUSCROLL]]==
Any first write to PPUSCROLL will immediately change fine X to the value of open bus, and will then (on the next pixel) correct fine X.


=PPU-internal bus conflicts=
=PPU-internal bus conflicts=

Revision as of 23:06, 19 March 2023

Early Writes

The 6502, and thus also the 2A03, guarantee that R/W and address bus are stable while φ2 (or M2) are high, but do not guarantee the data bus is stable.

Unfortunately, the PPU generally assumes that the data bus is stable during the entire time, and this causes a number of glitches in the PPU's behavior.

These can be mostly worked around by writing to the PPU register mirror where the relevant bit is either the same as the old value or the new value.

Here is a timing diagram for the 2A03G:

 (10ns) 0  40  80 120 160 200 240 280 320 360 400 440 480 520 560 600 640
     M2 \____________________/¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯\______
/ROMSEL __/¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯\__________________________________/¯¯¯¯ (when relevant)
/PPUSEL ____/¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯\__________________________________/¯¯ (when relevant)
    R/W ¯¯¯¯¯\_________________________________________________________ (read to write cycle)
     D0 ======ZZZZZZZZZZZZZZZZZZZZZZZZZZZ=============================Z
    R/W ____________/¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ (write to read cycle)

(need to add relative timing of address bus, if different from R/W)

PPUCTRL

In the 2C02G, the $C3 bits are processed in an asynchronous manner, and this can cause various problems:

  • $01s bit: On every active scanline, a write to PPUCTRL at the exact wrong time (write starting on dot 257, ending on dot 258) can cause the left nametable to be drawn for the upcoming scanline. [1] This is because the contents of open bus - $20 - are used by the PPU on dot 257, setting the "base nametable" to the left one instead of the intended one. As a work-around, write to the PPU address mirror where the bottom 2 bits of the upper byte of the address match the data that will be written. [2]
  • $02s bit: On every active field, a write to PPUCTRL at the exact wrong time (starting on prerender scanline dot 304, ending on dot 305) can cause the top nametable to be drawn for the upcoming field. Workaround is same as above.
  • $40s bit: Any write to PPUCTRL during the active field can temporarily disable "output colors on EXT pins" for one pixel. This is believed to be the cause of certain bugs reported in the HDNES.
  • $80s bit: Any write to PPUCTRL during the vertical blanking interval can cause the NMI output to be asserted, or deasserted, for about 80ns. However, this glitch is invisible, because the 6502 ignores its NMI input during this time.

In the 2C02A, it's known that the $18 bits in PPUMASK are also processed in an asynchronous manner, and suspected that all the other bits do also:

  • $04s bit: On the 2C02A, it's believed that this will have no effect, because the write to PPUCTRL would have to occur right in the middle of incrementing the PPU address while rendering is disabled.
  • $08s bit: On the 2C02A, it's believed that a write during horizontal blanking could cause exactly one bitplane of one sliver of one sprite to be fetched from the wrong pattern table.
  • $10s bit: On the 2C02A, it's believed that a write during active redraw could cause exactly one bitplane of one sliver of one background tile to be fetched from the wrong pattern table.
  • $20s bit: On the 2C02A, it's believed that a write at any time could cause one sprite sliver could be drawn incorrectly, specifics are unclear.

PPUMASK

On the 2C02G, the $81 bits are processed in an asynchronous manner and this can cause unimportant glitches:

  • $01s bit: Any write to PPUMASK can, at any time, turn off the "monochrome" flag for one pixel. [3]
  • $80s bit: Any write to PPUMASK can, regardless of subpixel phase, turn off "blue emphasis" for half of one pixel. [4]

In the 2C02A, it's known that the $18 bits in PPUMASK are also processed in an asynchronous manner, and suspected that all the other bits do also:

  • $18 bits: On the 2C02A, it's known that any write to PPUMASK during active redraw will turn off rendering for one pixel, causing all the documented hazards with disabling rendering.
  • $06 bits: on the 2C02A, the effects of this will be invisible under the above disabling.
  • $60 bits: on the 2C02A, it is suspected that any write to PPUMASK will turn off "red" or "green" emphasis for half of one pixel

OAMADDR

On the 2C02G, it is suspected that issues with writes to OAMADDR causing sprite corruption are due to this behavior.

PPUADDR, PPUSCROLL

On the 2C02G, any write starting on dot 257 and ending on dot 258 that updates coarse X in "t" can cause the same symptoms as writes to PPUCTRL. [5]

PPUADDR

Any second write to PPUADDR will immediately change update the lower three bits of coarse X and five bits of coarse Y to the value of open bus, and will then (on the next pixel, except as covered by the dot 257/258 glitch mentioned above) write the correct value. This is usually invisible but can cause an incorrect sliver.

PPUSCROLL

Any first write to PPUSCROLL will immediately change fine X to the value of open bus, and will then (on the next pixel) correct fine X.

PPU-internal bus conflicts

Any time the PPU tries to both increment "v" at the same time that it tries to reload "v" from "t" causes a bus conflict, resulting in the bitwise AND of the two inputs.

This can happen (at least) two different ways: