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.
| Category | Type | Problem | Workaround | Reference |
|---|---|---|---|---|
| Emphasis | Design | On 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. | |
| Emphasis | Design | On 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. | |
| EXT | Design | Enabling 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 |
| Palettes | Design | Color $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 |
| Palettes | Design | When 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. |
| |
| Palettes | Bug | If the low 4 bits of the palette address change while the PPU is reading palette RAM, palette data may corrupt.
| Software that toggles rendering mid-screen should be tested on real hardware across many power cycles to ensure corruption cannot occur.
| Palette corruption |
| Palettes | Bug | Reading 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 access | Bug | Some 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 access | Bug | On 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 |
| Reset | Design | On 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 |
| Scroll | Design | Setting 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. | |
| Scroll | Design | Background 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. | |
| Scroll | Bug | Writing 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. |
| PPU glitches |
| Scroll | Bug | A 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. |
| |
| Scroll | Bug | If 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 |
| Sprites | Design | OAMDATA 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. | |
| Sprites | Design | Because 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. | |
| Sprites | Design | OAM 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] |
| |
| Sprites | Bug | Sprite 0 hit does not occur at X=255. | Do not rely on sprite 0 hit in this column. | |
| Sprites | Bug | Sprite 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 |
| Sprites | Bug | Changing 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:
|
| |
| vblank flag | Design | If 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 flag | Bug | Reading 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. |
|
- [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:
- Standard controller / Power Pad: read the controller multiple times to make sure valid input is read.
- Four Score: check for signature.
- Arkanoid controller: prevent a sudden single frame acceleration.
- If already using DMC IRQ: read the controller in the DMC IRQ handler.
- Run an OAM DMA before reading the controller to align the CPU clock to the even/odd phase of the APU clock.
- 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:
- Write $4003 and $4007 only when they have changed.
- 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.
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.
- ↑ Forum: Random glitchy line in Super Mario Bros. on real hardware?
- ↑ Forum: 2nd2006_next_level test rom and extensions
- ↑ Forum post: PAL forced OAM refresh behavior
- ↑ Forum: Vibrato on square without phase reset.
- ↑ Forum: PAL DPCM frequency table contains 2 errors.
- ↑ 6502.org forum: 6502 hardware gotchas.
- ↑ Wikipedia’s article on the 6502.