6502 assembly
The MOS 6502 (and its variants) powered some of the most iconic home computers and consoles ever made — the Commodore 64, the NES, the Atari 8-bit family, the Apple II, and the BBC Micro. If you want to squeeze every last cycle out of these machines, assembly is where you go.
The IDE supports several 6502 assemblers. Which one you get depends on the platform preset you choose:
| Assembler | Typical platforms | Notes |
|---|---|---|
| KickAss | C64, C128 | Powerful macro system; .seg and .pc for memory layout |
| DASM | Atari 2600, NES | Widely used in console tutorials |
| ACME | C64, general | Clean, portable syntax; !byte, !word pseudo-ops |
| ca65 (cc65) | C64, Atari 8-bit, Apple II, NES | Part of the cc65 suite; pairs with ld65 linker |
Don't worry too much about which assembler you're using to start — the core 6502 instructions are identical across all of them. The differences are mainly in pseudo-ops and macro syntax.
Load an example first
In the IDE, use the Examples dropdown to load the platform's hello-world assembly template. It includes the correct startup stub and memory layout for you, so you can run something immediately before writing a single line yourself.
The 6502 in a nutshell
The 6502 has three registers you'll use constantly:
| Register | Size | Purpose |
|---|---|---|
| A (Accumulator) | 8-bit | Arithmetic and logic happen here |
| X | 8-bit | Index register — great for loops and table lookups |
| Y | 8-bit | Index register — pairs with indirect addressing |
There's also a stack pointer (SP), program counter (PC), and a processor status register (P) with flags like Zero (Z), Carry (C), and Negative (N). That's it. The power of 6502 programming comes from how cleverly you use these three registers together.
Your first program
Here's the smallest useful 6502 program for the C64 — change the border colour:
; C64 — set border colour to red
; The border colour register is at $D020
*= $C000 ; Load at $C000 (call with SYS 49152 from BASIC)
start:
lda #2 ; Red = colour index 2 in C64 palette
sta $D020 ; Store to border colour register
sta $D021 ; Also set background colour
rts ; Return to BASIC
In the IDE's C64 BASIC window, type SYS 49152 and press Enter — your border turns red. Three instructions, direct hardware access.
The instructions you'll use most
Moving data
lda #42 ; Load the value 42 into A (# = immediate value)
lda $D020 ; Load the byte at address $D020 into A
sta $D020 ; Store A into address $D020
ldx #0 ; Load 0 into X
ldy #10 ; Load 10 into Y
tax ; Copy A into X
tay ; Copy A into Y
txa ; Copy X into A
Arithmetic
clc ; Always clear Carry before addition
adc #5 ; A = A + 5 (add with carry)
sec ; Always set Carry before subtraction
sbc #3 ; A = A - 3 (subtract with borrow)
inc $50 ; Increment the byte at zero-page address $50
inx ; X = X + 1
iny ; Y = Y + 1
dex ; X = X - 1
dey ; Y = Y - 1
Comparisons and branches
cmp #10 ; Compare A with 10 (sets flags, does not change A)
beq equal ; Branch if equal (Z flag set)
bne not_equal ; Branch if not equal
bcc less ; Branch if Carry Clear (unsigned less-than after cmp)
bcs greater_eq ; Branch if Carry Set (unsigned >=)
bmi negative ; Branch if Minus (result was negative)
bpl positive ; Branch if Plus
Loops
A counted loop with X is one of the most common 6502 patterns:
ldx #10 ; Loop 10 times
loop: ; ... your code here ...
dex
bne loop ; Branch back while X is non-zero
Subroutines
jsr my_routine ; Jump to subroutine (pushes return address on stack)
; execution continues here after rts
my_routine:
; ... do something ...
rts ; Return
Addressing modes
Addressing modes determine where an instruction's data comes from:
| Mode | Example | Meaning |
|---|---|---|
| Immediate | lda #42 |
The literal value 42 |
| Zero page | lda $50 |
Byte at address $0050 (fast — 1 fewer byte and cycle) |
| Absolute | lda $D020 |
Byte at address $D020 |
| Absolute,X | lda $D800,x |
Byte at $D800 + X |
| Absolute,Y | lda $D800,y |
Byte at $D800 + Y |
| Zero page,X | lda $50,x |
Byte at $0050 + X |
| Indirect,Y | lda ($50),y |
Use address stored at $50/$51, then add Y |
| Implied | clc |
No operand needed |
Zero page (addresses $00–$FF) is special — instructions using it are one byte shorter and one cycle faster than their absolute equivalents. Put your most-used variables there.
Indirect,Y ((zp),y) is the workhorse for rendering and data processing — store a pointer in two zero-page bytes and walk through memory with Y.
The stack
The 6502 has a 256-byte hardware stack at $0100–$01FF. You push and pull bytes:
pha ; Push A onto stack
pla ; Pull top of stack into A
php ; Push processor status register
plp ; Pull processor status register
jsr and rts use the stack automatically. Keep subroutine nesting reasonable — deep nesting on deeply recursive code will overflow the 256-byte limit.
Platform memory maps
The 6502 can address 64 KB ($0000–$FFFF), but what lives where differs completely per machine:
| Range | Contents |
|---|---|
| $0000–$00FF | Zero page (fast variables) |
| $0100–$01FF | Stack |
| $0400–$07FF | Screen character RAM (default) |
| $0800–$9FFF | BASIC RAM |
| $C000–$CFFF | BASIC ROM (bank-switchable) |
| $D000–$D3FF | VIC-II chip |
| $D400–$D7FF | SID chip |
| $DC00–$DCFF | CIA 1 |
| $DD00–$DDFF | CIA 2 |
| $E000–$FFFF | Kernal ROM |
| Range | Contents |
|---|---|
| $0000–$00FF | Zero page |
| $0100–$01FF | Stack |
| $1000–$1DFF | Main RAM (only ~3.5 KB without expansion) |
| $9000–$93FF | VIC chip registers |
| $9400–$97FF | Colour RAM |
| $A000–$BFFF | BASIC ROM |
| $C000–$FFFF | Kernal ROM |
| Range | Contents |
|---|---|
| $0000–$00FF | Zero page |
| $0100–$01FF | Stack |
| $0200–$07FF | RAM |
| $2000–$2007 | PPU registers |
| $4000–$4017 | APU and controller I/O |
| $8000–$FFFF | PRG ROM (cartridge — your code) |
| Range | Contents |
|---|---|
| $0000–$00FF | Zero page |
| $0100–$01FF | Stack |
| $0200–$02FF | OS page 2 |
| $D000–$D7FF | GTIA / ANTIC / POKEY / PIA |
| $E000–$FFFF | OS ROM |
| Range | Contents |
|---|---|
| $0000–$00FF | Zero page (OS uses part of it) |
| $0100–$01FF | Stack |
| $0400–$07FF | OS workspace (avoid!) |
| $0E00–$7FFF | User RAM |
| $C000–$FFFF | OS ROM |
Interrupts and vectors
The 6502 has three hardware vectors at the very top of memory:
| Vector | Address | Purpose |
|---|---|---|
| NMI | $FFFA/$FFFB | Non-Maskable Interrupt (can't be ignored) |
| RESET | $FFFC/$FFFD | Power-on / reset start address |
| IRQ/BRK | $FFFE/$FFFF | Maskable interrupt / BRK instruction |
On most machines the OS owns these and you hook in indirectly (e.g. C64's $0314/$0315 IRQ vector). On bare-metal targets like the Atari 2600 and NES, your cartridge ROM supplies the actual vectors.
Full minimal examples
C64 — clear screen (KickAss syntax)
.const SCREEN = $0400
.const SPACE = $20
.const COLOUR = $D800
.const WHITE = 1
*= $C000
start:
ldx #0
clear:
lda #SPACE
sta SCREEN,x
lda #WHITE
sta COLOUR,x
inx
bne clear // Clears first 256 chars; repeat for pages 1-3
rts
Atari 2600 — colour-cycling background (DASM)
processor 6502
include "vcs.h"
org $F000
Start:
sei
cld
ldx #$FF
txs
lda #0
sta VSYNC
sta VBLANK
MainLoop:
lda #2 ; VSYNC on
sta VSYNC
sta WSYNC
sta WSYNC
sta WSYNC
lda #0
sta VSYNC
ldx #37 ; VBLANK — 37 lines
VBloop:
sta WSYNC
dex
bne VBloop
lda #0
sta VBLANK
ldx #192 ; 192 visible scanlines
ScanLoop:
sta WSYNC
txa
sta COLUBK ; Use X as colour — rainbow effect!
dex
bne ScanLoop
ldx #30 ; Overscan
OVloop:
sta WSYNC
dex
bne OVloop
jmp MainLoop
org $FFFC
.word Start ; RESET vector
.word Start ; IRQ vector
Common mistakes
Forgetting clc before adc — the Carry flag left over from a previous operation will corrupt your result. Always clear it first.
Forgetting sec before sbc — same in reverse. Always set Carry before subtracting.
16-bit values — the 6502 only does 8-bit arithmetic. For 16-bit, carry the high byte manually:
; 16-bit add: result = value + 500
clc
lda value_lo
adc #<500 ; Low byte of 500
sta result_lo
lda value_hi
adc #>500 ; High byte of 500, plus carry from low byte
sta result_hi
Clobbering registers in subroutines — save and restore what you use:
my_sub:
pha ; Save A
txa
pha ; Save X (via A)
; ... use A and X freely ...
pla
tax ; Restore X
pla ; Restore A
rts
Commenting hardware addresses — sta $D020 means nothing in six months. Use equates:
Pairing with high-level tools
- cc65 — write C and call assembly, or drop into inline asm for hot routines. See cc65 / C.
- BASIC
SYS— load a small ML routine from BASIC and jump to it withSYS address. See Commodore BASIC V2.
See also
- Z80 assembly — the other great 8-bit assembly family
- cc65 / C — C + assembly with the cc65 toolchain
- Commodore BASIC V2 — calling ML routines from BASIC
- C64 platform guide
- NES platform guide
- IDE getting started