Contents
- Usage
- Assembly Syntax
- Number Formats
- Expression Operators
- String Escape Sequences
- Addressing Modes
- Directives
- Symbols and Constants
- Conditional Assembly
- Macros
- Scoping
- Segments
- Debug Output
- Assembler Messages
- Assertions
- Processor Selection
- Supported Instructions
- Platform: C64
- Optimization Control
- Cycle Timing
- Hardware Alignment
- Bank Configuration
- VICE Integration
- Peephole Optimizer
- Listing File
- Multi-Pass Assembly
- Compatibility
- License
6asm
A 6502 cross-assembler written in C# / .NET 8. Supports NMOS 6502 official and undocumented opcodes with multi-pass assembly, macro expansion, conditional assembly, and tmpx/ACME/DASM-compatible syntax.
Usage
6asm [options] <input.asm>
Options
| Flag | Description |
|---|---|
-h, --help |
Show help message |
-v, --version |
Show version |
-o, --output FILE |
Output file name (default: input with .prg extension) |
-f, --format FORMAT |
Output format: bin, hex, srec (default: bin) |
-s, --symbols |
Generate symbol file (.sym) |
--symbol-file FILE |
Explicit symbol file path |
--list |
Generate listing file (.lst) |
--list-file FILE |
Explicit listing file path |
--vice |
Generate VICE monitor symbol file (.lbl) |
--vice-file FILE |
VICE symbol file path |
--analyze |
Suggest peephole optimizations (illegal opcodes, dead code) |
-q, --quiet |
Suppress output messages |
--verbose |
Show detailed pass/symbol information |
--max-passes N |
Maximum assembly passes (default: 10) |
-fillbyte=0xNN |
Fill byte for address gaps (default: 0x00) |
--endkeyword |
Treat END as a directive that stops assembly |
Examples
6asm game.asm # Assemble to game.prg
6asm -o out.prg game.asm # Explicit output file
6asm -f hex -s game.asm # Intel HEX output with symbol file
6asm -fillbyte=0xFF game.asm # Fill gaps with $FF
6asm --list game.asm # Generate listing file
Output Formats
| Format | Extension | Description |
|---|---|---|
bin |
.prg |
Raw binary with 2-byte load address header (C64 PRG) |
hex |
.hex |
Intel HEX |
srec |
.s19 |
Motorola S-Record |
d64 |
.d64 |
D64 disk image containing the PRG as a single file |
crt |
.crt |
CRT cartridge image (8K or 16K normal cartridge) |
Assembly Syntax
Line Format
[label[:]] [mnemonic/directive] [operand] [; comment]
- Labels at column 1 do not require a colon.
- Labels not at column 1 require a colon suffix.
- Comments begin with
;and extend to end of line. - Lines are case-insensitive (labels preserve case).
Labels
start lda #0 ; column 1 label (no colon needed)
loop: inx ; label with colon (any column)
SCREEN = $0400 ; constant assignment (EQU shorthand)
BORDER .equ $d020 ; explicit constant
@local dex ; local label (scoped)
Number Formats
| Prefix | Base | Example |
|---|---|---|
$ |
Hexadecimal | $FF, $D020 |
0x |
Hexadecimal | 0xFF, 0x1234 |
% |
Binary | %10110011, %1010_0011 |
@ |
Octal | @377 |
| (none) | Decimal | 255 |
'c' |
Character | 'A', '\n' |
Binary literals accept _ as a visual separator.
Expression Operators
Expressions are evaluated with standard mathematical operator precedence (highest precedence binds tightest).
Precedence Table (lowest to highest)
| Level | Operators | Description |
|---|---|---|
| 1 | = == != <> < > <= >= |
Comparison (result: 0 or 1) |
| 2 | \|\| .OR. |
Logical OR |
| 3 | && .AND. |
Logical AND |
| 4 | \| |
Bitwise OR |
| 5 | ^ .XOR. |
Bitwise XOR |
| 6 | & |
Bitwise AND |
| 7 | << >> |
Bit shift left/right |
| 8 | + - |
Addition, subtraction |
| 9 | * / % .MOD. |
Multiplication, division, modulo |
| 10 | - + ~ ! < > ^ .NOT. |
Unary: negate, identity, complement, not, lo byte, hi byte, bank byte |
| 11 | (primary) | Numbers, symbols, (expr), *, . |
Special Symbols
| Symbol | Meaning |
|---|---|
* |
Current program counter (in expressions) |
. |
Current program counter (alternate) |
Lo/Hi Byte Operators
lda #<address ; low byte of address
lda #>address ; high byte of address
lda #^address ; bank byte (bits 16-23)
String Escape Sequences
Quoted strings in directives (.text, .byte, .screen, etc.) support the following escapes:
| Escape | Result |
|---|---|
\n |
Newline ($0A) |
\r |
Carriage return ($0D) |
\t |
Tab ($09) |
\0 |
Null ($00) |
\\ |
Literal backslash |
{$XX} |
Hex byte value (e.g., {$24} emits $) |
{name} |
Named PETSCII control/color code (e.g., {white}, {rvson}) |
The {$XX} escape allows embedding arbitrary byte values inline within strings. This is useful for characters that are difficult to type or have special meaning in the assembler:
.screen "@@@2026{$24}02{$24}28@@@" ; $ signs via hex escape (16 bytes)
.text "Line1{$0D}Line2" ; embedded carriage return
.byte "{$00}" ; null byte in a string context
Named PETSCII Codes
For SEQ-file-style text, PETSCII control and color codes can be written by name inside
any string (case-insensitive). These are most meaningful in .text / !pet output:
.text "{clear}{white}HELLO {rvson}{red}WORLD{rvsoff}{return}"
Colors (the 16 C64 colors):
| Name(s) | Byte | Name(s) | Byte | |
|---|---|---|---|---|
{black} |
$90 |
{orange} |
$81 |
|
{white} |
$05 |
{brown} |
$95 |
|
{red} |
$1C |
{lightred} {pink} |
$96 |
|
{cyan} |
$9F |
{darkgrey} {grey1} |
$97 |
|
{purple} {magenta} |
$9C |
{grey} {grey2} {mediumgrey} |
$98 |
|
{green} |
$1E |
{lightgreen} {ltgreen} |
$99 |
|
{blue} |
$1F |
{lightblue} {ltblue} |
$9A |
|
{yellow} |
$9E |
{lightgrey} {grey3} |
$9B |
(gray spellings are accepted for every grey.)
Control codes:
| Name(s) | Byte | Name(s) | Byte | |
|---|---|---|---|---|
{clear} {clr} {cls} |
$93 |
{down} |
$11 |
|
{home} |
$13 |
{up} |
$91 |
|
{rvson} {rvs} |
$12 |
{right} |
$1D |
|
{rvsoff} |
$92 |
{left} |
$9D |
|
{return} {cr} |
$0D |
{del} |
$14 |
|
{lower} |
$0E |
{insert} {inst} |
$94 |
|
{upper} |
$8E |
An unrecognized {name} is left in the string literally, so existing text is unaffected.
Addressing Modes
All standard NMOS 6502 addressing modes are supported:
| Syntax | Mode | Example |
|---|---|---|
| (implied) | Implied | NOP, RTS |
A |
Accumulator | ASL A |
#$nn |
Immediate | LDA #$FF |
$nn |
Zero Page | LDA $10 |
$nn,X |
Zero Page,X | LDA $10,X |
$nn,Y |
Zero Page,Y | LDX $10,Y |
$nnnn |
Absolute | LDA $1000 |
$nnnn,X |
Absolute,X | LDA $1000,X |
$nnnn,Y |
Absolute,Y | LDA $1000,Y |
($nnnn) |
Indirect | JMP ($FFFC) |
($nn,X) |
Indexed Indirect | LDA ($10,X) |
($nn),Y |
Indirect Indexed | LDA ($10),Y |
$rr |
Relative | BNE loop |
65C02 extensions (when selected via .cpu 65c02):
| Syntax | Mode | Example |
|---|---|---|
($nn) |
Zero Page Indirect | LDA ($10) |
($nnnn,X) |
Indirect Absolute,X | JMP ($1000,X) |
Zero page and absolute modes are selected automatically based on the operand value. Forward references default to absolute (16-bit) and are optimized to zero page in subsequent passes if possible.
Directives
All directives may be written with or without a leading dot (.byte and BYTE are equivalent).
Origin
*= $0801 ; set program counter
.org $C000 ; alternate syntax
Data Definition
Bytes
.byte $FF, $00, 128 ; emit byte values
.byte "hello" ; emit ASCII string as bytes
.byte <label, >label ; lo/hi byte of a label
Aliases: BYTE, .BYTE, DC.B, .DC.B, DB, .DB, DFB
Words
.word $1000, label ; emit 16-bit values (little-endian)
Aliases: WORD, .WORD, DC.W, .DC.W, DW, .DW, DFW
Long (24-bit)
.long $123456 ; emit 24-bit value (little-endian)
Aliases: LONG, .LONG, DC.L, .DC.L, DL
32-bit (ACME)
!32 $12345678 ; emit 32-bit value (little-endian)
Text / Strings
.text "Trainer" ; emit PETSCII: prints "Trainer" via jsr $ffd2 (CHROUT)
.text "Price: {$24}9.99" ; embed $ via hex escape
.ascii "raw bytes here" ; emit raw ASCII bytes (no PETSCII conversion)
.text converts ASCII → PETSCII so the string prints with the intended case when
sent to CHROUT ($ffd2): uppercase A-Z → $C1-$DA, lowercase a-z → $41-$5A.
Write the string in normal case (.text "Trainer") — no manual case-swapping needed.
Use .ascii (or .asc) when you want the literal ASCII bytes with no translation.
Aliases: .text = TEXT, .TEXT (PETSCII). .ascii = .ASCII, ASC, .ASC,
DC, .DC (raw ASCII). A custom CMAP overrides both and applies to .text output.
PETSCII Text (ACME)
!pet "hello" ; emit PETSCII-encoded text (same as .text)
Uppercase A-Z maps to $C1-$DA, lowercase a-z maps to $41-$5A. The ACME !text,
!tx, and !raw pseudo-ops emit raw ASCII bytes (like .ascii).
Screen Codes
.screen "HELLO" ; emit C64 screen codes (not PETSCII)
.screen "@@@2026{$24}02{$24}28@@@" ; hex escapes work here too
.rscreen "HELLO" ; reverse-video screen codes (bit 7 set)
.pscreen "Le Mayday!" ; PETSCII -> screen codes, case preserved
There are three screen-code directives:
.screen— uppercase charset.A-Zanda-zboth fold to$01-$1A; digits and punctuation sit at standard screen-code positions. Aliases:SCREEN,.SCREEN,!SCR..rscreen— identical to.screen, but bit 7 is set on every byte, so the text displays in reverse video. Aliases:RSCREEN,.RSCREEN..pscreen("PETSCII screen") — encodes text as PETSCII (case-preserving, like.text) and then maps it through the standard PETSCII→screen-code conversion, so case is preserved: uppercaseA-Z→$41-$5A, lowercasea-z→$01-$1A. This matches the byte layout many PETSCII editors produce. Bracketed/escaped bytes (e.g.{$e0}) fall outside the printable range and pass through as literal screen codes. Aliases:PSCREEN,.PSCREEN.
.pscreen "Le Mayday!" ; -> 4C 05 20 4D 01 19 04 01 19 21 ("L","e"," ","M"...)
.pscreen "AB{$e0}c" ; -> 41 42 E0 03 (A, B, literal $E0, c)
All three also accept a bare comma-separated byte list (.screen 1,2,3), emitting the
values directly (.rscreen sets bit 7, .pscreen leaves them as-is).
XOR-Obfuscated Text (.x*)
Each string directive has an .x counterpart that takes an XOR key as its first
operand and XORs every emitted byte with it. Handy for lightly obfuscating strings that a
runtime routine XORs back with the same key:
.xtext $39,"Your Mom" ; .text bytes, each EOR #$39
.xascii $ff,"AB" ; raw ASCII, each EOR #$FF -> BE BD
.xscreen $80,"HELLO" ; .screen codes, each EOR #$80
.xrscreen $80,"HELLO" ; .rscreen codes, each EOR #$80
.xpscreen $80,"Le" ; .pscreen codes, each EOR #$80 -> CC 85
The key is any expression (e.g. $39, 128, a symbol) and is separated from the text by
a comma. All the usual escapes and {name} codes work in the string, and the XOR is
applied after the base directive's encoding. Bare byte lists are also supported
(.xtext $39,1,2,3). Aliases: XTEXT/.XTEXT, XASCII/.XASCII, XSCREEN/.XSCREEN,
XRSCREEN/.XRSCREEN, XPSCREEN/.XPSCREEN.
Space / Reserve
.ds 256 ; reserve 256 bytes (filled with fill byte)
.ds.w 10 ; reserve 10 words (20 bytes)
Aliases: DS, .DS, RES, .RES, DS.B, DS.W, DS.L
Fill
.fill 256, $EA ; fill 256 bytes with $EA
Aliases: FILL, .FILL, !FILL, !FI
Align
.align 256 ; align to next 256-byte boundary
Aliases: ALIGN, .ALIGN, !ALIGN
Repeat
.repeat 8, $FF ; emit $FF eight times
.repeat 3, $01, $02, $03 ; emit the sequence 01 02 03 three times (9 bytes)
.repeat 4, label ; word-sized labels emit lo-hi pairs
Aliases: REPEAT, .REPEAT, REPT, .REPT
For Loops
Generate data tables or unrolled code with a counter variable:
.for i = 0, 256
.byte sin(i * 2 * PI / 256) * 127 + 128
.endfor
Syntax: .for variable = start, count [, step]
| Parameter | Description |
|---|---|
variable |
Loop counter name (usable in expressions within the body) |
start |
Initial value |
count |
Number of iterations |
step |
Increment per iteration (default: 1) |
The loop variable is available in all expressions within the body, including instruction operands:
; Unroll 8 sprite enable bits
.for n = 0, 8
lda #1 << n
sta $d015
.endfor
Aliases: FOR/.FOR, ENDFOR/.ENDFOR/NEXT/.NEXT
Math Functions
Expressions support built-in math functions for generating lookup tables. All trig functions use radians. Intermediate calculations are done in floating-point; results are rounded to integers for .byte/.word output.
.byte sin(PI / 4) * 127 + 128 ; sine
.byte cos(0) * 255 ; cosine
.byte sqrt(144) ; = 12
.byte abs(-42) ; = 42
.byte min(10, 20) ; = 10
.byte max(10, 20) ; = 20
.byte pow(2, 8) ; = 256
.byte floor(3.7) ; = 3
.byte ceil(3.2) ; = 4
| Function | Description |
|---|---|
sin(x) |
Sine (x in radians) |
cos(x) |
Cosine |
tan(x) |
Tangent |
asin(x) |
Arc sine |
acos(x) |
Arc cosine |
atan(x) |
Arc tangent |
atan2(y,x) |
Two-argument arc tangent |
sqrt(x) |
Square root |
abs(x) |
Absolute value |
floor(x) |
Round down |
ceil(x) |
Round up |
round(x) |
Round to nearest |
int(x) |
Truncate to integer |
log(x) |
Natural logarithm |
log2(x) |
Base-2 logarithm |
exp(x) |
e^x |
pow(x,y) |
x raised to power y |
min(a,b) |
Minimum of two values |
max(a,b) |
Maximum of two values |
Built-in constants: PI (3.14159...), E (2.71828...)
Floating-point literals are supported in expressions: 3.14159, 0.5, etc.
Note: The built-in constant E returns Euler's number (2.71828...) when used as a bare identifier in expressions. If you have a label named E, it will be shadowed by the built-in constant. Use a different label name (e.g., E_ADDR, E_PTR) to avoid this. The constant PI has the same potential for collision but PI is rarely used as a label name.
Bounce (Wave Table)
.bounce preset, count, min, max, phases
Generates a cosine wave table. Parameters:
| Parameter | Description |
|---|---|
preset |
Wave preset (1 = cosine) |
count |
Number of table entries |
min |
Minimum output value |
max |
Maximum output value |
phases |
Number of complete wave cycles |
Example:
.bounce 1, 256, 0, 255, 1 ; 256-byte cosine wave, full range, 1 cycle
Aliases: BOUNCE, .BOUNCE
BASIC SYS Line
*= $0801
.sysline 10, $2000 ; BASIC: 10 SYS 8192
Generates a complete C64 BASIC line that performs a SYS call:
[next-line ptr lo/hi] [line# lo/hi] $9E(SYS) $20(space) [address as ASCII] $00 $00
The second parameter can be a decimal number, hex value, or label reference.
Aliases: SYSLINE, .SYSLINE
Binary Include
.binary "sprite.bin" ; include entire file
.binary "data.bin", 64 ; skip first 64 bytes
.binary "data.bin", 64, 128 ; skip 64, include 128 bytes
File paths are resolved relative to the source file's directory.
Aliases: INCBIN, .INCBIN, BINARY, .BINARY, !BINARY, !BIN
Source Include
.include "routines.asm" ; relative to current source file
!src <sprites.asm> ; ACME angle-bracket syntax
.append "vectors.asm" ; as65 syntax
Include paths are searched in order: absolute path, relative to current source file, then each -I include path. Nested includes are supported (max depth 64) with circular include detection.
Use .incdir (DASM) to add search paths at assembly time:
.incdir "libs/"
.include "math.asm" ; found in libs/math.asm
Aliases: INCLUDE/.INCLUDE, !SOURCE/!SRC, APPEND/.APPEND, INCDIR/.INCDIR
Structures
Define named data structures with automatic field offset calculation:
Player STRUCT
xpos .byte 0
ypos .byte 0
speed .word 0
health .byte 0
ENDSTRUCT
ldx #Player ; = 5 (total struct size)
lda $C000+Player.xpos ; offset 0
lda $C000+Player.ypos ; offset 1
lda $C000+Player.speed ; offset 2
lda $C000+Player.health ; offset 4
Inside a STRUCT block, labels define field names and data directives (.byte, .word, .long, .ds) define field sizes. No bytes are emitted; the struct name is defined as a constant equal to the total size, and each field becomes StructName.FieldName equal to its byte offset.
Aliases: STRUCT/.STRUCT, ENDSTRUCT/.ENDSTRUCT/ENDS/.ENDS
Character Mapping
Remap character values for .text output:
CMAP 'A', 'Z', $01 ; map A-Z to $01-$1A (screen codes)
.text "HELLO" ; emits 08 05 0C 0C 0F (CMAP wins over PETSCII)
CMAP ; reset to identity (no mapping)
.text "HELLO" ; emits C8 C5 CC CC CF (PETSCII)
CMAP start, end, mapTo remaps all input bytes from start through end so that start maps to mapTo, start+1 maps to mapTo+1, and so on. CMAP with no arguments resets to identity mapping. When a CMAP is active it takes precedence over .text's PETSCII conversion; with no CMAP, .text applies PETSCII and .ascii emits raw bytes.
Aliases: CMAP/.CMAP
Symbols and Constants
EQU / Constants
SCREEN .equ $0400
BORDER = $D020
VIC EQU $D000
Constants are immutable once defined.
SET / Variables
counter .set 0
counter .set counter + 1 ; redefinable
Aliases: SET, .SET, !SET
Conditional Assembly
.if expression
; assembled when expression is non-zero
.else
; assembled when expression is zero
.endif
.ifdef SYMBOL
; assembled when SYMBOL is defined
.endif
.ifndef SYMBOL
; assembled when SYMBOL is not defined
.endif
Aliases: IF/.IF/!IF, IFDEF/.IFDEF/!IFDEF, IFNDEF/.IFNDEF/!IFNDEF, ELSE/.ELSE, ENDIF/.ENDIF/EIF/.EIF
Macros
Definition
spritePos .macro
lda #\1
sta $D000
lda #\2
sta $D001
.endm
Invocation
#spritePos 100, 150 ; tmpx-style: # prefix
+spritePos 100, 150 ; ACME-style: + prefix
spritePos 100, 150 ; also works without prefix
Parameter Substitution
| Syntax | Style | Example |
|---|---|---|
\1, \2, ... |
tmpx | lda #\1 |
{1}, {2}, ... |
DASM | lda #{1} |
\0 or {0} |
Argument count | .byte \0 |
\0 (or {0}) expands to the number of arguments passed to the macro, enabling variadic macros:
emit .macro
.byte \0 ; emit argument count first
.if \0 > 0
.byte \1
.endif
.if \0 > 1
.byte \2
.endif
.endm
#emit 10, 20 ; emits: 02 0A 14
#emit 42 ; emits: 01 2A
Early Macro Exit
check .macro
.if \1 == 0
EXITM ; stop expanding this macro
.endif
sta \1
.endm
EXITM immediately stops expansion of the current macro. Any lines after EXITM in the macro body are skipped. Open .if blocks inside the macro are automatically cleaned up.
Aliases: EXITM/.EXITM
Local Labels in Macros
delay .macro
?loop dex
bne ?loop
.endm
Labels prefixed with ? are automatically made unique per macro invocation. The .# suffix is also replaced with a unique counter.
Maximum macro nesting depth: 32 levels.
Scoping
.scope
.loop: dex
bne .loop ; local to this scope
.endscope
.subroutine name
; labels are scoped to this subroutine
Aliases: SCOPE/.SCOPE/!ZONE/!ZN, ENDSCOPE/.ENDSCOPE, SUBROUTINE/.SUBROUTINE
Segments
.segment "code"
.code ; shorthand for code segment
.data ; shorthand for data segment
.bss ; shorthand for BSS segment
Debug Output
.echo "Value is: ", expression
.print "Current PC: ", *
Aliases: ECHO/.ECHO, PRINT/.PRINT
Assembler Messages
!warn "Check this value" ; emit warning during assembly
!error "Bad configuration" ; emit error
!serious "Fatal problem" ; emit fatal error and halt
Assertions
.assert * < $C000, "Code exceeds ROM boundary!"
Aliases: ASSERT/.ASSERT
Processor Selection
.cpu 6502 ; NMOS 6502 (default)
.cpu 65c02 ; 65C02
.processor 6502
Aliases: CPU/.CPU/!CPU, PROCESSOR/.PROCESSOR
Supported Instructions
NMOS 6502 (56 official opcodes)
ADC AND ASL BCC BCS BEQ BIT BMI BNE BPL BRK BVC BVS
CLC CLD CLI CLV CMP CPX CPY DEC DEX DEY EOR INC INX
INY JMP JSR LDA LDX LDY LSR NOP ORA PHA PHP PLA PLP
ROL ROR RTI RTS SBC SEC SED SEI STA STX STY TAX TAY
TSX TXA TXS TYA
Undocumented / Illegal Opcodes
Illegal opcodes are enabled by default. All common aliases are recognized:
AHX ALR ANC ANE ARR ASR AXS DCP DOP HLT ISB ISC JAM
KIL LAS LAX LXA RLA RRA SAX SBX SHA SHS SHX SHY SLO
SRE SXA SYA TAS TOP XAA
Platform: C64
The following predefined constants are always available for common C64 hardware addresses:
Zero Page: D6510, R6510, TXTTAB, VARTAB, ARYTAB, STREND, FRETOP, MEMSIZ, CURLIN, TXTPTR, FAC, FACSGN, ARG, PNTR, LNMX, TBLX, TIME/TIMEH/TIMEM/TIMEL
VIC-II ($D000): VIC, SP0X..SP7X, SP0Y..SP7Y, MSIGX, SCROLY/CR1, RASTER, SPENA, SCROLX/CR2, VMCSB, VICIRQ, IRQMASK, EXTCOL/BGCOL0..BGCOL3, SPMC0/SPMC1, and more.
SID ($D400): SID, voice frequency/pulse/control/envelope, filter, volume registers.
CIA ($DC00/$DD00): CIA1/CIA2, port/timer/interrupt registers.
KERNAL: CHROUT, CHRIN, GETIN, CLRSCR, SETLFS, SETNAM, OPEN, CLOSE, LOAD, SAVE, STOP, PLOT, IOBASE, SCNKEY, LISTEN, TALK, READST, and more.
BASIC: BASIC_START, BASIC_END, BASIC_COLD, BASIC_WARM
Colors: BLACK, WHITE, RED, CYAN, PURPLE, GREEN, BLUE, YELLOW, ORANGE, BROWN, LIGHT_RED, DARK_GREY, GREY, LIGHT_GREEN, LIGHT_BLUE, LIGHT_GREY
Optimization Control
NOOPT
lda $10 ; assembles as AD 10 00 (absolute, 3 bytes)
OPT
lda $10 ; assembles as A5 10 (zero page, 2 bytes)
By default, 6asm automatically optimizes absolute addressing to zero page when the operand value fits in one byte. NOOPT disables this optimization — all addresses are assembled as absolute (16-bit). OPT re-enables it. This is useful when you need stable instruction sizes (e.g., self-modifying code).
Aliases: OPT/.OPT, NOOPT/.NOOPT
Cycle Timing
Mark code blocks for cycle counting in the listing file:
.cycles
loop inc $d020 ; 6
lda $d012 ; 4
cmp #$80 ; 2
bne loop ; 2
.endcycles ; ;>>> 14 cycles
When --list is used, a Cy column appears showing the base cycle count per instruction, with a total at each .endcycles. Cycle counts are base values (not counting page-crossing or branch-taken penalties).
Raster Budget
Set a cycle budget to get warnings when code exceeds a raster line:
.rasterbudget 63 ; PAL: 63 cycles per line
.cycles
lda #$01 ; 2
sta $d020 ; 4
.endcycles ; ;>>> 6/63 cycles (57 free)
.rasterbudget 23 ; badline: only 23 cycles
.cycles
; ... tight code here ...
.endcycles ; warns if over 23
The listing shows used/budget and whether you're free or over. The assembler emits a warning if a block exceeds the budget.
Use .rasterbudget with no argument to clear the budget.
Aliases: CYCLES/.CYCLES, ENDCYCLES/.ENDCYCLES, RASTERBUDGET/.RASTERBUDGET
Hardware Alignment
Directives that align the program counter to hardware-required boundaries and warn if the following data exceeds the expected block size:
.spritealign ; align to 64B boundary
.for i = 0, 63
.byte i ; 63 bytes of sprite data — fits in 64B block
.endfor
.charsetalign ; align to 2048B boundary
.binary "charset.bin" ; warns if > 2048 bytes
| Directive | Alignment | Max Size | Use Case |
|---|---|---|---|
.spritealign |
64 bytes | 64B | Sprite data blocks |
.charsetalign |
2048 bytes | 2048B | Character sets |
.bitmapalign |
8192 bytes | 8192B | Bitmap graphics |
.screenalign |
1024 bytes | 1024B | Screen RAM |
.sidalign |
256 bytes | 256B | SID music data |
The limit auto-clears on the next *=, .org, or alignment directive.
Bank Configuration
Track the C64 memory bank configuration ($01 register) and warn when code accesses memory regions that aren't visible:
.bank $37 ; default: all ROM + I/O visible
lda $d020 ; OK
jsr $ffd2 ; OK — Kernal visible
.bank $35 ; I/O + RAM (Kernal banked out)
lda $d020 ; OK — I/O visible
jsr $ffd2 ; WARNING — Kernal not mapped
.bank $34 ; all RAM
lda $d020 ; WARNING — I/O not visible
.bank ; clear bank tracking
| Bank Value | LORAM | HIRAM | CHAREN | $A000 | $D000 | $E000 |
|---|---|---|---|---|---|---|
$37 |
1 | 1 | 1 | BASIC ROM | I/O | Kernal ROM |
$36 |
0 | 1 | 1 | RAM | I/O | Kernal ROM |
$35 |
1 | 0 | 1 | RAM | I/O | RAM |
$34 |
0 | 0 | 0 | RAM | RAM | RAM |
$33 |
1 | 1 | 0 | BASIC ROM | Char ROM | Kernal ROM |
Aliases: BANK/.BANK
VICE Integration
Symbol File
Generate a VICE monitor symbol file with --vice:
6asm --vice game.asm # generates game.lbl
Format: al C000 .label — loadable in VICE with ll "game.lbl".
Breakpoints and Watchpoints
Emit debug commands to the VICE symbol file:
.break ; breakpoint at current PC
.break $0830 ; breakpoint at specific address
.watch $d020 ; watchpoint on store (default)
.watchstore $d020 ; explicit store watchpoint
.watchload $dc00 ; load watchpoint
These appear in the .lbl file as VICE monitor commands:
break $0820
watch store $D020
watch load $DC00
Aliases: BREAK/.BREAK, WATCH/.WATCH, WATCHSTORE/.WATCHSTORE, WATCHLOAD/.WATCHLOAD
Peephole Optimizer
The --analyze flag runs a post-assembly analysis suggesting optimizations:
6asm --analyze --list game.asm
Detects:
- Illegal opcode substitutions:
LDA+LDXsame address →LAX,DEC+CMP→DCP,ASL+ORA→SLO, and more (only reliable/stable illegals) - Redundant loads:
STA addrthenLDA addr, back-to-back loads to same register, repeated immediate loads with no modification between - Redundant transfers:
TAX+TXA,TYA+TAY - Duplicate flag operations:
CLC+CLC,SEC+SEC
Suggestions are printed to console and appended to the listing file with byte/cycle savings estimates.
Listing File
Generate with --list. The listing includes:
- Source lines with assembled bytes and addresses
- File boundary markers for included files
- Cycle timing column (when
.cyclesblocks are used) - Symbol table with definition file:line
- Cross-reference: unreferenced labels and symbol reference locations
- Memory map with segment sizes and fill byte counts
- Optimization suggestions (when
--analyzeis used)
Multi-Pass Assembly
6asm uses multi-pass assembly (up to 10 passes by default) to resolve forward references and optimize addressing modes. On each pass:
- Forward-referenced labels are resolved as their values become known.
- Absolute addresses are optimized to zero page when possible.
- Assembly continues until the symbol table stabilizes.
Compatibility
6asm accepts source files written for tmpx (Turbo Macro Pro cross-assembler), DASM, and ACME assembler syntax.
tmpx Compatibility
#macroinvocation syntax\1,\2parameter substitution.screen,.bounce,.repeat,.binarydirectives*and.as program counter references- Column-1 labels without colons
ACME Compatibility
6asm supports the !-prefixed pseudo-op syntax used by the ACME cross-assembler:
| ACME Syntax | 6asm Equivalent | Notes |
|---|---|---|
!byte, !by, !8, !08 |
.byte |
Emit bytes |
!word, !wo, !16 |
.word |
Emit 16-bit little-endian |
!24 |
.long |
Emit 24-bit little-endian |
!32 |
(new) | Emit 32-bit little-endian |
!fill, !fi |
.fill |
Fill with count,value |
!align |
.align |
Alignment |
!text, !tx, !raw |
.ascii |
Raw ASCII text |
!pet |
.text |
PETSCII text (case-converted for CHROUT) |
!scr |
.screen |
Screen code text |
!source, !src |
.include |
Include source file |
!binary, !bin |
.incbin |
Include binary file |
!if / !ifdef / !ifndef |
.if / .ifdef / .ifndef |
Conditional assembly |
!set |
.set |
Reassignable symbol |
!macro |
.macro |
Macro definition |
!cpu |
.processor |
Set target CPU |
!warn |
(new) | Emit assembler warning |
!error |
(new) | Emit assembler error |
!serious |
(new) | Emit fatal error and stop |
!eof, !endoffile |
END |
Stop processing file |
!initmem |
(new) | Set fill byte for gaps |
!zone, !zn |
.scope |
Scoping / zones |
!to |
(stub) | Output file (use CLI -o instead) |
!pseudopc |
(stub) | Offset assembly |
!convtab, !ct |
(stub) | Character conversion table |
!sl, !svl, !sal |
(stub) | Symbol list export |
!al, !as, !rl, !rs |
(stub) | 65816 register size |
ACME macro invocation uses + prefix: +macroName arg1, arg2
Not yet supported:
- { } brace-delimited blocks for conditionals/macros
- Anonymous +/- labels
!for note: ACME uses !for .i, 1, 8 { ... } with brace-delimited bodies. 6asm supports .for loops with .endfor block termination instead: .for i = 0, 8 / .endfor. The !for directive is recognized but uses 6asm's .for syntax, not ACME's brace syntax.
ACME Syntax Conflicts
| Feature | ACME | 6asm | Resolution |
|---|---|---|---|
^ operator |
Power-of | Bitwise XOR | Kept as XOR — standard across most assemblers |
& prefix |
Octal number | Bitwise AND | Kept as AND — use @ for octal instead |
>>> operator |
Logical shift right | Not supported | Use >> (arithmetic shift) |
EOR keyword |
Bitwise XOR | Not supported | Use ^ or .XOR. |
DASM Compatibility
{1},{2}parameter substitutionSUBROUTINEscopingSEG/SEGMENTdirectives (stubs)DC.B/DC.W/DC.Ldata directivesINCLUDE/INCBIN/INCDIRdirectives
as65 Compatibility
.INCLUDE/.APPENDfor file inclusion
License
MIT