🦊 smal's blog

NES Memory Reference

Most info here has been compiled from nesdev.org, this is simply a reference page with everything in 1 spot, made to help me with nes homebrew.

The NES has 3 separate address spaces:


CPU Memory

.--------------------------------------.
|        64KB CPU ADDRESS SPACE        |
|--------------------------------------|
| ADDR    | INFO          | BYTES      |
|______________________________________|
| 0x0000  | RAM           | (2,048)    |
| 0x0800  | ...           |            |
| 0x1000  | ...           |            |
| 0x1800  |_...___________|____________|
| 0x2000  | PPU Registers | (8)        |
| 0x2800  | ...           |            |
| 0x3000  | ...           |            |
| 0x3800  |_...___________|____________|
| 0x4000  | 2A03 Registers|_32_________|
| 0x4020  |CARTRIDGE SPACE| 49,119     |
| 0x5000  |               |            |
| 0x5800  |               |            |
| 0x6000  |¯mram¯¯¯¯¯¯¯¯¯¯| [8,192]    |
| 0x6800  |               |            |
| 0x7000  |               |            |
| 0x7800  |               |            |
| 0x8000  |¯/romsel¯¯¯¯¯¯¯| [32,768]   |
| 0x8800  |               |            |
| 0x9000  |               |            |
| 0x9800  |               |            |
| 0xA000  |               |            |
| 0xA800  |               |            |
| 0xB000  |               |            |
| 0xB800  |               |            |
| 0xC000  |               |            |
| 0xC800  |               |            |
| 0xD000  |               |            |
| 0xD800  |               |            |
| 0xE000  |               |            |
| 0xE800  |               |            |
| 0xF000  |               |            |
| 0xF800  |               |            |
'--------------------------------------'
* note: 
    1.work ram and ppu registers are mirrored
      in the "empty" space following them
    2.mram and /romsel refer to CARTRIDGE SPACE
      subdivisions commonly used by mappers

Work RAM

All RAM can be accessed equally by the CPU, however the first two pages are also used for special purposes by the CPU:

.--------------------------------------.
| ADDR    | INFO              | BYTES  |
|---------+-------------------+--------|
| 0x0000  | 2a03 Zero Page    | 256    | 
| 0x0100  | 2a03 Stack Page   | 256    |
| 0x0200  | General Purpose   | 1,536  |
'--------------------------------------'

PPU Registers

A set of 8 registers allow the CPU to communicate with the PPU, access to the PPU and OAM Memory is also done through these registers:

.--------------------------------------.
| ADDR    | INFO              | BYTES  |
|---------+-------------------+--------|
| 0x2000  | PPU CTRL          | 1      |
| write:  +----------------------------| 
|  NMI Enable(V)                1bit   |
|  PPU Master/Slave(P)          1bit   |
|  Sprite Height(H)             1bit   |
|  Background Pattern Table(B)  1bit   |
|  Sprite Pattern Table         1bit   | 
|  Increment Mode(I)            1bit   |
|  Nametable Address(NN)        2bit   |
|---------+-------------------+--------|
| 0x2001  | PPU MASK          | 1      |
| write:  +----------------------------| 
|  Color Emphasis(BGR)          3bit   |
|  Sprite Enable(s)             1bit   |
|  Background Enable(b)         1bit   |
|  Sprite Left Col Enable(M)    1bit   |
|  Background Left Col Enable(m)1bit   |
|  Greyscale Mode(G)            1bit   |
|---------+-------------------+--------|
| 0x2002  | PPU STATUS        | 1      |
| read:   +----------------------------| 
|  VBlank Started(V)            1bit   |
|  Sprite Zero Hit(S)           1bit   |
|  Sprite Overflow(O)           1bit   |
|  ---                          5bit   |
|---------+-------------------+--------|
| 0x2003  | OAM ADDR          | 1      |
| 0x2004  | OAM DATA          | 1      |
| 0x2005  | PPU SCROLL(x,y)   | 1      |
| 0x2006  | PPU ADDR (msb,lsb)| 1      |
| 0x2007  | PPU DATA          | 1      |
'--------------------------------------'
* note: PPU SCROLL and ADDR are written in 
    2 consecutive writes

The OAM and PPU memory spaces can be accessed by the CPU by writing to the respective ADDR/DATA registers, however OAM data should instead be transferred by using the 2a03 DMA feature.

The PPU ADDR auto increments whenever PPU DATA is read/written, this is controlled by the Increment Mode bit in PPU CTRL, 0=increments of 1/going across, 1 = increments of 32/going down

2a03 Registers (APU and IO)

The 2a03 chip contains 32 memory mapped registers, these control the APU (Audio Processing Unit), controller input and OAM DMA, as well as several unused/test registers.

.--------------------------------------.
| ADDR    | INFO              | BYTES  |
|---------+-------------------+--------|
| 0x4000  | APU Pulse 1       | 4      |
| 0x4004  | APU Pulse 2       | 4      |
| 0x4008  | APU Triangle      | 4      |
| 0x400C  | APU Noise         | 4      |
| 0x4010  | APU DMC/Sample    | 4      |
| 0x4014  | OAM DMA control   | 1      |
| 0x4015  | APU channel enable| 1      |
| 0x4016  | Joystick 1        | 1      |
| 0x4017  | Joystick 2        | 1      |
| 0x4018  | Unused/Test       | -      |
'--------------------------------------'

TODO: Info about registers here

APU

The APU (Audio Processing Unit) produces the audio of the NES, there are 5 different channels, most of which function in differing ways:

Pulse Channels

.--------------------------------------.
| ADDR    | INFO              | BYTES  |
|---------+-------------------+--------|
| 0x4000  | Duty and Volume   | 1      |
|         +----------------------------|
|  Duty Cycle                   2bit   |
|  Length Counter Halt          1bit   |
|  Constant Volume              1bit   |
|  Volume/Envelope              4bit   |
|---------+----------------------------|
| 0x4001  | Sweep Control     | 1      |
|         +----------------------------| x2 Pulse Channels
|  Enable(E)                    1bit   |
|  Divider P+1 half frames (P)  3bit   |
|  Negate flag (N)              1bit   |
|  Shift Count(SSS)             3bit   |
|---------+----------------------------|
| 0x4002  | Freq Low          | 1      |
| 0x4003  | Counter/Freq Hi   | 1      |
|         +----------------------------|
|  Length counter load          5bit   |
|  Freq Hi                      3bit   |
'--------------------------------------'

Triangle Channel

.--------------------------------------.
| ADDR    | INFO              | BYTES  |
|---------+-------------------+--------|
| 0x4008  | Linear Counter    | 1      |
| 0x400A  | Freq Low          | 1      |
| 0x400B  | Counter/Freq Hi   | 1      |
'--------------------------------------'

Noise Channel

.--------------------------------------.
| ADDR    | INFO              | BYTES  |
|---------+-------------------+--------|
| 0x400C  | Length/Volume     | 1      |
| 0x400E  | Mode/Period       | 1      |
| 0x400F  | Counter           | 1      |
'--------------------------------------'

DMC Channel

.--------------------------------------.
| ADDR    | INFO              | BYTES  |
|---------+-------------------+--------|
| 0x4010  | Flags and Rate    | 1      |
| 0x4011  | Direct Load       | 1      |
| 0x4012  | Sample Address    | 1      |
| 0x4013  | Sample Length     | 1      |
'--------------------------------------'

Channel Control

.--------------------------------------.
| ADDR    | INFO              | BYTES  |
|---------+-------------------+--------|
| 0x4010  | Status            | 1      |
|  write  +----------------------------|
|    ---                        3bit   |
|    Enable DMC                 1bit   |
|    Enable Noise               1bit   |
|    Enable Triangle            1bit   |
|    Enable Pulse 2             1bit   |
|    Enable Pulse 1             1bit   |
|  read                                |
|    DMC Interrupt              1bit   |
|    Frame Interrupt            1bit   |
|     ---                       1bit   |
|    DMC Active                 1bit   |
|    Length Counters (T/N/2/1)  4bit   |
'--------------------------------------'

IO

The 2a03 contains built-in IO capabilities in the form of 5 pins:

The Famicom, NES and Famiclones each wire these signals differently to their respective peripherals and ports. Requiring differing code and hardware design for each.

OAM DMA

When the OAM_DMA register is written to, the 256 bytes at the specified page are written to the OAM_DATA register of the PPU, the CPU is paused during the 513-514 cycles of this process.

This DMA is the proper way to update OAM and should be performed every vblank, as the OAM memory will become corrupted if it is not regularly refreshed.

It is common practice to initialize the PPU's OAM_ADDR register to 0 before performing this write.

Cartridge Space

The arrangement Cartridge Space will vary depending on what memory mapper is being used, with most mappers following a variation of the following:

.---------------------------------------.
| ADDR    | INFO               | BYTES  |
|---------+--------------------+--------|
| 0x4020  | Start of Cart Space|        |
| 0x6000  | Common MRAM Area   | 8192   |
| 0x8000  | /ROMSEL area       | 32767  |
'---------------------------------------'
/ROMSEL is a signal sent by the NES to the cartridge slot, 
this is used in the majority of mappers for ROM access.

Further Some areas have fixed purposes by the 2a03 chip:

.---------------------------------------.
| ADDR    | INFO               | BYTES  |
|---------+--------------------+--------|
| 0xC000  | (DPCM Sample Area) | 16369  |
| 0xFFFA  | 2a03 NMI Vector    | 2      |
| 0xFFFC  | 2a03 Reset Vector  | 2      |
| 0xFFFE  | 2a03 IRQ/BRK Vector| 2      |
'---------------------------------------'
The Vectors contain the addresses that the CPU 
will jump to whenever certain types of interrupt occur.

PPU Memory

.--------------------------------------.
|        16KB PPU ADDRESS SPACE        |
|--------------------------------------|
| ADDR    | INFO              | BYTES  |
|---------+-------------------+--------|
| 0x0000  | Patterns/Cart     | 8,192  | 
| 0x2000  | Nametables/VRAM   | 4,096* |
| 0x3F00  | Palette RAM       | 32     |
'--------------------------------------'
* The NES only has 2,048 bytes of internal VRAM, 
  unless the cartridge has aditional VRAM this 
  space will be mirrored in either an 
  horizontal or vertical manner.

PatternTables

A PatternTable is a 4096 byte area of memory that defines the graphics to be used for the tiles that constitute the backgrounds and sprites.

There exist 2 different pattern tables, the sprites and tiles can independently select either of these to use, via the appropriate bits in the PPU_CTRL register. They are located inside the cartridge and can either be in ROM or RAM, as well as possible bankings offered by the mapper being used.

Each tile is composed of 16 bytes in a bit-plane format:

.--------------------------------------.
| ADDR    | INFO              | BYTES  |
|---------+-------------------+--------|
| 0x0000  | Color Bit 0       | 8      | x256 tiles x2 Pattern tables 
| 0x0008  | Color Bit 1       | 8      |
'--------------------------------------'

NameTables

A NameTable is a 1024 byte area of memory that lays out a background, the PPU has 4 different NameTables located at 0x2000, 0x2400, 0x2800 and 0x2C00. Note that unless the cartridge includes extra vram, 2 of these NameTables will be mirrors of each other, either in a horizontal or vertical fashion, as the NES only has 2Kb of internal VRAM.

.--------------------------------------.
| ADDR    | INFO              | BYTES  |
|---------+-------------------+--------|
| 0x2000  | Tiles (32x30)     | 960    | x4 Nametables 
| 0x23C0  | Colors (8x8)      | 64     |
'--------------------------------------'

Each byte of the colors attribute table selects the palette to be used in a 32x32 (2x2 tile) area.

Palette Memory

Palette memory holds the colors to be used by each of the 16 palettes. Each palette has 4 colors but the first is always either the background color (background palettes) or transparent (sprite palettes).

.--------------------------------------.
| ADDR    | INFO              | BYTES  |
|---------+-------------------+--------|
| 0x3F00  | Background Color  | 1      |
| 0x3F01  | BG Palette 0      | 3      |
| 0x3F05  | BG Palette 1      | 3      |
| 0x3F09  | BG Palette 2      | 3      |
| 0x3F0D  | BG Palette 3      | 3      |
| 0x3F11  | SP Palette 0      | 3      |
| 0x3F15  | SP Palette 1      | 3      |
| 0x3F19  | SP Palette 2      | 3      |
| 0x3F1D  | SP Palette 3      | 3      |
'--------------------------------------'

The palette colors are selected from the 64 color master palette of the NES:

NES Palette


OAM Memory

OAM memory holds the data for the 64 sprites/objects:

.--------------------------------------.
|        256b OAM ADDRESS SPACE        |
|--------------------------------------|
| ADDR    | INFO              | BYTES  |
|---------+-------------------+--------|
| 0x00    | Sprite Y Position | 1      |  
| 0x01    | Sprite Tile Index | 1      |
| 0x02    | Sprite Attributes | 1      |
|         +----------------------------|
|  Flip Vertically              1bit   |
|  Flip Horizontally            1bit   | x 64 sprites
|  In front/In back of bg       1bit   |
|  ---                          3bit   |
|  Palette of sprite (4 to 7)   2bit   |
|---------+--------------------+-------|
| 0x03    | Sprite X Position  | 1     |
'--------------------------------------'

Note that a max of 8 sprites can be shown on each horizontal line, with the lower sprites taking precedence. To avoid invisible sprites their order should be quickly swapped (flickered) in software.