return to Choose a Color Scheme: Dark
Print Mode

Learn Multi platform 65816 Assembly Programming... For Ultimate Power!

This page is Under Construction.... 'Learn 65816 is planned for 2021'

While most hardware developers moved to the 68000, and those looking for a more powerful 8 bit could move to the 6809, there was 'another alternative'.... The 65816

Backwards compatible with the 65C02, and with full 16 bit capabilities, the 65816 (AKA W65C816) gave the 'best of both worlds'... although 1/3 slower than the 68000 mhz for mhz, the 65816 was powerful and capable, and was the driving force behind the SNES and APPLE IIGS...

It's 24 bit address bus gives it the capability to address 16 megabytes - like the 68000

The 65802 is essentially a 65816 with an 8 bit address bus.


If you want to learn 6809 get the Cheatsheet! it has all the 6809 commands, It will give you a quick reference when you're stuck or confused, which will probably happen a lot in the early days!

We'll be using Macroassembler AS for our assembly in these tutorials... VASM is an assembler which supports rarer CPU's like 6809 and 65816 and many more, and also supports multiple syntax schemes...

You can get the source and documentation for AS from the official website HERE

What is the 65816 and what are 'bits' You can skip this if you know about binary and Hex (This is a copy of the same section in the Z80 tutorial)
The 65816 is an 16-Bit processor with a 24 bit Address bus!... it has two 8 bit accumulatrors, A and B, that can be combined to make up one 16 bit accumulator C (AB)... it will default to 8 bit mode, in which it pretty much works like a 65c02... so much so, we're able to develop for the SNES in out 6502 tutorials!
What's 8 bit... well, one 'Bit' can be 1 or 0
four bits make a Nibble (0-15)
two nibbles (8 bits) make a byte (0-255)
two bytes (16 bits) make a word (0-65535)

And what is 65535? well that's 64 kilobytes ... in computers Kilo is 1024, because four bytes is 1024 bytes
64 kilobytes is the amount of memory a basic 8-bit system can access

The 6502 is 8 bit so it's best at numbers less than 256... it can do numbers up to 65535 too more slowly... and really big numbers will be much harder to do! - we can design our game round small numbers so these limits aren't a problem.
With the 65816 there is a 16 bit mode, this gives us more power, and allows us to work in pairs of bytes more easily!

You probably think 64 kilobytes doesn't sound much when a small game now takes 8 gigabytes, but that's 'cos modern games are sloppy, inefficient,  fat and lazy - like the basement dwelling losers who wrote them!!!
ASM code is small, fast, and super efficient - with ASM you can do things in 1k that will amaze you!

Numbers in Assembly can be represented in different ways.
A 'Nibble' (half a byte) can be represented as Binary (0000-1111) , Decimal (0-15) or  Hexadecimal (0-F)... unfortunately, you'll need to learn all three for programming!

Also a letter can be a number... Capital 'A'  is stored in the computer as number 65!

Think of Hexadecimal as being the number system invented by someone wit h 15 fingers, ABCDEF are just numbers above 9!
Decimal is just the same, it only has 1 and 0.

In this guide, Binary will shown with a % symbol... eg %11001100 ... hexadecimal will be shown with $ eg.. $FF.
Assemblers will use a symbol to denote a hexadecimal number, in 6502 programming $ is typically used to denote hex, and # is used to tell the assembler to tell the assembler something is a number (rather than an address), so $# is used to tell the assembler a value is a Hex number
In this tutorial VASM will be used for all assembly, if you use something else, your syntax may be different! 

Decimal 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ... 255
Binary 0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111   11111111
Hexadecimal 0 1 2 3 4 5 6 7 8 9 A B C D E F   FF

Another way to think of binary is think what each digit is 'Worth' ... each digit in a number has it's own value... lets take a look at %11001100 in detail and add up it's total

Bit position 7 6 5 4 3 2 1 0
Digit Value (D) 128 64 32 16 8 4 2 1
Our number (N) 1 1 0 0 1 1 0 0
D x N 128 64 0 0 8 4 0 0
128+64+8+4= 204            So %11001100 = 204 !

If a binary number is small, it may be shown as %11 ... this is the same as %00000011
Also notice in the chart above, each bit has a number, the bit on the far right is no 0, and the far left is 7... don't worry about it now, but you will need it one day!

If you ever get confused, look at Windows Calculator, Switch to 'Programmer Mode' and  it has binary and Hexadecimal view, so you can change numbers from one form to another!
If you're an Excel fan, Look up the functions DEC2BIN and DEC2HEX... Excel has all the commands to you need to convert one thing to the other!

But wait! I said a Byte could go from 0-255 before, well what happens if you add 1 to 255? Well it overflows, and goes back to 0!...  The same happens if we add 2 to 254... if we add 2 to 255, we will end up with 1
this is actually usefull, as if we want to subtract a number, we can use this to work out what number to add to get the effect we want

Negative number -1 -2 -3 -5 -10 -20 -50 -254 -255
Equivalent Byte value 255 254 253 251 246 236 206 2 1
Equivalent Hex Byte Value FF FE FD FB F6 EC CE 2 1

All these number types can be confusing, but don't worry! Your Assembler will do the work for you!
You can type %11111111 ,  &FF , 255  or  -1  ... but the assembler knows these are all the same thing! Type whatever you prefer in your ode and the assembler will work out what that means and put the right data in the compiled code!

The 65816 Registers

the 65816 has an 'Emulation mode' in which it works as an 8-bit 65c02, but we can turn it into 'Native mode' for 16 bit operation... the cpu will start in 'Native' 8 bit mode

The 65816 extends the 6502 with an extra accumulator (like the 6809)... it also adds two registers DBR and PBR which are effectively like the extra 8 bits to allow for 24 bit addressing (similar to segments on the 8086)

The Zero Page is now called the Direct Page - because it no longer has to be at $0000xx
24 Bit 16 Bit 8 Bit Use cases
accumulator A
A 8 Bit Accumulator
accumulator B
B 8 Bit Accumulator
16-Bit Accumulator  C B A A+B combined to make a 16 bit accumulator
Processor flag register
P Flags
Indirect X DBR X Indirect Register
Indirect Y DBR Y Indirect Register
Hardware Stack  Pointer  00 S Stack 
Program Counter PBR PC Running Command
Direct page Register 00 D
D+nn Zero page is relocatable on 65816
Databank Register DBR
Program Bank Register PBR

    Flags: NVMXDIZC / -------E
Name Meaning
N Negative 1=Negative
V Overflow 1=True
M Memory / Accumulator Select 1=8bit / 0=16bit
X XY index register select 1=8bit / 0=16bit
D Decimal mode 1=True
I IRQ disable 1=Disable
Z Zero 1=Result Zero
C Carry 1=Carry
E Emulator mode
(accessed via carry flag with XCE)
0=native 16 bit
1=Emulated 8 bit
eSwitching from 16 bit mode to 8 bit mode will set the top byte of X/Y to 0...  they will still be 0 if you switch back to 16 bit mode - so the top byte of X+Y will have been lost!
The C register (16 bit accumulaor) becomes the combination of BA

if data is transfered between X and A, when the source is 16 bit, and the destination is 8 bit, then only the Low byte will be transferred.

when we surround something with brackets () we're loading indirectly from that address, eg LDA ($12) loads a byte from the address in zeropage $12+13...
when we use SQUARE brackets [] we're loading a Long (16 bits) from the address, eg LDA [$12] loads 2 bytes from the address in zeropage $12+13...

The ZeroPage (ZP) has been renamed to the Direct Page (DP) - but it's basically the same thing - its just now that it doesn't need to be at $00xx they felt the need to rename it!... any addressing modes you're used to with the ZeroPage will work EXACLY the same now with DP... and in these tutorials we'll keep the DP in the same memory location as the ZeroPage was to keep things simple!

Maybe we should call it "The addressing mode formally known as ZeroPage" or something! (TAMFKAZ!!)

Converting from VASM to Macro-AS

Because VASM doesn't support 65816, we're going to have to use Macro-AS for assembly, unfortunately, there's a few differences! VASM Macro-AS
1. Symbol definitions must preceed first use
   In Vasm an EQU statement could come after it's first use
2. #>label and #<label do not work
 use #label/256 and #label&255 instead
3. Macro definitions are different
4. we have to tell Macro AS we're defaulting to 8 bit mode

The Old 6502 Addressing Modes
The 6502 has 11 different addrssing modes... many have no comparable equivalent on the Z80
Mode Description Syntax Sample Command Z80 Equivalent effective result
Implied / Inherant A command that needs no paprameters
SEC  (set carry) SCF
Relative A command which uses the program counter PC with and offset nn (-128 to +127)  #$nn BEQ [label] (branch if equal) JR Z,[label]
Accumulator A command which uses the Accumulator as the parameter
ROL (ROtate bits Left) RLCA
Immediate A command which takes a byte nn as a parameter  #$nn ADC #1 ADC 1 &nn
Absolute Take a parameter from a two byte memory address $nnnn  $nnnn LDA $2000 LD a,(&2000) (&nnnn)
Absolute Indexed Take a parameter from a two byte memory address $nnnn+X (or Y)  $nnnn,X LDA $2000,X (&nnnn+X)
Direct Page Take a parameter from the direct page address $00nn $nn ADC $32 (&00nn)
Direct Page Indexed Takes a parameter from memory address $00nn+X $nn,X ADC $32,X (&00nn+X)
Indirect Take a parameter from pointer at address $nnnn...
if $nnnn contains $1234 the parameter would come from the address at $1234
 ($nnnn) LD HL,(&1000)
indirect DP The 65c02 has an extra feature, where it can read from an unindexed Zero page ($nn) ((&00nn))
Pre Indexed (Indirect,X) Take a paramenter from pointer at address $nnnn+X
if $nnnn contains $1234, and X contained 4  the parameter would come from the address at $1238
($nn,X) ADC ($32,X) ((&00nn+X))
Postindexed  (Indirect),Y Take pointer from address $nnnn, add Y... get the parameter from the result
if $nnnn contains $1234, and Y contained 4, the address would be read from $1234... then 4 would be added... and the parameter would be read from ther resulting address
($nn),Y ADC ($32),Y ((&00nn)+Y)

The New 65c02 Addressing Modes
Mode Description Syntax Sample Command effective result
Direct page Indirect Read  from the address in the Direct page ($nn) LDA ($10)
Absolute indexed indirect read from a 16 bit address with an X offsetr ($nnnn,x) JMP ($1000,X) (($nnnn))

The New 65816 Addressing Modes
The 6502 has 11 different addrssing modes... many have no comparable equivalent on the Z80
Mode Description Syntax Sample Command effective result
Program Counter Relative Long $nnnn BRL Label
Stack Relative $nn,S LDA 3,S
Stack Relative Indirect Indexed with Y ($nn,S),Y LDA (3,S),Y
Block Move src,dest MVP 0,0
Absolute Long $nnnnnn LDA $020000 ($nnnnnn)
Absolute Indirect Long [$nnnnnn] JMP [$020000] (($nnnnnn))
Absolute Long Indexed with X $nnnnnn,X LDA $020000,X
Absolute Indirect Long [$nnnn] JMP [$2000]
Direct Page Indirect Long [$nn] LDA [$20] ($nn) [16 bit]
Direct Page Indirect Long Indexed with Y [$nn],Y LDA [$20],Y

Interrupt vectors
The top few bytes of the first 64k contain addresess to the handlers for interupts - these contain a 16 bit address for the interrupt handler (menaing the handler must be in the first 64k of ram)

Some of the handlers work in 8 and 16 bit mode, others are exclusive to one mode or the other
From To 8 Bit
16 Bit Mode
$00FFF6 $00FFF7 IRQ
$00FFF8 $00FFF9 Reset -

Turning on 16 bit!
There are 3 elements of the processor we can switch into 16 bit mode... 
Enable (16 bit) Disable (8 bit)
1. Disable emulation (Turning the CPU into 65816 mode)
This enables the extra addressing modes and allows access to the full memory range, and SP/DP relocation (you can still use XBA to get to the B accumulator)
We have to do this by setting the carry flag, and exchanging it with the E flag via XCE
2. Enable the 16 bit Accumulator
All accumulator operations will now work in 16 bits... this is done by setting bit 6 of the processor flags to 0
We also need to use ASSUME to tell the assembler to start compiling 16 bit values for commands containing the Accumulator (So LDA #$00 will compile to LDA #$0000)
3. Enable the 16 bit X and Y registers
X and Y registers will now work in 16 bits... this is done by setting bit 5 of the processor flags to 0...
Turning this off will LOSE the top byte of X and Y
Again, we need to tell the assembler with ASSUME
2+3. Enable16 bit for all 3 registers together
We can set both bits to1 together.... we still need two ASSUME commands, but these don't compile to bytecode

Simulating Commands:
BSR subprogram PSR retaddr
brl subprogram

Learn Assembly for the Greatest Classic Processors:  Z80 - 6502 - 68000
Visit to get my games and their source code! | Support me on patreon