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$01020304 ; Equivalent to $01, $02, $03, $04MASK = %11110000 ; 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 = 53MASK =$FF & %11110000 ; MASK = $F0OFFSET = LABEL -* ; Distance from current PC to LABELHIBYTE = ADDRESS >> 8 ; Extract high byteLOBYTE = ADDRESS &$FF ; Extract low byteFLAG = 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 colonLOOP DEX ; Label without colon BNE LOOPSymbols can be assigned constant values using the = operator:
SCREEN =$0400BORDER_COLOR =$D020MAX_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 RTSROUTINE2: 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: .DS256 ; 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 #20LOAD_XY$FF,$00For macros without arguments:
.MACRO SAVE_REGS PHA TXA PHA TYA PHA.ENDMSAVE_REGS ; Call with no argumentsSAVE_REGS() ; Alternative syntax; 16-bit increment macro.MACRO INC16INC \1 BNE +INC \1+1+:.ENDMCOUNTER: .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.ELSENOP ; 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 variableCOUNT = COUNT +1 ; Modify variable; Generate 256-byte sine table (conceptual example).VAR I = 0SINE_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 .WORD10 ; Line number .BYTE$9E ; SYS token .BYTE"2062",0 ; Address as stringNEXT_LINE: .WORD0 ; End of BASIC; Main program starts hereSTART: LDX #0@LOOP: LDA MESSAGE,X BEQ DONE JSR$FFD2 ; CHROUT INX BNE @LOOPDONE: RTSMESSAGE: .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