OpenSNES
Modern Open-Source SNES Development SDK
Loading...
Searching...
No Matches
SNES Hardware Register Reference

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
// Turn screen on with full brightness
REG_INIDISP = 0x0F;
// Force blank (screen off)
REG_INIDISP = 0x80;
#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)
// 8x8 small, 16x16 large sprites, tiles at $0000
REG_OBJSEL = 0x00;
// 16x16 small, 32x32 large sprites
REG_OBJSEL = 0x60;
#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.

// Set sprite 0 position
REG_OAMDATA = 100; // X position
REG_OAMDATA = 80; // Y position
REG_OAMDATA = 0; // Tile number
REG_OAMDATA = 0; // Attributes
#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
// Mode 1 with 8x8 tiles
REG_BGMODE = 0x01;
// Mode 1 with 16x16 BG1 tiles
REG_BGMODE = 0x11;
#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
// BG1 tilemap at $7000 (32x32 tiles)
REG_BG1SC = 0x70;
// BG1 tilemap at $7000 (64x64 tiles)
REG_BG1SC = 0x73;
#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)
// BG1 tiles at $0000, BG2 at $4000
REG_BG12NBA = 0x40;
#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.

// Scroll BG1 horizontally by 100 pixels
REG_BG1HOFS = 100 & 0xFF;
REG_BG1HOFS = 100 >> 8;
// Scroll BG1 vertically by 50 pixels
REG_BG1VOFS = 50 & 0xFF;
REG_BG1VOFS = 50 >> 8;
#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
// Increment after high byte write, step by 1
REG_VMAIN = 0x80;
#define REG_VMAIN
VRAM address increment mode (W).
Definition registers.h:112

$2116-$2117 - VMADDL/H (VRAM Address)

Word address (not byte address).

// Set VRAM address to $4000 (word address $2000)
REG_VMADDL = 0x00;
REG_VMADDH = 0x20;
#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)

// Write word $1234 to VRAM
REG_VMDATAL = 0x34; // Low byte
REG_VMDATAH = 0x12; // High byte (triggers increment)
#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).

// Set palette address to color 0
// Set palette address to sprite palette 0 (color 128)
REG_CGADD = 128;
#define REG_CGADD
CGRAM address (W).
Definition registers.h:154

$2122 - CGDATA (CGRAM Data Write)

15-bit color: 0BBBBBGGGGGRRRRR

// Set color to white ($7FFF)
REG_CGDATA = 0xFF; // Low byte
REG_CGDATA = 0x7F; // High byte
// Set color to red ($001F)
REG_CGDATA = 0x1F;
REG_CGDATA = 0x00;
// Set color to green ($03E0)
REG_CGDATA = 0xE0;
REG_CGDATA = 0x03;
// Set color to blue ($7C00)
REG_CGDATA = 0x00;
REG_CGDATA = 0x7C;
#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
// Enable BG1 and sprites
REG_TM = 0x11;
// Enable BG1, BG2, and sprites
REG_TM = 0x13;
#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
// Enable NMI and auto-joypad
REG_NMITIMEN = 0x81;
// Enable NMI, V-IRQ, and auto-joypad
REG_NMITIMEN = 0xA1;
#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.

// Trigger IRQ at scanline 128
REG_VTIMEL = 128;
#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!

// In NMI handler:
volatile u8 dummy = REG_RDNMI; // 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
// Wait for auto-joypad to complete
while (REG_HVBJOY & 0x01) {}
#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.

// Wait 8 cycles
u16 result = REG_RDMPYL | (REG_RDMPYH << 8); // = 100
#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.

REG_WRDIVL = 100 & 0xFF;
REG_WRDIVH = 100 >> 8;
// Wait 16 cycles
u16 quotient = REG_RDDIVL | (REG_RDDIVH << 8); // = 14
u16 remainder = REG_RDMPYL | (REG_RDMPYH << 8); // = 2
#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.

// Start DMA on channel 0
REG_MDMAEN = 0x01;
// Start DMA on channels 0 and 1
REG_MDMAEN = 0x03;
#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
// Mode 1 (for VRAM): write to $2118/$2119 alternating
REG_DMAP(0) = 0x01;
// Mode 0 (for CGRAM): write to $2122
REG_DMAP(0) = 0x00;
#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).

// DMA to VRAM data port ($2118)
REG_BBAD(0) = 0x18;
// DMA to CGRAM data port ($2122)
REG_BBAD(0) = 0x22;
// DMA to OAM data port ($2104)
REG_BBAD(0) = 0x04;
#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).

// Source at $7E:2000
REG_A1TL(0) = 0x00;
REG_A1TH(0) = 0x20;
REG_A1B(0) = 0x7E;
#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).

// Transfer 1024 bytes
REG_DASL(0) = 0x00;
REG_DASH(0) = 0x04;
#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

// DMA 2048 bytes to VRAM at $0000
void dmaToVRAM(const void* src, u16 vramAddr, u16 size) {
// Set VRAM address
REG_VMAIN = 0x80;
REG_VMADDL = vramAddr & 0xFF;
REG_VMADDH = vramAddr >> 8;
// Configure DMA channel 0
REG_DMAP(0) = 0x01; // Mode 1 (2-register)
REG_BBAD(0) = 0x18; // Destination: $2118 (VMDATAL)
REG_A1TL(0) = (u16)src & 0xFF;
REG_A1TH(0) = (u16)src >> 8;
REG_A1B(0) = 0x7E; // Source bank
REG_DASL(0) = size & 0xFF;
REG_DASH(0) = size >> 8;
// Start DMA
REG_MDMAEN = 0x01;
}

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 - - - -
// Read joypad 1
while (REG_HVBJOY & 0x01) {} // Wait for auto-read
u16 joy = REG_JOY1L | (REG_JOY1H << 8);
// Check buttons
if (joy & 0x0080) { /* A pressed */ }
if (joy & 0x8000) { /* B pressed */ }
if (joy & 0x0800) { /* Up pressed */ }
#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>:

// Direct register access
REG_INIDISP = 0x0F;
REG_BGMODE = 0x01;
REG_TM = 0x11;
// DMA channel macros
REG_DMAP(0) = 0x01; // Channel 0
REG_BBAD(1) = 0x22; // Channel 1
REG_A1TL(2) = 0x00; // Channel 2
// PPU status (read-only)
if (REG_HVBJOY & 0x80) { /* In VBlank */ }

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