Contents

Basics

NTSC Master clock is 21.47727273 MHz and each PPU pixel lasts four clocks; PAL master clock is 26.6017125 MHz, and each PPU pixel lasts five clocks. $xy refers to a palette color in the range $00 to $3F.

Scanline Timing

Values in PPU pixels (341 total per scanline).

Rendering scanlines (n=240):

name

start

duration

row

notes

short sync

280

25

0-239

black (back porch)

305

4

0-239

colorburst

309

15

0-239

black (the rest of back porch)

324

5

0-239

pulse (background color in grayscale)

329

1

0-239

left border (background color)

330

15

0-239

active

4

256

0-239

if background rendering is disabled, border will be rendered instead

right border (background color)

260

11

0-239

black (front porch)

271

9

0-239

Post-render scanlines (n=2):

name

start

duration

row

short sync

280

25

240-241

black (back porch)

305

4

240-241

colorburst

309

15

240-241

black (the rest of back porch)

324

5

240-241

pulse (background color in grayscale)

329

1

240-241

bottom border (background color)

330

282

240-241

black (front porch)

271

9

240-241

Post-render blanking scanlines (n=3):

name

start

duration

row

short sync

280

25

242-244

black (back porch)

305

4

242-244

colorburst

309

15

242-244

black

324

297

242-244

Vertical sync scanlines (n=3):

name

start

duration

row

long sync

280

318

245-247

black (sync separator)

257

23

245-247

Pre-render blanking scanlines (n=14):

name

start

duration

row

notes

short sync

280

25

248-261

black (back porch)

305

4

248-261

colorburst

309

15

248-261

14 columns on end of row 261 for odd frames, if either background or sprite rendering is enabled

black

324

297

248-261

For a total of 262 scanlines.

Brightness Levels

Voltage levels used by the PPU are as follows - absolute, relative to synch, and normalized between black level and white:

Type

Absolute

Relative

Normalized

Synch

0.781

0.000

-0.359

Colorburst L

1.000

0.218

-0.208

Colorburst H

1.712

0.931

0.286

Color 0D

1.131

0.350

-0.117

Color 1D (black)

1.300

0.518

0.000

Color 2D

1.743

0.962

0.308

Color 3D

2.331

1.550

0.715

Color 00

1.875

1.090

0.397

Color 10

2.287

1.500

0.681

Color 20

2.743

1.960

1.000

Color 30

2.743

1.960

1.000

$xE/$xF output the same voltage as $1D. $x1-$xC output a square wave alternating between levels for $xD and $x0. Colors $20 and $30 are exactly the same.

When grayscale is active, all colors between $x1-$xD are treated as $x0. Notably this behavior extends to the first pixel of the border color, which acts as a sync pulse on every visible scanline.

Color Phases

The color generator is clocked by the rising and falling edges of the ~21.48 MHz clock, resulting in an effective ~42.95 MHz clock rate. There are 12 color square waves, spaced at regular phases. Each runs at the ~3.58 MHz colorburst rate. On NTSC, color $xY uses the wave shown in row Y from the table. NTSC color burst (pure shade -U) uses color phase 8 (with voltages listed above); PAL color burst is believed to alternate between 7 (-U+V) and A (-U-V), so hue is rotated by 15° from NTSC. PAL alternates the broadcast sign of the V component, so on PAL every odd scanline will use the appropriate opposite phase—e.g. phases 5-C are respectively replaced with C-5.

Color Tint Bits

There are three color modulation channels controlled by the top three bits of $2001. Each channel uses one of the color square waves (see above diagram) and enables attenuation of the video signal when the color square wave is high. A single attenuator is shared by all channels.

$2001

Active phase

Complement

Bit 7

Color 8

Color 2 (blue)

Bit 6

Color 4

Color A (green)

Bit 5

Color C

Color 6 (red)

When signal attenuation is enabled by one or more of the channels and the current pixel is a color other than $xE/$xF (black), the signal is attenuated as follows (calculations given for both relative and absolute values as shown in the voltage table above):

relative = relative * 0.746

normalized = normalized * 0.746 - 0.0912

For example, when $2001 bit 6 is true, the attenuator will be active during the phases of color 4.
This means the attenuator is not active during its complement (color A), and the screen appears to have a tint of color A, which is green.

Note that on the Dendy and PAL NES, the green and red bits swap meaning.

It is important to note that while the NES only generates eight (8) samples of NTSC signal per pixel, the wavelength for chroma is 12 samples long. This means that the colors of adjacent pixels get mandatorily mixed up to some degree. For the same reason, narrow black&white details can be interpreted as colors.

Because the scanline length is uneven (341*8 is not an even multiple of 12), the color mixing shifts a little each scanline. This appears visually as a sawtooth effect at the edges of colors at high resolution. The sawtooth cycles every 3 scanlines.

Because also the frame length is uneven (neither 262*341*8 nor (262*341-1)*8 is an even multiple of 12), the color mixing also changes a little every frame. When rendering is normally enabled, the screen is alternatingly 89342 and 89341 cycles long. The combination of these (89342+89341)*8 is an even multiple of 12, which means that the artifact pattern cycles every 2 frames. The pattern of cycling can be changed by disabling rendering during the end of the pre-render scanline; it forces the screen length to 89342 cycles, even if would be 89341 otherwise.

The process of decoding NTSC signal (convert it into RGB) is subject to a lot of study, and there are many patents and different techniques for it. A simple method suitable for emulation is covered below. It is not accurate, because in reality the chroma is blurred much more than is done here (the region of signal sampled for I and Q is wider than 12 samples), and the filter used here is a simple box FIR filter rather than an IIR filter, but it already produces a quite authentic looking picture. In addition, the border region (total of 26 pixels of background color around the 256-pixel scanline) is not sampled.

If you want more saturated colors, just multiply i and q with a factor of your choosing, such as 1.7. If you want brighter colors, just multiply y, i and q with a factor of your choosing, such as 1.1. If you want to adjust the hue, just add or subtract a value from/to phase. If you want to see so called chroma dots, change the begin and end in such manner that you collect a number of samples that is not divisible with 12. If you want to blur the video horizontally, change the begin and end in such manner that the samples are collected from a wider region.

The YIQ colors can be converted into sRGB colors with the following formula, using the FCC-sanctioned YIQ-to-RGB conversion matrix. This produces a value that can be saved to e.g. framebuffer:

The two images below illustrate the NTSC artifacts.
In the left side image, 12 samples of NTSC signal were generated for each NES pixel,
and each display pixel was separately rendered by decoding that 12-sample signal.
In the right side image, 8 samples of NTSC signal were generated for each NES pixel,
and each display pixel was rendered by decoding 12 samples of NTSC signal from the
corresponding location within the scanline.

Per-pixel rendering: 12 samples of NTSC signal per input pixel; the same 12 samples are decoded for each output pixel

Same in grayscale (zero saturation). This illustrates well how the different color values have exactly the same luminosity; only the chroma phase differs.

Same as above, but rendered at 256x240 rather than at 2048x240 and then downscaled

Same in grayscale

The source code of the program that generated both images can be read at [1]. Note that even though the image resembles the well-known Philips PM5544 test card, it is not the same; the exact same
colors could not be reproduced with NES colors. In addition, some parts were changed to better test NES features. For example, the backgrounds for the "station ID" regions (the black rectangles at the top and at the bottom inside the circle) are generated using the various blacks within the NES palette.

Interactive Demo

The following C source code implements the above described algorithm and displays it on screen with interactive mouse control of phase using SDL.