Complete reference for all SNES hardware registers commonly used in game development.
Table of Contents
PPU Registers ($2100-$213F)
Display Control
$2100 - INIDISP (Display Control)
| Bit | Name | Description |
| 7 | Force Blank | 1 = Screen off (black), VRAM/OAM/CGRAM accessible |
| 6-4 | - | Unused |
| 3-0 | Brightness | 0 = Black, 15 = Full brightness |
#define REG_INIDISP
Display control (W).
Definition registers.h:49
$2101 - OBJSEL (Sprite Settings)
| Bit | Name | Description |
| 7-5 | Size | Sprite size combination (see table) |
| 4-3 | Name Select | Sprite tile address offset |
| 2-0 | Base | Sprite tile base address (/8192) |
Sprite Sizes:
| Value | Small | Large |
| 0 | 8x8 | 16x16 |
| 1 | 8x8 | 32x32 |
| 2 | 8x8 | 64x64 |
| 3 | 16x16 | 32x32 |
| 4 | 16x16 | 64x64 |
| 5 | 32x32 | 64x64 |
| 6 | 16x32 | 32x64 (undocumented: fullsnes lists 6-7 as reserved; snesdev-wiki documents them) |
| 7 | 16x32 | 32x32 (undocumented, idem) |
#define REG_OBJSEL
Object (sprite) size and base (W).
Definition registers.h:52
OAM (Sprite Memory)
$2102-$2103 - OAMADDL/H (OAM Address)
| Register | Bits | Description |
| $2102 | 7-0 | OAM address low byte |
| $2103 | 7 | OAM priority rotation |
| $2103 | 0 | OAM address bit 9 |
$2104 - OAMDATA (OAM Data Write)
Write sprite data to OAM. Address auto-increments.
#define REG_OAMDATA
OAM data write (W).
Definition registers.h:61
#define REG_OAMADDH
OAM address high (W).
Definition registers.h:58
#define REG_OAMADDL
OAM address low (W).
Definition registers.h:55
Background Mode
$2105 - BGMODE (Background Mode)
| Bit | Name | Description |
| 7 | BG4 Tile Size | 0 = 8x8, 1 = 16x16 |
| 6 | BG3 Tile Size | 0 = 8x8, 1 = 16x16 |
| 5 | BG2 Tile Size | 0 = 8x8, 1 = 16x16 |
| 4 | BG1 Tile Size | 0 = 8x8, 1 = 16x16 |
| 3 | Mode 1 BG3 Priority | 0 = Normal, 1 = High |
| 2-0 | BG Mode | 0-7 (see table below) |
Background Modes:
| Mode | BG1 | BG2 | BG3 | BG4 | Notes |
| 0 | 4-color | 4-color | 4-color | 4-color | 4 layers |
| 1 | 16-color | 16-color | 4-color | - | Most common |
| 2 | 16-color | 16-color | - | - | Offset-per-tile |
| 3 | 256-color | 16-color | - | - | Direct color |
| 4 | 256-color | 4-color | - | - | Offset-per-tile |
| 5 | 16-color | 4-color | - | - | Hi-res 512px |
| 6 | 16-color | - | - | - | Hi-res + offset |
| 7 | 256-color | - | - | - | Rotation/scaling |
#define REG_BGMODE
BG mode and tile size (W).
Definition registers.h:64
Background Tilemap
$2107-$210A - BG1SC-BG4SC (Tilemap Address/Size)
| Bit | Name | Description |
| 7-2 | Address | Tilemap VRAM address (/1024) |
| 1-0 | Size | 00=32x32, 01=64x32, 10=32x64, 11=64x64 |
#define REG_BG1SC
BG1 tilemap address (W).
Definition registers.h:70
$210B-$210C - BG12NBA/BG34NBA (Tile Data Address)
| Bit | Name | Description |
| 7-4 | BG2/BG4 | Tile data address (/4096) |
| 3-0 | BG1/BG3 | Tile data address (/4096) |
#define REG_BG12NBA
BG1/2 tile data address (W).
Definition registers.h:82
Background Scroll
$210D-$2114 - BGnHOFS/BGnVOFS (Scroll Registers)
Write twice: first low byte, then high byte.
#define REG_BG1VOFS
BG1 vertical scroll (W, 2x write).
Definition registers.h:91
#define REG_BG1HOFS
BG1 horizontal scroll (W, 2x write).
Definition registers.h:88
VRAM Access
$2115 - VMAIN (VRAM Address Increment)
| Bit | Name | Description |
| 7 | Increment | 0 = After $2118, 1 = After $2119 |
| 3-2 | Translation | Address translation mode |
| 1-0 | Step | 00 = +1, 01 = +32, 10 = +128, 11 = +128 |
#define REG_VMAIN
VRAM address increment mode (W).
Definition registers.h:112
$2116-$2117 - VMADDL/H (VRAM Address)
Word address (not byte address).
#define REG_VMADDH
VRAM address high (W).
Definition registers.h:124
#define REG_VMADDL
VRAM address low (W).
Definition registers.h:121
$2118-$2119 - VMDATAL/H (VRAM Data Write)
#define REG_VMDATAL
VRAM data write low (W).
Definition registers.h:127
#define REG_VMDATAH
VRAM data write high (W).
Definition registers.h:130
Color (CGRAM)
$2121 - CGADD (CGRAM Address)
Color index (0-255).
#define REG_CGADD
CGRAM address (W).
Definition registers.h:154
$2122 - CGDATA (CGRAM Data Write)
15-bit color: 0BBBBBGGGGGRRRRR
#define REG_CGDATA
CGRAM data write (W).
Definition registers.h:157
Screen Enable
$212C - TM (Main Screen Enable)
| Bit | Name | Description |
| 4 | OBJ | Enable sprites on main screen |
| 3 | BG4 | Enable BG4 on main screen |
| 2 | BG3 | Enable BG3 on main screen |
| 1 | BG2 | Enable BG2 on main screen |
| 0 | BG1 | Enable BG1 on main screen |
#define REG_TM
Main screen designation (W).
Definition registers.h:187
$212D - TS (Sub Screen Enable)
Same bit layout as TM, for sub screen (color math).
CPU Registers ($4200-$421F)
Interrupt Control
$4200 - NMITIMEN (Interrupt Enable)
| Bit | Name | Description |
| 7 | NMI Enable | Enable VBlank NMI |
| 5 | V-IRQ | Enable V-counter IRQ |
| 4 | H-IRQ | Enable H-counter IRQ |
| 0 | Joypad | Enable auto-joypad read |
#define REG_NMITIMEN
Interrupt enable (W).
Definition registers.h:259
$4207-$420A - HTIMEx/VTIMEx (IRQ Timer)
H position (0-339) and V position (0-261/311) for IRQ trigger.
#define REG_VTIMEL
V-count timer low (W).
Definition registers.h:286
#define REG_HTIMEH
H-count timer high (W).
Definition registers.h:283
#define REG_VTIMEH
V-count timer high (W).
Definition registers.h:289
#define REG_HTIMEL
H-count timer low (W).
Definition registers.h:280
$4210 - RDNMI (NMI Status)
| Bit | Name | Description |
| 7 | NMI | 1 = NMI occurred (cleared on read) |
| 3-0 | Version | CPU version |
IMPORTANT: Read this register in NMI handler to acknowledge NMI!
#define REG_RDNMI
NMI flag and version (R).
Definition registers.h:301
unsigned char u8
8-bit unsigned integer (0 to 255)
Definition types.h:47
$4211 - TIMEUP (IRQ Status)
| Bit | Name | Description |
| 7 | IRQ | 1 = IRQ occurred (cleared on read) |
IMPORTANT: Read this register in IRQ handler to acknowledge IRQ!
$4212 - HVBJOY (Blank/Joypad Status)
| Bit | Name | Description |
| 7 | VBlank | 1 = In VBlank |
| 6 | HBlank | 1 = In HBlank |
| 0 | Joypad | 1 = Auto-joypad read in progress |
#define REG_HVBJOY
H/V blank and joypad status (R).
Definition registers.h:307
Math Hardware
$4202-$4203 - WRMPYA/B (Multiply)
8-bit x 8-bit = 16-bit multiplication.
#define REG_WRMPYA
Multiplicand A (W).
Definition registers.h:265
#define REG_RDMPYH
Multiplication result high (R).
Definition registers.h:322
#define REG_WRMPYB
Multiplicand B (W).
Definition registers.h:268
#define REG_RDMPYL
Multiplication result low (R).
Definition registers.h:319
unsigned short u16
16-bit unsigned integer (0 to 65535)
Definition types.h:53
$4204-$4206 - WRDIV (Divide)
16-bit / 8-bit division with remainder.
#define REG_WRDIVB
Divisor (W).
Definition registers.h:277
#define REG_WRDIVL
Dividend low (W).
Definition registers.h:271
#define REG_RDDIVH
Division result high (R).
Definition registers.h:316
#define REG_WRDIVH
Dividend high (W).
Definition registers.h:274
#define REG_RDDIVL
Division result low (R).
Definition registers.h:313
DMA Control
$420B - MDMAEN (General DMA Enable)
Write a bit mask to start DMA on channels 0-7.
#define REG_MDMAEN
DMA enable (W).
Definition registers.h:292
$420C - HDMAEN (HDMA Enable)
Enable HDMA on channels 0-7. HDMA runs automatically each scanline.
DMA Registers ($4300-$437F)
Each channel has 16 bytes of registers at $43x0-$43xF where x = channel (0-7).
Channel Configuration
$43x0 - DMAPx (DMA Parameters)
| Bit | Name | Description |
| 7 | Direction | 0 = A→B (CPU→PPU), 1 = B→A |
| 6 | HDMA Mode | 0 = Absolute, 1 = Indirect |
| 5 | - | Unused |
| 4 | Direction | A-bus address step direction: 0 = increment, 1 = decrement (ignored when bit 3 is set) |
| 3 | Fixed | 1 = fixed A-bus address (no step) — e.g. $08 for a fill from one byte |
| 2-0 | Mode | Transfer mode (see table) |
Transfer Modes:
| Mode | Pattern | Description |
| 0 | 1 byte | Write to single register |
| 1 | 2 bytes | Write to reg, reg+1 alternating |
| 2 | 2 bytes | Write to reg twice |
| 3 | 4 bytes | Write to reg, reg+1 x2 each |
| 4 | 4 bytes | Write to reg, reg+1, reg+2, reg+3 |
#define REG_DMAP(n)
DMA parameters for channel n.
Definition registers.h:367
$43x1 - BBADx (B-Bus Address)
PPU register address (low byte only, $21xx).
#define REG_BBAD(n)
DMA B-bus address for channel n.
Definition registers.h:370
$43x2-$43x4 - A1TxL/H/B (Source Address)
24-bit source address (CPU side).
#define REG_A1TL(n)
DMA A-bus address low for channel n.
Definition registers.h:373
#define REG_A1TH(n)
DMA A-bus address high for channel n.
Definition registers.h:376
#define REG_A1B(n)
DMA A-bus bank for channel n.
Definition registers.h:379
$43x5-$43x6 - DASxL/H (Transfer Size)
Number of bytes to transfer (0 = 65536).
#define REG_DASL(n)
DMA size low for channel n.
Definition registers.h:382
#define REG_DASH(n)
DMA size high for channel n.
Definition registers.h:385
DMA Example
void dmaToVRAM(
const void* src,
u16 vramAddr,
u16 size) {
}
Joypad Registers
$4218-$421F - JOY1-4 (Auto-Read Results)
After auto-joypad read completes (check $4212 bit 0):
| Register | Description |
| $4218 | Joypad 1 low byte |
| $4219 | Joypad 1 high byte |
| $421A | Joypad 2 low byte |
| $421B | Joypad 2 high byte |
| $421C-$421F | Joypads 3-4 (multitap) |
Button Bit Layout:
| Byte | Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
| Low ($4218) | B | Y | Select | Start | Up | Down | Left | Right |
| High ($4219) | A | X | L | R | - | - | - | - |
if (joy & 0x0080) { }
if (joy & 0x8000) { }
if (joy & 0x0800) { }
#define REG_JOY1H
Joypad 1 data high (R).
Definition registers.h:334
#define REG_JOY1L
Joypad 1 data low (R).
Definition registers.h:331
OpenSNES Register Macros
The SDK provides convenient macros in <snes/registers.h>:
Further Reading
- Memory Map - Full address space layout
- OAM - Sprite attribute memory
- Fullsnes - Complete technical reference
SA-1 Registers ($2200-$230E)
The SA-1 is a second 65816 CPU at 10.74 MHz. These registers control inter-processor communication and memory protection. For the library-side register macros, see sa1.h.
| Register | Address | R/W | Description |
| CCNT | $2200 | W | SA-1 CPU control: reset, IRQ, NMI, 4-bit message to SA-1 |
| CRV | $2203-$2204 | W | SA-1 Reset Vector (address SA-1 jumps to on release from reset) |
| SIWP | $2229 | W | SNES I-RAM Write Protection — per-page write-enable bits; OpenSNES writes $FF (all writable) |
| CIWP | $222A | W | SA-1 I-RAM Write Protection — per-page write-enable bits; OpenSNES writes $FF (all writable) |
SIWP/CIWP polarity (resolved). Each bit is a write-**enable** flag for one 256-byte I-RAM page: bit=1 writable, $FF = fully writable. fullsnes, the official Nintendo development manual (§4.1.25) and nocash's hardware debugging agree, and Mesen2, snes9x and luna behave accordingly. The Super Famicom Dev Wiki page states the opposite for $2229/$222A and is wrong (it is also inconsistent with its own $2226/$2227 entries). See KNOWN_LIMITATIONS.md for sources. | SFR | $2300 | R | Status Flags: SA-1 IRQ/NMI pending, 4-bit message from SA-1 |
Typical boot sequence:
; Set SA-1 reset vector to our coprocessor entry point
lda.w #sa1_entry
sta.l $2203 ; CRV low
lda.w #sa1_entry>>8
sta.l $2204 ; CRV high (bank handled by mapping)
; Enable I-RAM writes for both CPUs (bit=1 = write-enable; see note above)
lda #$FF
sta.l $2229 ; SIWP — I-RAM fully writable by the SNES CPU
sta.l $222A ; CIWP — I-RAM fully writable by the SA-1
; Release SA-1 from reset
lda #$00
sta.l $2200 ; CCNT — clear reset bit, SA-1 starts running
SuperFX / GSU Registers ($3000-$303F)
The SuperFX (Graphics Support Unit) is a custom RISC processor with 16 general-purpose registers, hardware multiply, and a built-in pixel PLOT engine. GSU code runs from cartridge ROM/cache and renders into a shared SRAM framebuffer.
| Register | Address | R/W | Description |
| R0-R15 | $3000-$301F | R/W | General-purpose registers (16-bit each). Writing R15 starts the GSU! |
| SFR | $3030-$3031 | R | Status/Flag Register: GO bit (bit 5) = GSU is running |
| PBR | $3034 | R/W | Program Bank Register (GSU code bank) |
| SCBR | $3038 | W | Screen Base Register (framebuffer base address in SRAM) |
| SCMR | $303A | W | Screen Mode: pixel depth + bus ownership (RAN=SRAM, RON=ROM access) |
| VCR | $303B | R | Version Code Register (read-only, identifies GSU revision) |
Key concepts:
- R15 is the program counter — writing to it triggers GSU execution
- SFR GO bit — poll $3030 bit 5 to wait for GSU to finish (STOP instruction clears it)
- SCMR bus ownership — SNES and GSU cannot access ROM/SRAM simultaneously; set RAN/RON bits to hand off bus access between frames
- PLOT — GSU instruction that writes a pixel to the framebuffer at coordinates set by R1 (X) and R2 (Y), using the color in the COLOR register
; Wait for GSU to finish (poll SFR GO bit)
- lda.l $3030
and #$20 ; bit 5 = GO
bne - ; loop while GSU is running
; Start GSU: write entry address to R15
lda.w #gsu_entry
sta.l $301E ; R15 low/high — GSU begins execution