🦊 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 - Where the game code runs, it has a 16-bit width (65536 addresses)
- PPU Memory - Where the background and tiles are stored, it has a 14-bit width (16384 addresses)
- OAM Memory - Where the sprites position, flipping and tile index are stored, it has a 8-bit width (256 addresses)
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:
- 2 Pulse Channels
- 1 Triangle Channel
- 1 Noise Channel
- 1 DMC/Sample Channel
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:
- OUT0, OUT1, OUT2 - Static output pins which can be set by writing to
$4016
(3 lowest bits) - /OE1, /OE2 - Active low pins that are pulsed when reading from
$4016
and$4017
, respectively. These signals are also often referred to as CLK(1/2). Each of these signals is used to enable an external buffer (74ls368
), transferring their contents onto the 2a03 through the data bus.
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:
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.