Skip to main content
NESdev Wiki
Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage

Errata

This page describes aspects of the NES hardware that may be considered an error in the implementation (errata), or just otherwise unintuitive or unexpected behaviour.

Many of these issues are poorly emulated, and can cause program compatibility problems, especially for homebrew games not tested on hardware.

Video

CategoryTypeProblemWorkaroundReference
EmphasisDesignOn RGB PPUs, emphasis maximizes the emphasized color (even for $0F black) instead of dimming the other two colors. This is significantly different than the emphasis behavior on other PPUs and causes the screen to be entirely white when all 3 emphasis bits are set.If supporting RGB PPUs, provide the user an option to disable emphasis, detect RGB PPUs and automatically disable emphasis if they are detected, or avoid using emphasis in parts of the screen that contain critical information.
EmphasisDesignOn PAL and Dendy, the red and green emphasis bits in PPUMASK ($2001) are swapped relative to NTSC and RGB PPUs.Change which bits are used for red and green if a PAL or Dendy PPU is detected.
EXTDesignEnabling EXT output by setting bit 6 of PPUCTRL ($2000) causes the PPU to output pixel data onto pins that are grounded in every NTSC and PAL console model. This produces near-constant bus conflicts that may over time damage the PPU or the output drivers on these pins.Do not set this bit.Master/slave mode
PalettesDesignColor $0D in the palette is "blacker than black" and causes image stability problems on some TVs. This is made worse when using emphasis, which further darkens this color.Do not use this color. Instead, use $0F for black.Color $0D games
PalettesDesignWhen the PPU's internal v register points at a palette address while rendering is disabled, the PPU draws the color at that address. This means updating palettes outside of vblank can cause stripes of color to flash on the screen. Writing an address to PPUADDR ($2006) is usually what makes v point into palette RAM, but this same behavior occurs if rendering ends while v contains a value that looks like a palette RAM address, which can cause flashing colors in the bottom screen border for some scroll positions.
  • Schedule palette writes to occur during vblank, rather than writing them at any time while rendering is disabled.
  • Avoid choosing a scroll position in horizontally-scrolling games that leaves v with a palette RAM address at the bottom of the screen. For example, if moving the background down one scanline to compensate for sprites being drawn one scanline lower than their actual Y position, start on the last scanline of nametables 2 and 3 instead of nametables 0 and 1.
PalettesBugIf the low 4 bits of the palette address change while the PPU is reading palette RAM, palette data may corrupt.
  • This can occur on one CPU/PPU alignment when turning rendering off mid-screen if v points into palette RAM and the palette address from the rendering pipeline differs from the v palette address. Turning rendering off during an attributes fetch widens the range of v values that are treated as pointing into palette RAM (v & $3C00 == $3C00).
  • This can also occur on another CPU/PPU alignment when turning rendering on mid-screen while v points into palette RAM, for the same reason. In this case, the rendering pipeline address is always $3F00.
  • On early revisions of the PPU (letterless, A, B, D), palette RAM can corrupt in other circumstances that are not yet fully understood; it's hypothesized that corruption may occur when rendering starts if v points into palette RAM, or that t->v copies after 2nd PPUADDR writes are not synchronized and may be able to change the address mid-access on some CPU/PPU alignments.
Software that toggles rendering mid-screen should be tested on real hardware across many power cycles to ensure corruption cannot occur.
  • If turning rendering off mid-screen, take care to ensure that v is not within a vulnerable address range.
  • If turning rendering on mid-screen, ensure the low nybble of v is 0 if v is pointing into palette RAM.
  • On early PPU revisions, when palettes are updated in vblank, the low nybble of v should end in 0 before moving it out of palette RAM, or palettes should be written to the last mirror of palette RAM so v automatically increments to address $0000 after writing the last color.
  • On early PPU revisions, it may be necessary to update v in vblank to ensure the stale value of v from the last frame doesn't point into palette RAM when the next frame begins.
Palette corruption
PalettesBugReading palette RAM via PPUDATA ($2007) on G and H PPUs is unreliable; on one CPU/PPU alignment, the returned data may be corrupt. Palette RAM is not readable at all on earlier PPUs; the feature to read them was only added in revision G.Keep a copy of the palettes in CPU RAM if needed instead of reading it from the PPU.Reading palette RAM
Register accessBugSome 6502 write instructions produce an extra read or write as part of their operation, which produces unexpected results when used with registers with side-effects, such as OAMDATA ($2004) and PPUDATA ($2007). This includes all read-modify-write instructions (ASL, LSR, ROL, ROR, DEC, INC) as well as indexed addressing instructions (e.g. STA $2000,X). Spurious writes to OAMDATA and accesses to PPUDATA can trigger extra increments of the PPU's internal address registers, and writing PPUDATA too quickly can cause data corruption. Note that accessing PPUDATA multiple cycles in a row likely skips some increments (possibly every 2nd increment), and reading PPUDATA multiple cycles in a row may return garbage data (perhaps on every 2nd read).Do not use instructions with dummy reads on PPUDATA or with dummy writes on PPUDATA or OAMDATA.
Register accessBugOn NTSC CPUs, DMC DMA during reads from PPUDATA ($2007) can cause extra reads of the register, which each increment the VRAM address, causing bytes to be skipped. These extra reads can also cause the vblank flag to be missed when polling PPUSTATUS (though NMI should be used for this purpose, instead). Note that accessing PPUDATA multiple cycles in a row likely skips some increments (possibly every 2nd increment).Disable DPCM when reading from PPUDATA, or time register reads so they cannot collide with DMA (like with controllers).DMA register conflicts
ResetDesignOn NES-001 frontloader consoles, after power-on or reset, the PPU refuses to accept data written to the PPUCTRL ($2000), PPUMASK ($2001), PPUSCROLL ($2005), and PPUADDR ($2006) registers for about the length of one frame.NES init code should wait for the vblank flag in PPUSTATUS to become set twice before otherwise using the PPU. The flag may start out set, so it should be read first to clear it before waiting.PPU power up state
ScrollDesignSetting the VRAM address using PPUADDR ($2006) clobbers the scroll position because they use the same internal register.Reset the scroll position using PPUCTRL ($2000) and PPUSCROLL ($2005) after finishing all background updates.
ScrollDesignBackground Y scroll positions in the range 240-255 are treated as "negative", rendering the attribute table as though it is two rows of nametable tiles. When the PPU increments the scroll position past 255, it wraps to 0 of the same nametable rather than carrying a 1 into the Y nametable bit.Skip over these scroll positions when scrolling vertically. Scrolling below value 0 should wrap to 239, and scrolling above value 239 should wrap to 0.
ScrollBugWriting to PPUCTRL ($2000), PPUSCROLL ($2005), or PPUADDR ($2006) at the exact start of horizontal blanking (dot 257) may cause the PPU to use the wrong scroll position for the next scanline. This is because the PPU's internal t register briefly gets the CPU open bus value at the start of the write, and the PPU may reload the horizontal scroll position during this brief time. Most commonly, this happens in games using an NMI only design when writing PPUCTRL to enable NMI at the end of the gameplay frame, causing the next scanline to show the left name table instead of the right.
  • Don't write to PPUCTRL outside vertical or forced blanking except as part of a properly timed raster effect. If you are writing to PPUCTRL as a way of temporarily preventing the NMI handler from being called while it is already running, don't disable NMI through PPUCTRL. Instead, use a variable to lock out reentrant NMI, and check this variable at the beginning of your NMI handler.[1]
  • Write to PPUCTRL through either $2000 or the mirror at $2100, depending on the desired value of the least significant bit (the X nametable) that will be written to PPUCTRL.[2]
  • Use the PPU open bus mitigation for the PPU early write glitch to ensure the bus contains the correct value for the entire write to these registers. This works for all 3 registers.
  • Time the register writes so they don't occur on the vulnerable dot.
  • Use vertical arrangement or one-screen mirroring so both possible nametable positions are identical.
PPU glitches
ScrollBugA 1st write to PPUSCROLL ($2005) mid-scanline causes fine x to briefly use the CPU open bus value, potentially selecting the wrong pixel for one dot. On one or more CPU/PPU alignments, the timing of the change to fine x may corrupt the background pixel output for one dot.
  • Use the PPU open bus mitigation for the PPU early write glitch or write to register mirrors of PPUSCROLL that match the new fine x value in the low 3 bits of the page number ($2x05 where x is 0-7) to ensure the correct value is on the data bus during the entire write.
  • For the pixel corruption, it is hypothesized that adjusting the timing of when the write takes effect by controlling whether CPU open bus contains the new value or old value of fine x could be used to correct the problem in the vulnerable alignment; it may be that one of the two timings is safe. However, one would need to somehow detect which timing to use.
ScrollBugIf v is updated both from a horizontal or vertical increment and from t at the same time, the incremented component(s) of both t and v are set to the bitwise AND of the two sources. This occurs if the t->v copy from a 2nd PPUADDR ($2006) write occurs at the same time as any of the many horizontal v increments during rendering scanlines or during the vertical v increment at dot 256. It also occurs if the horizontal and vertical increments from a PPUDATA ($2007) access occur on dot 257 or during pre-render dots 280-304.Ensure that 2nd PPUADDR writes for raster effects occur in hblank during dots 257-327, which do not increment v. Note that the t->v copy occurs about 3 dots after the register write begins. It is helpful to leave a margin on either side to ensure timing is reliably safe.PPU-internal bus conflicts
SpritesDesignOAMDATA is only readable on G and H PPUs. Earlier PPUs do not support this (most notably, the common E PPU that was used in both Japan and North America).Don't rely on reading from OAMDATA.
SpritesDesignBecause sprite evaluation and fetching occur on the scanline before the sprites are actually drawn, turning rendering on mid-screen may show incorrect sprite data for one scanline.After turning rendering on mid-screen, keep sprite rendering off for one complete scanline to hide any garbage sprite data that may appear on that scanline.
SpritesDesignOAM uses DRAM (dynamic RAM), which needs to be refreshed frequently to avoid 'decay', the corruption of data stored in OAM. Accessing a byte of OAM refreshes an entire 9-byte row (8 contiguous bytes of OAM1 and 1 byte of OAM2), and all of OAM is accessed as part of sprite rendering on each scanline. This does not occur when the PPU is not rendering, but vblank is short enough that OAM will not decay during it. (Because of this, on lag frames, it is best to avoid OAM DMA because OAM will maintain its state and the OAM buffer in CPU RAM may have incomplete data that could cause garbage sprites or sprite flicker.) However, OAM can decay when rendering is turned off via PPUMASK ($2001) for too long. Only the row pointed to by OAMADDR ($2003) will persist, because the PPU accesses that address every dot. PAL systems avoid OAM decay during their much longer vblank by forcefully refreshing OAM during scanlines 265-310.[3]
  • If rendering has been turned off, perform OAM DMA during the vblank before the first rendered frame.
  • If vblank is being extended by turning rendering off early or on late, OAM DMA should be done late enough in this extended vblank that the time between the start of OAM DMA and start of rendering is not longer than a standard vblank period (21 scanlines).
  • On PAL, OAM DMA must be done early enough in vblank to avoid conflicting with the forced refresh, but the refresh is timed so it begins after any NTSC vblank code would have already completed; that is, if your OAM DMA fits on NTSC, it will also fit on PAL.
SpritesBugSprite 0 hit does not occur at X=255.Do not rely on sprite 0 hit in this column.
SpritesBugSprite overflow detection, meant to set a flag if a scanline attempts to draw more than 8 sprites, does not work correctly. The PPU mistakenly increments (separately) both the high 6 bits and low 2 bits of the OAM1 address instead of just the high 6 bits, causing it to check tile, attributes, and X coordinate bytes for some sprites as though they are Y coordinates. This produces both false positives and false negatives.Ensure that the 8th and 9th sprites on a scanline are adjacent in OAM and that no prior scanlines have 8 or more sprites.Sprite overflow bug
SpritesBugChanging the OAM address to a different row while the PPU is accessing OAM corrupts all 9 bytes of the newly selected row. This corruption typically copies the entire old row to the new row, but may instead cause analog corruption of the new row depending on the timing of the address change. If the rows are the same, no corruption occurs. Both OAM1 and OAM2 live in the same OAM space, and the PPU uses either OAM1ADDR ($2003) or OAM2ADDR to access OAM on every dot. The address it is using can change mid-access, causing corruption, in the following situations:
  • Writing to OAMADDR ($2003) while it is in use will change the OAM1 address mid-access on some CPU/PPU alignments because writes to this register are not synchronized. OAM1ADDR is used on every dot during vblank and forced blank, and on some dots during rendering. This register is also affected by the PPU early write glitch, so writing to OAMADDR changes the address first to the CPU open bus value and then to the intended value. This means that writing once can corrupt 2 rows.
  • Turning rendering off while OAM2 is being accessed immediately changes to the OAM1 address. On some alignments, this happens mid-access, corrupting the OAM1ADDR row.
  • Turning rendering on during a dot when sprite rendering would access OAM2 immediately changes the address from OAM1ADDR to OAM2ADDR. On some alignments, this happens mid-access, corrupting the OAM2ADDR row.
  • On C, E, G and H PPUs, when rendering begins automatically on pre-render dot 0, the address changes from OAM1ADDR to OAM2ADDR mid-access and reliably copies the OAM1ADDR row to the OAM2ADDR row, regardless of alignment. This does not appear to be capable of analog corruption of the OAM2ADDR row and does not occur on letterless, A, B, or D PPUs.
  • OAMADDR ($2003) should not be written except to set it to 0 before OAM DMA.
  • Because writing to OAMADDR is difficult or impossible to do safely, OAM should generally not be updated manually with OAMDATA unless using very specific access patterns that guarantee that all bytes of a corrupted row are written and the address is returned to 0 before rendering next begins.
  • When turning rendering off mid-screen, try to target approximately dots 320-340, a region where both OAM1ADDR and OAM2ADDR are 0, so that corruption does not occur when turning rendering off nor back on.
  • If turning rendering off early to extend vblank, the value of OAM1ADDR is less relevant because OAM will usually be updated with OAM DMA before the next frame. To ensure just OAM2ADDR is 0 so that corruption does not occur at the start of the next frame, rendering can instead be turned off during approximately dots 65-256 if the next scanline does not have any sprites. This is a much wider target than the narrow 320-340 region (although this narrow region is superior because it prevents corruption also when turning rendering off, which is useful on lag frames).
vblank flagDesignIf the program changes the vblank NMI from disabled to enabled through bit 7 of PPUCTRL ($2000) while the vblank flag of PPUSTATUS ($2002) is set, an NMI will trigger immediately. This can cause NMI to occur later than at the start of vblank, or cause more than one NMI in a single vblank.Read PPUSTATUS shortly before enabling NMI to clear the vblank flag.
vblank flagBugReading PPUSTATUS ($2002) at the same time that the vblank flag in bit 7 becomes 1 at the start of vblank prevents it becoming 1 at all that frame, or prevents NMI from being asserted long enough for the CPU to see it.
  • Outside of init code, wait for vblank using NMI, not PPUSTATUS reads.
  • On Dendy, ensure that the cycle length of each PPUSTATUS vblank flag poll loop does not evenly divide the length of the frame; otherwise, the loop may hang forever by triggering this bug every frame. A common cause of this is a poll loop that crosses a page boundary.

Input

  • [2A03] DMC DMA during a controller read ($4016/$4017) causes double clocking, which causes bits of the report to be skipped. A common symptom is spurious presses of Right. Workarounds:

Audio

  • In sweep decrease mode, the carry input differs between the two channels, causing a slightly different sweep rate.
  • Channels set to low frequencies can get silenced by the sweep unit if it is left in increase mode, even if the sweep is otherwise disabled. Workaround:
    • Write $08 to $4001 and $4005 to use decrease mode while disabling sweep.
  • Writing to $4003 or $4007 to change the high byte of the period while a note is playing causes a click as the phase resets. Workarounds:
    1. Write $4003 and $4007 only when they have changed.
    2. The hardware sweep can be used to change the high period bits by clocking it immediately with a write to $4017.[4] (Advanced technique.)
  • The length counter and linear counter share a control bit in $4008. Both counters therefore run simultaneously.
    • Since the linear counter has a shorter maximum duration, longer length counter periods are unusable in practice.
  • Unlike the pulse channels, the triangle does not mute ultrasonic frequencies. These are usually attenuated by the mixer before reaching the speakers.
    • Several games “silence” the triangle by setting period=0, resulting in audible pops.
  • The length counter for DPCM samples ends up reading 1 byte past the end of an otherwise 16 byte aligned sample. This creates a need for 15 bytes of padding between samples.
  • The sample playback frequency table contains a set of 16 pitches tuned to a standard A-440 scale. These appear to have been designed for a limited wavetable synthesis using looped samples, but because of the +1 modifier on sample length, the wavelength is detuned. (Workaround: Use a 17-byte loop and the key of B major.)
  • The frequency table on PAL systems contains 2 slight tuning errors ($4 and $C).[5]
  • Playback of samples generates occasional conflicts with controller reads through $4016/4017 (see above: Input), as well as PPU reads through $2007 (see above: Video). When using DPCM samples, the code must work around these conflicts.
  • Each sample byte is played with the least significant bit first. This bit order is the opposite of how the PPU scans out background tiles.

CPU

The NES CPU has several hardware gotchas, most of which are inherited from the MOS 6502 it is derived from:[6][7]

  • JMP ($xxyy), or JMP indirect, does not advance pages if the lower eight bits of the specified address is $FF; the upper eight bits are fetched from $xx00, 255 bytes earlier, instead of the expected following byte.
  • All of the zero page addressing modes wrap within the zero page. The $xx,x , $xx,y, and ($xx,x) addressing modes all count 254,255,0,1…; none advance 254,255,256,257…
  • The ($xx),y addressing mode wraps when fetching the indirect address if the lower eight bits are stored at $FF (the upper eight bits are fetched from $0000, not $0100).
  • BRK, IRQ, or NMI can mask each other under certain conditions. (see Visual6502wiki/6502_BRK_and_B_bit and Visual6502wiki/6502_Timing_of_Interrupt_Handling ) Not all can happen on the NES.
  • Decimal mode was disconnected from the ALU in the NES’s second-source 6502 to save on patent royalties. Some famiclones, however, use an authentic 6502 with a working decimal mode. (Workaround: Don’t SED, and convert binary numbers to decimal when displaying them.)
  • The 6502 has several unofficial opcodes that were not part of its specification, but do reliably exist on the Famicom and NES. Code using these opcodes may not be portable to other variants of the 6502 CPU (e.g. SNES). Some games rely on the operation of these unofficial opcodes.
  • Page wrapping behavior is reliable on the Famicom/NES but may not be portable to other variants of the 6502 CPU.

References

  1. ↑ Forum: Random glitchy line in Super Mario Bros. on real hardware?
  2. ↑ Forum: 2nd2006_next_level test rom and extensions
  3. ↑ Forum post: PAL forced OAM refresh behavior
  4. ↑ Forum: Vibrato on square without phase reset.
  5. ↑ Forum: PAL DPCM frequency table contains 2 errors.
  6. ↑ 6502.org forum: 6502 hardware gotchas.
  7. ↑ Wikipedia’s article on the 6502.

Edit this page on GitHub