65816 Assembly programming for
the SNES and Super Famicom
The Super Nintendo (AKA Super
Famicom) was the successor to the NES... relatively late to
the 16 bit game, the SNES made the unusual choice to not use the
68000 CPU for its processor, favoring the slower and less efficient
65186... this is rumored to be because during development backwards
compatibility with the NES was planned...
The 65186 is a true 16 bit CPU, but it has a 6502 compatibility
mode, in which it works exactly like a 6502, and we can perform
basic SNES programming without even using a 16 bit command!
Cpu
3.58 mhz 65816
Ram
128k
Vram
64k
Tiles
1024 x 4 or 16 color
Sprites
128 onscreen (32 per line)
Resolution
512x448
Colors
256 (16 per tile) from 32768
Sound
chip
64k DSP 8 channel SPC700
The UK SNES PSU outputs 9V AC the Japanese Super Famicom PSU
outputs 10V DC - CENTER NEGATIVE BE CAREFUL! adapters that
output AC are very rare and will kill most systems - and
Center negative was common for 80's Japanese systems, but is
very rare these days - YOU HAVE BEEN WARNED!
The 65816 has a 16 bit address bus,
it generally works with a 16 bit address, and a 8 bit Data Bank
register (DB)
The ZeroPage and Stack pointer are relocatable, but default to the
6502 normal of $0000-$01FF
Our minimal 32k cartridge will appear in rom from $8000, and
execution will start there, the CPU will default in 8 bit mode.
Bank
16 bit address
Purpose
$00-$3F
$0000-$1FFF
RAM
$2000-$5FFF
Hardware Regs
$6000-$7FFF
Expand (???)
$8000-$FFFF
Cartridge Rom
$70
$0000-$7FFF
Battery Backed Up Ram
$7E
$0000-$1FFF
Scratchpad RAM (same as
bank $00 to $3F)
$2000-$FFFF
RAM
$7F
$0000-$FFFF
RAM
VRAM
VRAM addresses are re-configurable, Here's a sample map which we'll be
working around in these tutorials ... Note the addresses are in WORDS (2
bytes in each address)... so the 64k memory is accessed by addresses
$0000-$7FFF
Address
Use
$0000
BG1 Tilemap
$1000
Tile Patterns
$4000
Sprite Patterns
$7FFF
Last byte of ram
Cartridge Header
Address
Bytes
Category
Purpose
Example
$FFC0
21
Rom
Cartridge
title
(Space Padded)
Test
Rom9012345678901
$FFD6
1
Rom
ROM/RAM
information
on cart.
$00
$FFD7
1
Rom
ROM
size.
$01
$FFD8
1
Rom
RAM
size.
$00
$FFD9
1
Rom
Developer
ID
code.
$00
$FFDB
1
Rom
Version
number.
$00
$FFDC
2
Rom
Checksum
complement.
$????
$FFDE
2
Rom
Checksum.
$????
$FFE0
2
65816
Mode
$0000
$FFE2
2
65816
Mode
$0000
$FFE4
2
65816
Mode
COP
Vector
$0000
$FFE6
2
65816
Mode
Brk
Vector
$0000
$FFE8
2
65816
Mode
Abort
Vector
(Unused)
$0000
$FFEA
2
65816
Mode
NMI
Vector
(V-blank)
$0000
$FFEC
2
65816
Mode
Reset
Vector
(Unused)
$0000
$FFEE
2
65816
Mode
IRQ
Vector
(H/V/External)
$0000
$FFF0
2
6502
Mode
$0000
$FFF2
2
6502
Mode
$0000
$FFF4
2
6502
Mode
COP
Vector
$0000
$FFF6
2
6502
Mode
BRK
Vector
(unused)
$0000
$FFF8
2
6502
Mode
Abort
Vector
(Unused)
$0000
$FFFA
2
6502
Mode
NMI
Vector
(V-blank)
$0000
$FFFC
2
6502
Mode
Reset
Vector
(6502 Mode)
$8000
$FFFE
2
6502
Mode
IRQ/BRK
Vector
$0000
Tile Definitions
Tile definitions use 4 bitplanes for
16 colors, and tile definitions are 8x8 - so 32 bytes ... Data is
transferred in Words, and rather strangely we send bitplane 1+2 of lines,
one at a time... then we do the same for bitplanes 3 and 4
Tilemap Data
The Tilemap will typically start from address $0000, each entry contains two
bytes
F
E
D
C
B
A
9
8
7
6
5
4
3
2
1
0
V
H
L
P
P
P
T
T
T
T
T
T
T
T
T
T
V=vflip
H=hflip L=layer (in
front of sprites) P=palette
T=tile number
(0-1023)
Palette Definitions
Palettes are defined by something called 'CGRAM'
there are 256 palette entries...
Because we can only select 8 palettes for Tiles/Sprites, assuming
our sprites and tiles are 16 colors the first 128 colors will be
used by Tiles, and the second 128 will be used by Sprites
To define a palette entry we need to select a palette entry number
by writing it's number as a byte to $2121
We have to write two bytes to $2122... we write them in REVERSE
order, so the low byte is written first!
Address
Name
Purpose
Bits
Details
$2121
CGADD
Colour
# (or pallete) selection
xxxxxxxx
x=color
(0-255)
$2122
CGDATA
Colour
data
-bbbbbgg
gggrrrrr
Color
Data BGR
H byte (sent second)
L byte (sent first)
F
E
D
C
B
A
9
8
7
6
5
4
3
2
1
0
-
B
B
B
B
B
G
G
G
G
G
R
R
R
R
R
Color
Number
Type
Palette
Number
0
Background
0
16
Background
1
32
Background
2
48
Background
3
64
Background
4
80
Background
5
96
Background
6
112
Background
7
128
Sprite
0
144
Sprite
1
160
Sprite
2
176
Sprite
3
192
Sprite
4
208
Sprite
5
224
Sprite
6
240
Sprite
7
255
�
�
Sprite Definitions - Overview
Sprites use as special bank of 512 bytes of 'OAM' memory for their
definitions... they also use standard VRAM for the pattern data.
In theory the Pattern data can be relocated... but in practice it's best to
just assume it's at $4000 (address in 16 bit words)
Sprites can be various sizes - a 'default size' is set for all sprites...
and certain selected sprites can be double size...
this is, however a bit tricky... lets say you have the default size as
8x8... and one double size 16,16 sprite
If we point this sprite 'double size' 16x16 sprite to pattern 'Tile
0', the 4 8x8 chunks will be made up of tile numbers:
1
2
16
17
Lets look at this example of a 16x16
sprite in AkuSprite Editor... Akusprite editor is designed for 8x8
sprites, but we can export a 16x16 one in the following way
If we want to export this quickly, so we can use it as a single
doublesize sprite, one option is to tick the 'FixedSize' tickbox,
and set the size to 128,16
This will export the sprite correctly - of course there will be a
lot of unused space in the exported file... so we would want to
combine all our 16x16 together into a single image
Generally it would be easier to build up a 16x16 sprite from 4 8x8
sprites
Sprite Definitions - Ports Used
Address
Name
Purpose
Bits
Details
$2101
OBSEL
OAM
size
(Sprite)
SSSNNBBB
S=size
(See below) N=Bame addr B=Base addr
$2102
OAMADDL/L
OAM
address
LLLLLLLL
a=oam
address
L
$2103
OAMADDL/H
OAM
address
R000000H
R=
priority Rotation / H=oam address MSB
$2104
OAMDATA
OAM
data
????????
$212C
TM
Main
screen
designation
---S4321
S=sprites
4-1=enable
Bgx
$2138
OAMDATAREAD
Read
data
from OAM
Sprite Sizes
Sprite size is set for all sprites, but each sprite can either be normal
or large defined by a single bit from address $0100 onwards
SSS
Size (Normal / Large)
0 %000
8x8 / 16x16
1 %001
8x8 / 32x32
2 %010
8x8 / 64x64
3 %011
16x16 / 32x32
4 %100
16x16 / 64x64
5 %101
32x32 / 64x64
6 %110
16x32 / 32x64 (Undocumented)
7 %111
16x32 / 32x32 (Undocumented)
Sprite Definitions - OAM Data
The SNES has 128 hardware sprites.
Selecting a HL address is done by setting registers $2102 (L) and
$2103 (H)
Each address below $0100 holds Two Bytes (The first table)...each address
$0100 or above holds just one!... All data is written via the $2104
Note, Sprites use Palettes from 128... so the color palette used is the
value in CCC +128
Sprite data should only be written to Vram during Vsync.
Address
Byte
1
Byte
2
Meaning
SprNum
$0000
XXXXXXXX
YYYYYYYY
X=Xpos (bits
0-7) Y=Ypos
0
$0001
TTTTTTTT
YXPPCCCT
T=Tile
Y=yflip X=xflip P=priority compared to BG C=palette +128
0
$0002
XXXXXXXX
YYYYYYYY
X=Xpos (bits
0-7) Y=Ypos
1
$0003
TTTTTTTT
YXPPCCCT
T=Tile
Y=yflip X=xflip P=priority compared to BG C=palette +128
1
�
�
�
�
�
�
�
�
�
�
$00FE
XXXXXXXX
YYYYYYYY
X=Xpos (bits
0-7) Y=Ypos
127
$00FF
TTTTTTTT
YXPPCCCT
T=Tile
Y=yflip X=xflip P=priority compared to BG C=palette +128
127
$0100
SXSXSXSX
(no 2nd byte)
S=LargeSize
(1=Large size) sprite X=Xpos (bit 8)
0-3
$0101
SXSXSXSX
(no 2nd byte)
S=LargeSize
(1=Large size) sprite X=Xpos (bit 8)
4-6
�
�
�
�
$011F
SXSXSXSX
(no 2nd byte)
S=LargeSize
(1=Large size) sprite X=Xpos (bit 8)
124-127
SNES Joypad
Presumably because of the planned backwards compatibility, the SNES
actually uses the same ports in the same way as the NES! However, because
the SNES has more buttons, we can do 16 reads rather than 8 to get the
extra buttons.
Mode
Port
Purpose
7
6
5
4
3
2
1
0
Write
$4016
Strobe
(reset)
-
-
-
-
-
-
-
Strobe
Read
$4016
Joypad
1/3
-
-
-
-
-
Mic
Pad3
Pad1
Read
$4017
Joypad
2/4
-
-
-
-
-
-
Pad4
Pad2
F
E
D
C
B
A
9
8
7
6
5
4
3
2
1
0
-
-
-
-
R
L
X
A
Right
Left
Down
Up
Start
Select
Y
B
The SNES firmware also reads in these ports automatically, and stores
these in ram - we can use these versions if we prefer.
Sound on the SNES via the SPC700
To make sound on the SNES we have to use it:s SPC700
processor! what's the SPC700? well it's an 8 bit dedicated sound CPU with
64k of isolated memory - meaning that it can't be directly accessed from
the main CPU!
The SPC700 is a great sound processor, used by the Super Nintendo and many
other systems like... er.. the super nintendo!
Ok, so nothing else uses it, and it's a total pain! while it's sound
ability is good, its a real hassle to program... it uses it's own special
instruction set, and its bytecode matches no other CPU, and data has to be
transferred to it using a special procedure because we can't access it's
memory directly.
The CPU is 8 bit and Little endian, so $1234 is stored in ram as
$34,$12... it's registers are the same as the 6502, but it has some 16 bit
commands that use YA as a 16-bit pair like the Z80
it has a ZeroPage, but the ZeroPage is referred to as the
DirectPage, as it can be at $00xx or $01xx... $01xx is also used as the
Stack
Most data transfer commands are done with MOVe commands (like the 68000),
but like the z80, the destination is on the left... for example:
mov a, #$00 ;Set A to $00
Pointers to Sound samples are stored in a single block.. this
block must be byte aliened, and has 256 sound definitions - each of
which contains 2 pointers... one for the start of the sample, and
one for the loop
For example, take the example definition, this will be loaded into
SPC700 ram at $300... we would tell the sound chip to use $03 as the
memory position using the "DIR" sound register $5D
The sound samples themselves are made up of 9 byte chunks, the first
byte is a header, and the other 16 nibbles are the sound data... the
final sample in the sound should have the End bit set in the header,
and the Loop bit if you wish
align
8
SFXBank:
;We're going to load this into $0300
dw SFXBank_Sound1-SFXBank+$300
;Sample 0
main
dw SFXBank_Sound1-SFXBank+$300
;Sample 0
Loop align 4
SFXBank_Sound1: ;
SSSSFFLE S= bitshift (0-12) FF=Filter L=Loop E=End
db
%11000111,$FF,$F0,$F0,$F0,$F0,$F0,$F0,$F0
;
01 23 45 67 89 AB CD EF
The SPC700 Memory Map
Start
End
Purpose
0000
00EF
Zero
Page / Direct Page
00F0
00F0
Unused
00F1
00F1
Control
Port (Timers & reset)
00F2
00F2
Sound
Register Select
00F3
00F3
Sound
Register Value
00F4
00F4
Link to
65816 address $2140
00F5
00F5
Link to
65816 address $2141
00F6
00F6
Link to
65816 address $2142
00F7
00F7
Link to
65816 address $2143
0100
01FF
Stack
0200
FFBF
RAM
FFC0
FFFF
ROM
The SPC700 Registers
Address
Register
Description
Bits
Meaning
c0
VOL (L)
Left Volume
-VVVVVVV
Volume
c1
VOL (R)
Right Volume
-VVVVVVV
Volume
c2
P (L)
Pitch L
PPPPPPPP
Pitch
c3
P (H)
Pitch H
--PPPPPP
Pitch
c4
SRCN
Source number
(references the source directory)
SSSSSSSS
Source
c5
ADSR (1)
If bit7 is
set, ADSR is enabled. If cleared GAIN is used.
EDDDAAAA
Enable,
Dr, Ar
c6
ADSR (2)
These two
registers control the ADSR envelope.
LLLRRRRR
sL,sR
c7
GAIN
This register
provides function for software envelopes.
Echo delay,
4-bits, higher values require more memory.
----EEEE
Echo
delay
fF
COEF
8-tap FIR
Filter coefficients
SCCCCCCC
Signed
Coeficcient
c =
Channel 0-7� f= filter coefficient
0-7
SPC700 and VASM
Vasm doesn't support the SPC700, but we can simulate the commands
with macros, for example, we can set an immediate value with the
macro to the right
macro
s_mov_a_ii,aval ;Set
A=immediate value
db $E8
db \aval
endm
If we want to do a relative jump, we can calculate the relative
offset with Destination-(*+1)
s_bne_r SoundCallPause-(*+1)
If we want to include our SPC700 code in our main rom, we'll need
to adjust call addresses for the changing location, to do this we
can use the formula [DestinationLabel]- [StartOfProgramInMainRom]+
[DestinationOfProgramInSPC700Ram]