Version 1.0
- Introduction
- Command Line Usage
- Source File Format
- Numbers and Literals
- Expressions
- Labels and Symbols
- Addressing Modes
- Assembler Directives
- Macros
- Conditional Assembly
- Loops
- Complete Instruction Reference
PASM is a modern, feature-rich assembler for the MOS 6502 and WDC 65C02 microprocessors. It supports the full instruction set of both processors, including illegal/undocumented 6502 opcodes, and provides powerful macro and conditional assembly capabilities.
- Full 6502 instruction set support
- Extended 65C02 instruction set support
- Illegal/undocumented 6502 opcode support (optional)
- Powerful expression evaluation with C-style operators
- Macro definitions with parameter substitution
- Conditional assembly (
.if,.ifdef,.ifndef) - Loop constructs (
.do/.while) - Local and anonymous labels
- Multiple input file support
- Include file directive
- Commodore 64 PRG file output format
pasm inputfile1 [inputfile2 ...] [options]
| Option | Description |
|---|---|
-h |
Print help. |
-ast |
Print the Abstract Syntax Tree. |
-v |
Enable verbose mode. (displays symbol table) |
-all |
Show all symbols in listing. By default only used symbols are listed. |
-c64 |
Commodore64 program mode. Set first 2 bytes as load address. |
-o <file> |
Specify output filename |
-65c02 |
Enable 65C02 extended instruction set |
-il |
Allow illegal/undocumented 6502 instructions |
-nowarn |
Suppress warning messages |
-li |
List all valid instructions with addressing modes and cycle counts |
# Basic assembly
pasm program.asm -o program.bin
# 65C02 assembly with verbose output
pasm program.asm -65c02 -v -o program.bin
# Commodore 64 PRG file
pasm game.asm -c64 -o game.prg
# Multiple source files
pasm main.asm utils.asm data.asm -o program.binSource files are plain text files with one statement per line. The general format of a line is:
[label[:]] [instruction/directive [operands]] [; comment]
All components are optional. Blank lines and comment-only lines are permitted.
- Instructions and directives: Case-insensitive (
LDA,lda, andLdaare equivalent) - Symbols and labels: Case-insensitive (
MYVAR,MyVar, andmyvarrefer to the same symbol)
Comments begin with a semicolon (;) and extend to the end of the line:
LDA #$00 ; Load accumulator with zero
; This entire line is a comment| Format | Prefix | Example | Value |
|---|---|---|---|
| Decimal | (none) | 255 |
255 |
| Hexadecimal | $ |
$FF |
255 |
| Binary | % |
%11111111 |
255 |
| Character | 'c' or "c" |
'A' |
65 |
For readability, hexadecimal and binary numbers may contain spaces between digit groups:
.BYTE $01 02 03 04 ; Equivalent to $01, $02, $03, $04
MASK = %1111 0000 ; Equivalent to %11110000Text strings are enclosed in single or double quotes:
.BYTE "Hello, World!"
.BYTE 'SCORE: 'PASM supports a rich expression syntax with C-style operators. Expressions are evaluated as signed integers.
| Precedence | Operators | Description |
|---|---|---|
| 1 | ( ) |
Parentheses |
| 2 | - + ~ |
Unary minus, plus, one's complement |
| 3 | * / % |
Multiply, divide, modulo |
| 4 | + - |
Add, subtract |
| 5 | << >> |
Shift left, shift right |
| 6 | < > <= >= |
Relational comparison |
| 7 | == != <> |
Equality, inequality |
| 8 | & |
Bitwise AND |
| 9 | ^ |
Bitwise XOR |
| 10 | | |
Bitwise OR |
| 11 | && |
Logical AND |
| 12 | || |
Logical OR |
| Symbol | Meaning |
|---|---|
* |
Current program counter (when not an operator) |
VALUE = 10 * 5 + 3 ; VALUE = 53
MASK = $FF & %11110000 ; MASK = $F0
OFFSET = LABEL - * ; Distance from current PC to LABEL
HIBYTE = ADDRESS >> 8 ; Extract high byte
LOBYTE = ADDRESS & $FF ; Extract low byte
FLAG = VALUE >= 100 ; FLAG = 0 (false) or 1 (true)Labels mark locations in code and can be defined with or without a trailing colon:
START: LDA #$00 ; Label with colon
LOOP DEX ; Label without colon
BNE LOOPSymbols can be assigned constant values using the = operator:
SCREEN = $0400
BORDER_COLOR = $D020
MAX_LIVES = 3The program counter can be set directly:
* = $C000 ; Set PC to $C000Local labels begin with @ and are scoped between two global labels:
ROUTINE1:
LDX #$10
@LOOP: DEX
BNE @LOOP ; References local @LOOP
RTS
ROUTINE2:
LDX #$20
@LOOP: DEX ; Different @LOOP, local to ROUTINE2
BNE @LOOP
RTSWhen a new global label is defined, all previous local labels go out of scope.
Anonymous labels provide a lightweight way to create short-range branch targets:
Definition:
+:defines a forward anonymous label-:defines a backward anonymous label
Reference:
+references the next forward anonymous label++references the second forward anonymous label-references the previous backward anonymous label--references the second-to-last backward anonymous label
-: LDA DATA,X
BEQ + ; Branch to next + label
JSR PROCESS
+: INX
CPX #$10
BNE - ; Branch to previous - labelMultiple levels of anonymous references:
-: ; First backward label
-: ; Second backward label
BEQ -- ; Branch to first backward label
BNE - ; Branch to second backward labelPASM automatically selects zero-page addressing when operands fit in a single byte and the instruction supports it.
| Mode | Syntax | Example | Description |
|---|---|---|---|
| Implied | OPC |
NOP |
No operand |
| Accumulator | OPC A |
ASL A |
Operates on accumulator |
| Immediate | OPC #val |
LDA #$FF |
8-bit immediate value |
| Absolute | OPC addr |
LDA $1234 |
16-bit address |
| Zero Page | OPC zp |
LDA $80 |
8-bit zero page address |
| Absolute,X | OPC addr,X |
LDA $1234,X |
Absolute indexed by X |
| Absolute,Y | OPC addr,Y |
LDA $1234,Y |
Absolute indexed by Y |
| Zero Page,X | OPC zp,X |
LDA $80,X |
Zero page indexed by X |
| Zero Page,Y | OPC zp,Y |
LDX $80,Y |
Zero page indexed by Y |
| Indirect | OPC (addr) |
JMP ($FFFC) |
Indirect addressing |
| Indexed Indirect | OPC (zp,X) |
LDA ($80,X) |
Indexed indirect (X) |
| Indirect Indexed | OPC (zp),Y |
LDA ($80),Y |
Indirect indexed (Y) |
| Relative | OPC label |
BNE LOOP |
Branch relative |
| ZP Relative | OPC zp,label |
BBR0 $10,DONE |
65C02 bit branch |
Sets the program counter to a specific address:
.ORG $C000 ; Code starts at $C000Stores one or more bytes in memory:
.BYTE $00, $01, $02, $03
.BYTE "Hello", 0 ; String with null terminator
.BYT 'A', 'B', 'C'Stores one or more 16-bit words (little-endian):
.WORD $1234 ; Stores $34, $12
.WORD LABEL1, LABEL2 ; Address tableReserves a block of bytes (advances PC without generating output):
BUFFER: .DS 256 ; Reserve 256 bytesFills a region with a repeated byte value:
.FILL $EA, 16 ; 16 bytes of NOPs ($EA)
.FILL 0, 256 ; 256 zero bytesIncludes another source file:
.INCLUDE "macros.asm"
.INC "data.asm"Macros allow you to define reusable code sequences with parameter substitution.
.MACRO NAME
; Macro body
; Use \1, \2, \3... for parameters
.ENDMOr using the short forms:
.MAC NAME
; body
.ENDMACROParameters are referenced using \1, \2, \3, etc.:
.MACRO LOAD_XY
LDX #\1
LDY #\2
.ENDMMacros are called by name, optionally followed by arguments:
LOAD_XY 10, 20 ; Expands to LDX #10 / LDY #20
LOAD_XY $FF, $00For macros without arguments:
.MACRO SAVE_REGS
PHA
TXA
PHA
TYA
PHA
.ENDM
SAVE_REGS ; Call with no arguments
SAVE_REGS() ; Alternative syntax; 16-bit increment macro
.MACRO INC16
INC \1
BNE +
INC \1+1
+:
.ENDM
COUNTER: .WORD 0
INC16 COUNTER ; Increment 16-bit counterConditional assembly based on expression evaluation:
DEBUG = 1
.IF DEBUG
JSR PRINT_STATUS ; Only assembled if DEBUG != 0
.ELSE
NOP ; Assembled if DEBUG == 0
.ENDIFTest whether a symbol is defined:
.IFDEF USE_SOUND
JSR INIT_SOUND
.ENDIF
.IFNDEF SKIP_INTRO
JSR SHOW_INTRO
.ENDIFConditionals can be nested:
.IF PLATFORM == 1
.IFDEF HAS_SID
JSR PLAY_MUSIC
.ENDIF
.ENDIFRepeat a block of code while a condition is true. Useful with .VAR for assembly-time iteration:
.VAR I = 0
.DO
.BYTE I
.VAR I = I + 1
.WHILE I < 10This generates: .BYTE 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
Variables differ from equates in that they can be modified during assembly:
.VAR COUNT = 0 ; Define variable
COUNT = COUNT + 1 ; Modify variable; Generate 256-byte sine table (conceptual example)
.VAR I = 0
SINE_TABLE:
.DO
.BYTE (I * I) / 256 ; Simplified calculation
I = I + 1
.WHILE I < 256Use the -li command line option to display all supported instructions with their addressing modes and cycle counts:
pasm -li| Mnemonic | Description |
|---|---|
ADC |
Add with Carry |
AND |
Logical AND |
ASL |
Arithmetic Shift Left |
BCC |
Branch if Carry Clear |
BCS |
Branch if Carry Set |
BEQ |
Branch if Equal (Zero) |
BIT |
Bit Test |
BMI |
Branch if Minus |
BNE |
Branch if Not Equal |
BPL |
Branch if Plus |
BRK |
Break |
BVC |
Branch if Overflow Clear |
BVS |
Branch if Overflow Set |
CLC |
Clear Carry |
CLD |
Clear Decimal |
CLI |
Clear Interrupt Disable |
CLV |
Clear Overflow |
CMP |
Compare Accumulator |
CPX |
Compare X Register |
CPY |
Compare Y Register |
DEC |
Decrement Memory |
DEX |
Decrement X |
DEY |
Decrement Y |
EOR |
Exclusive OR |
INC |
Increment Memory |
INX |
Increment X |
INY |
Increment Y |
JMP |
Jump |
JSR |
Jump to Subroutine |
LDA |
Load Accumulator |
LDX |
Load X Register |
LDY |
Load Y Register |
LSR |
Logical Shift Right |
NOP |
No Operation |
ORA |
Logical OR |
PHA |
Push Accumulator |
PHP |
Push Processor Status |
PLA |
Pull Accumulator |
PLP |
Pull Processor Status |
ROL |
Rotate Left |
ROR |
Rotate Right |
RTI |
Return from Interrupt |
RTS |
Return from Subroutine |
SBC |
Subtract with Carry |
SEC |
Set Carry |
SED |
Set Decimal |
SEI |
Set Interrupt Disable |
STA |
Store Accumulator |
STX |
Store X Register |
STY |
Store Y Register |
TAX |
Transfer A to X |
TAY |
Transfer A to Y |
TSX |
Transfer SP to X |
TXA |
Transfer X to A |
TXS |
Transfer X to SP |
TYA |
Transfer Y to A |
| Mnemonic | Description |
|---|---|
BRA |
Branch Always |
PHX |
Push X Register |
PHY |
Push Y Register |
PLX |
Pull X Register |
PLY |
Pull Y Register |
STZ |
Store Zero |
TRB |
Test and Reset Bits |
TSB |
Test and Set Bits |
BBR0-BBR7 |
Branch on Bit Reset |
BBS0-BBS7 |
Branch on Bit Set |
RMB0-RMB7 |
Reset Memory Bit |
SMB0-SMB7 |
Set Memory Bit |
STP |
Stop Processor |
WAI |
Wait for Interrupt |
| Mnemonic | Description |
|---|---|
SLO |
ASL + ORA |
RLA |
ROL + AND |
SRE |
LSR + EOR |
RRA |
ROR + ADC |
SAX |
Store A AND X |
LAX |
Load A and X |
DCP |
DEC + CMP |
ISC |
INC + SBC |
ANC |
AND + set Carry |
ALR |
AND + LSR |
ARR |
AND + ROR |
XAA |
TXA + AND |
AXS |
A AND X - operand |
AHX |
Store A AND X AND high byte |
SHY |
Store Y AND high byte |
SHX |
Store X AND high byte |
TAS |
Transfer A AND X to SP |
LAS |
Load A, X, SP with SP AND memory |
USBC |
Unofficial SBC |
; Hello World for Commodore 64
; Assemble with: pasm hello.asm -c64 -o hello.prg
.ORG $0801
; BASIC stub: 10 SYS 2062
.WORD NEXT_LINE
.WORD 10 ; Line number
.BYTE $9E ; SYS token
.BYTE "2062", 0 ; Address as string
NEXT_LINE:
.WORD 0 ; End of BASIC
; Main program starts here
START:
LDX #0
@LOOP:
LDA MESSAGE,X
BEQ DONE
JSR $FFD2 ; CHROUT
INX
BNE @LOOP
DONE:
RTS
MESSAGE:
.BYTE "HELLO, WORLD!", 13, 0PASM provides descriptive error messages including file name and line number:
Error: Unknown opcode 'LDZ' at hello.asm:15
Error: Opcode 'LDA' does not support addressing mode Indirect at test.asm:23
Error: Division by zero at math.asm:10
Error: Relative branch target out of range (-128 to 127) at game.asm:156
- Use local labels for loop targets to avoid name collisions
- Use anonymous labels (
+/-) for very short forward/backward branches - Organize code with includes to separate data, macros, and main logic
- Use macros for common code patterns
- Use conditional assembly to build debug and release versions from the same source
- Use
.VARwith.DO/.WHILEfor generating data tables at assembly time
PASM - Written by Paul Baxter