6502 Assembly on the C64 with VICE
22 Jan 2026
One of the most effective ways to learn 6502 assembly is to work directly at the machine level: assembling instructions by hand, stepping through execution, and inspecting registers and memory as the CPU runs. The VICE Monitor provides an excellent interactive environment for doing exactly that on the Commodore 64.
This post is a practical guide to the subset of monitor commands you will actually use when practicing 6502 programming on the C64. It deliberately ignores advanced or peripheral features and concentrates on assembling code, disassembling it, stepping through instructions, inspecting state, and managing breakpoints.
Top 10 Command Cheatsheet
| Command | Description |
|---|---|
| a | assemble |
| d | disassemble |
| r | registers |
| z | step into |
| n | step over |
| g | run |
| m | memory dump |
| > | write bytes |
| break | breakpoint |
| watch | watch memory |
Entering the Monitor
From within VICE, open the monitor with:
Alt + H
(or from the menu: Debug → Monitor).
You will see a prompt similar to:
(C:$0801) >
This tells you:
- You are in the computer CPU address space (
C:) - The current “dot” address is
$0801(used as a default by many commands)
For nearly all C64 assembly practice, you will stay in the C: address space.
Memory Layout for the Commodore 64
A few practical conventions for learning:
$0801–$9FFFis generally safe RAM (BASIC program area upward)$C000–$CFFFis commonly used as scratch RAM for machine-code$D000–$DFFFis I/O (VIC-II, SID, CIA)$E000–$FFFFis KERNAL ROM (read-only unless banked out)
For practice, $C000 is a good default code location.
Disassembling Code
Basic Disassembly
To disassemble memory into 6502 instructions:
d $C000
This disassembles forward from $C000.
To disassemble a range:
d $C000 $C050
If you omit arguments:
d
the monitor disassembles starting from the current “dot” address.
Disassembly respects labels if you define them, which becomes very useful later.
Assembling Instructions
Interactive Assembly Mode
To assemble code starting at a given address:
a $C000
You can now type one instruction per line:
LDA #$06
STA $D020
RTS
Press Enter on a blank line to exit assembly mode.
The monitor automatically advances the address as instructions are entered.
Viewing and Modifying Registers
Display Registers
To display the CPU registers:
r
You will see output similar to:
A:00 X:00 Y:00 SP:FF PC:C000 FL:24
Modifying Registers
You can directly set registers:
r PC=$C000
Or multiple registers at once:
r A=$10, X=$00, Y=$00
The status register is referred to as FL.
Stepping Through Code
Step Into (Execute One Instruction)
z
Each z executes exactly one instruction and then stops.
To step multiple instructions:
z 10
Step Over Subroutines
If you encounter a JSR and want to execute it as a single step:
n
This runs the subroutine internally and stops at the instruction after the JSR.
Step Out of a Subroutine
If you are already inside a subroutine:
ret
Execution continues until the matching RTS or RTI.
Running and Continuing Execution
To resume execution from the current PC:
g
To set the PC and immediately run:
g $C000
This is often used after assembling new code.
Inspecting Memory
Hex Dump
To view memory as hexadecimal bytes:
m $C000
To view a range:
m $C000 $C0FF
Viewing Text
To view memory as PETSCII characters:
i $0400 $07E7
To view screen codes:
ii $0400 $07E7
These are helpful when working with screen memory or character data.
Writing Bytes Directly to Memory
You can write raw bytes using the > command:
> $C000 A9 06 8D 20 D0 60
This is equivalent to assembling:
LDA #$06
STA $D020
RTS
If you omit the address, bytes are written at the current “dot” address.
Breakpoints and Watchpoints
VICE uses a unified concept called checkpoints, which includes breakpoints, watchpoints, and tracepoints.
Execution Breakpoint
Stop when code at an address executes:
break exec $C000
Start execution:
g
Execution halts when the PC reaches $C000.
Temporary Run-Until Break
To run until a specific address once:
un $C050
The temporary breakpoint deletes itself after being hit.
Watch Memory Writes
Break when memory is written to:
watch store $D020
This is extremely useful when learning how code affects VIC-II registers.
Managing Breakpoints
List checkpoints:
break
Delete one:
del 2
Disable / enable:
disable 2
enable 2
Conditional Breakpoints
You can attach conditions to breakpoints:
cond 2 if A==$00
Conditions can reference:
- Registers (
A,X,Y,PC,SP,FL) - Raster line (
RL) - Cycle count (
CY) - Memory via banked dereferencing
This allows very precise debugging even in tight loops.
Using Labels
Labels make disassembly and debugging far more readable.
Add a label:
al $C000 .main
Show labels:
shl
Delete a label:
dl .main
Clear all labels:
cl
You can also load labels generated by assemblers such as ACME or cc65 later, but even manually added labels greatly improve clarity.
A Simple Practice Workflow
- Assemble code at
$C000 - Disassemble to verify correctness
- Set the PC manually
- Step through instructions with
z - Inspect registers and memory after each step
- Add breakpoints or watchpoints as needed
- Iterate
Closing Thoughts
The VICE Monitor is not just a debugger; it is an interactive lab for learning the 6502 at the instruction-level. By assembling directly into memory, stepping instruction by instruction, and watching registers and I/O change in real time, you gain an intuition that is hard to replicate with higher-level tooling.
Once this workflow becomes second nature, integrating an external assembler feels like an optimization—not a dependency.