64tass v1.51 r675 manual

This is the manual for 64tass, the multi pass optimizing macro assembler for the 65xx series of processors. Key features:

This is a development version, features or syntax may change over time. Not everything is backwards compatible.

Project page: http://sourceforge.net/projects/tass64/


Usage tips

64tass is a command line assembler, the source can be written in any text editor. As a minimum the source filename must be given on the command line. The -a parameter is highly recommended if the source is Unicode or ASCII.

64tass -a src.asm

There are also some useful parameters which are described later.

For comfortable compiling I use such Makefiles (for make):

demo.prg: source.asm macros.asm pic.drp music.bin
        64tass -C -a -B -i source.asm -o demo.tmp
        pucrunch -ffast -x 2048 demo.tmp >demo.prg

This way demo.prg is recreated by compiling source.asm whenever source.asm, macros.asm, pic.drp or music.bin had changed.

Of course it's not much harder to create something similar for win32 (make.bat), however this will always compile and compress:

64tass.exe -C -a -B -i source.asm -o demo.tmp
pucrunch.exe -ffast -x 2048 demo.tmp >demo.prg

Here's a slightly more advanced Makefile example with default action as testing in VICE, clean target for removal of temporary files and compressing using an intermediate temporary file:

all: demo.prg
        x64 -autostartprgmode 1 -autostart-warp +truedrive +cart $<

demo.prg: demo.tmp
	pucrunch -ffast -x 2048 $< >$@

demo.tmp: source.asm macros.asm pic.drp music.bin
        64tass -C -a -B -i $< -o $@

.INTERMEDIATE: demo.tmp
.PHONY: all clean
clean:
        $(RM) demo.prg demo.tmp

It's useful to add a basic header to your source files like the one below, so that the resulting file is directly runnable without additional compression:

        *= $0801
        .word (+), 2005  ;pointer, line number
        .null $9e, ^start;will be sys 4096
+	.word 0          ;basic line end

        *= $1000

start	rts

A frequently coming up question is, how to automatically allocate memory, without hacks like ∗=∗+1? Sure there's .byte and friends for variables with initial values but what about zero page, or RAM outside of program area? The solution is to not use an initial value by using ? or not giving a fill byte value to .fill.

        *= $02
p1	.word ?         ;a zero page pointer
temp	.fill 10        ;a 10 byte temporary area

Space allocated this way is not saved in the output as there's no data to save at those addresses.

What about some code running on zero page for speed? It needs to be relocated, and the length must be known to copy it there. Here's an example:

        ldx #size(zpcode)-1;calculate length
-       lda zpcode,x
        sta wrbyte,x
        dex             ;install to zeropage
        bpl -
        jsr wrbyte
        rts
;code continues here but is compiled to run from $02
zpcode  .logical $02
wrbyte  sta $ffff       ;quick byte writer at $02
        inc wrbyte+1
        bne +
        inc wrbyte+2
+	rts
	.here

The assembler supports lists and tuples, which does not seems interesting at first as it sound like something which is only useful when heavy scripting is involved. But as normal arithmetic operations also apply on all their elements at once, this could spare quite some typing and repetition.

Let's take a simple example of a low/high byte jump table of return addresses, this usually involves some unnecessary copy/pasting to create a pair of tables with constructs like >(label−1).

jumpcmd lda hibytes,x   ; selected routine in X register
        pha
        lda lobytes,x   ; push address to stack
        pha
        rts             ; jump, rts will increase pc by one!
; Build an anonymous list of jump addresses minus 1
-	= (cmd_p, cmd_c, cmd_m, cmd_s, cmd_r, cmd_l, cmd_e)-1
lobytes .byte <(-)      ; low bytes of jump addresses
hibytes .byte >(-)      ; high bytes

There are some other tips below in the descriptions.


Expressions and data types

Integer constants

Integer constants can be entered as a string of decimal numbers of arbitary length. The following operations are accepted:

Integer operators and functions
x + yadd x to y2 + 2 is 4
x − ysubtract y from x41 is 3
x ∗ ymultiply x with y23 is 6
x / yinteger divide x by y7 / 2 is 3
x % yinteger modulo of x divided by y5 % 2 is 1
x ∗∗ yx raised to power of y2 ∗∗ 4 is 16
−xnegated value2 is −2
+xunchanged+2 is 2
~x−x − 1~3 is −4
<xlower byte<2049 is $01
>xhigher byte>2049 is $08
`xbank byte`65536 is $01
<>xlower word<>65537 is $0001
>`xhigher word<`65537 is $1000
><xlower byte swapped word><2049 is $0108
x <=> yx compares to y2 <=> 5 is −1
x == yx equals to y2 == 3 is false
x != yx does not equal to y2 != 3 is true
x < yx is less than y2 < 3 is true
x > yx is more than y2 > 3 is false
x >= yx is more than y or equals2 >= 3 is false
x <= yx is less than y or equals2 <= 3 is true
x | ybitwise or2 | 6 is 6
x ^ ybitwise xor2 ^ 6 is 4
x & ybitwise and2 & 6 is 2
x << ylogical shift left1 << 3 is 8
x >> yarithmetic shift right−8 >> 3 is −1
abs(a)absolute valueabs(−1) is 1
sign(a)sign value (−1, 0, 1)sign(−4) is −1
int(a)convert to integerint(11) is 11
bool(a)convert to booleanbool(1) is true

An integer has a truth value of true if it's non-zero. The true value is the same as 1.

Integers are automatically promoted to float as necessary in expressions.

        .byte 23        ; decimal

        lda #<label
        ldy #>label
        jsr $ab1e

        ldx #<>source   ; word extraction
        ldy #<>dest
        lda #size(source)-1
        mvn #`source, #`dest; bank extraction

        lda #((bitmap & $2000) >> 10) | ((screen & $3c00) >> 6)
        sta $d018

Bit string constants

Bit string constants can be entered as hexadecimal by a leading dollar sign or binary with a leading percent sign. The following operations are accepted:

Bit string operators and functions
~xinvert bits~%101 is ~%101
y .. xconcatenate bits$a .. $b is $ab
y x nrepeat%101 x 3 is %101101101
x[n]extract bit(s)$a[1] is 0
x[s]slice bits$1234[4:8] is $3
x | ybitwise or~$2 | $6 is ~$0
x ^ ybitwise xor~$2 ^ $6 is ~$4
x & ybitwise and~$2 & $6 is $4
abs(a)absolute valueabs(%11) is 3
sign(a)sign value (−1, 0, 1)sign(~%11) is −1
int(a)convert to integerint(%11) is 3
bool(a)convert to booleanbool(%11) is true
len(a)length in bitslen($034) is 12
all(a)all bits set or no bits at allall($f) is true
any(a)at least one bit setany(~$f) is false

A bit string has a truth value of true if it's integer value is non-zero.

Length of bit string constants are defined in bits and is calculated from the number of digits used including leading zeros.

Bit strings are automatically promoted to integer or floating point as necessary in expressions. The higher bits are extended with zeros or ones as needed.

        .byte $33       ; hex
        .byte %00011111 ; binary

        lda $01
        and #~$07
        ora #$05
        sta $01

        lda $d015
        and #~%00100000 ;clear a bit
        sta $d015

Floating point constants

Floating point constants have a radix point in them and optionally an exponent. A decimal exponent is e while a binary one is p. The following operations can be used:

Floating point operators and functions
x + yadd x to y2.2 + 2.2 is 4.4
x − ysubtract y from x4.11.1 is 3.0
x ∗ ymultiply x with y1.53 is 4.5
x / yinteger divide x by y7.0 / 2.0 is 3.5
x % yinteger modulo of x divided by y5.0 % 2.0 is 1.0
x ∗∗ yx raised t power of y2.0 ∗∗ −1 is 0.5
−xnegated value2.0 is −2.0
+xunchanged+2.0 is 2.0
x <=> yx compares to y5.0 <=> 3.0 is 1
x == yx equals to y2.0 == 3.0 is false
x != yx does not equal to y2.0 != 3.0 is true
x < yx is less than y2.0 < 3.0 is true
x > yx is more than y2.0 > 3.0 is false
x >= yx is more than y or equals2.0 >= 3.0 is false
x <= yx is less than y or equals2.0 <= 3.0 is true
x | ybitwise or2.5 | 6.5 is 6.5
x ^ ybitwise xor2.5 ^ 6.5 is 4.0
x & ybitwise and2.5 & 6.5 is 2.5
x << ylogical shift left1.0 << 3.0 is 8.0
x >> yarithmetic shift right−8.0 >> 4 is −0.5
~xalmost −x~2.1 is almost −2.1
abs(a)absolute valueabs(−1.0) is 1.0
sign(a)sign value (−1, 0, 1)sign(−4.0) is −1
floor(a)round downfloor(−4.8) is −5.0
round(a)round to nearest away from zeroround(4.8) is 5.0
ceil(a)round upceil(1.1) is 2.0
trunc(a)round down towards zerotrunc(−1.9) is −1
frac(a)fractional partfrac(1.1) is 0.1
sqrt(a)square rootsqrt(16.0) is 4.0
cbrt(a)cube rootcbrt(27.0) is 3.0
log10(a)common logarithmlog10(100.0) is 2.0
log(a)natural logarithmlog(1) is 0.0
exp(a)exponentialexp(0) is 1.0
pow(a, b)a raised to power of bpow(2.0, 3.0) is 8.0
sin(a)sinesin(0.0) is 0.0
asin(a)arc sineasin(0.0) is 0.0
sinh(a)hyperbolic sinesinh(0.0) is 0.0
cos(a)cosinecos(0.0) is 1.0
acos(a)arc cosineacos(1.0) is 0.0
cosh(a)hyperbolic cosinecosh(0.0) is 1.0
tan(a)tangenttan(0.0) is 0.0
atan(a)arc tangentatan(0.0) is 0.0
tanh(a)hyperbolic tangenttanh(0.0) is 0.0
rad(a)degrees to radianrad(0.0) is 0.0
deg(a)radian to degreesdeg(0.0) is 0.0
hypot(y, x)polar distancehypot(4.0, 3.0) is 5.0
atan2(y, x)polar angleatan2(0.0, 3.0) is 0.0
float(a)convert to floating pointfloat(1) is 1.0

A floating point number has a truth value of true if it's non-zero.

As usual comparing floating point numbers for (non) equality is a bad idea due to rounding errors.

There are no predefined floating point constants, define them as necessary. Hint: pi is rad(180) and e is exp(1).

Floating point numbers are automatically truncated to integer as necessary.

Fixed point conversion can be done by using the shift operators for example a 8.16 fixed point number can be calculated as (3.14 << 16) & $ffffff. The binary operators operate like if the floating point number would be a fixed point one. This is the reason for the strange definition of inversion.

        .byte 3.66e1       ; 36.6, truncated to 36
        .byte $1.8p4       ; 4:4 fixed point number (1.5)
        .int 12.2p8        ; 8:8 fixed point number (12.2)

Character string constants

Strings are enclosed in single or double quotes and can hold any Unicode character. Operations like indexing or slicing are always done on the original representation. The current encoding is only applied when it's used in expressions as numeric constants or in context of text data directives. Doubling the quotes inside the strings escapes them.

String operators and functions
y .. xconcatenate strings"a" .. "b" is "ab"
y in xis substring of"b" in "abc" is true
a x nrepeat"ab" x 3 is "ababab"
a[i]character from start"abc"[1] is "b"
a[i]character from end"abc"[−1] is "c"
a[s]no change"abc"[:] is "abc"
a[s]cut off start"abc"[1:] is "bc"
a[s]cut off end"abc"[:−1] is "ab"
a[s]reverse"abc"[::−1] is "cba"
bool(a)convert to booleanbool("a") is true
len(a)number of characterslen("abc") is 3
all(a)all characters non-zero or empty stringall("c") is true
any(a)at least one non-zero characterany("c") is true
str(a)convert to stringstr($1) is "$1"
repr(a)representation to stringstr("a") is '"a"'
format(s, ∗)string formattingformat("%02x", 12) is "0c"

A string has a truth value of true if it contains at least one character which is not translated to zero.

Strings are converted to integers, byte and bit strings as necessary using the current encoding and escape rules. For example when using a sane encoding "z"−"a" is 25.

Indexing characters with positive integers start with zero. Negative indexes are translated internally by adding the number of characters to them, therefore −1 can be used to access the last character. Indexing with list of integers is possible as well so "abc"[(−1, 0, 1)] is "cab".

Slicing is an operation when parts of string are extracted from a start position to an end position with a step value. These parameters are separated with colons enclosed in square brackets and are all optional. Their default values are [start:maximum:step=1]. Negative start and end characters are converted to positive internally by adding the length of string to them. Negative step operates in reverse direction, non single steps will jump over characters.

mystr   = "oeU"         ; text
        .text 'it''s'   ; text: it's
        .word "ab"+1    ; character, results in "bb" usually

        .text "text"[:2]     ; "te"
        .text "text"[2:]     ; "xt"
        .text "text"[:-1]    ; "tex"
        .text "reverse"[::-1]; "esrever"

String formatting can interpret a list of values and convert them to a string. The converted values are inserted at the % sign which is followed by conversion flags, minimum field length, and conversion type. The these flags can be used:

Formatting flags
#alternate form ($a, %10, 10.)
width/precision from list
.precision
0pad with zeros
left adjusted (default right)
 blank when positive or minus sign
+sign even if positive

The following conversions are implemented:

Formatting conversions
a Ahexadecimal floating point (uppercase)
bbinary
cunicode character
ddecimal
e Eexponential float (uppercase)
f Ffloating point (uppercase)
g Gexponential/floating point
sstring
rrepresentation
x Xhexadecimal (uppercase)
%percent sign
        .text format("%#04x bytes left", 1000); $03e8 bytes left

Byte string constants

Byte strings are like strings, but hold bytes instead of characters. They can be created by prefixing quoted strings with a b, this converts the string using the current encoding to bytes.

Byte string operators and functions
y .. xconcatenate stringsb"a" .. b"b" is b"ab"
y in xis substring ofb"b" in b"abc" is true
a x nrepeatb"ab" x 3 is b"ababab"
a[i]byte from startb"abc"[1] is b"b"
a[i]byte from endb"abc"[−1] is b"c"
a[s]no changeb"abc"[:] is b"abc"
a[s]cut off startb"abc"[1:] is b"bc"
a[s]cut off endb"abc"[:−1] is b"ab"
a[s]reverseb"abc"[::−1] is b"cba"
bool(a)convert to booleanbool(b"a") is true
len(a)number of byteslen(b"abc") is 3
all(a)all bytes non-zero or no bytesall(b"c") is true
any(a)at least one non-zero byteany(b"c") is true

A byte string has a truth value of true if it contains at least one non-zero byte.

Indexing and slicing works as with character strings.

        .enc screen	;use screen encoding
mystr   = b"oeU"        ;convert text to bytes
        .enc none	;normal encoding

        .text mystr     ;text as originally encoded

List and tuples

Lists and tuples can hold a collection of values. Lists are defined from values separated by comma between square brackets [1, 2, 3], an empty list is []. Tuples are similar but are enclosed in parentheses instead. An empty tuple is (), a single element tuple is (4,) to differentiate from normal numeric expression parentheses. When nested they function similar to an array. Currently both types are immutable.

List and tuple operators and functions
y .. xconcatenate lists[1] .. [2] is [1, 2]
y in xis member of list2 in [1, 2, 3] is true
a x nrepeat[1, 2] x 2 is [1, 2, 1, 2]
a[i]element from start("1", 2)[1] is 2
a[i]element from end("1", 2, 3)[−1] is 3
a[s]no change(1, 2, 3)[:] is (1, 2, 3)
a[s]cut off start(1, 2, 3)[1:] is (2, 3)
a[s]cut off end(1, 2.0, 3)[:−1] is (1, 2.0)
a[s]reverse(1, 2, 3)[::−1] is (3, 2, 1)
∗aconvert to argumentsformat("%d: %s", ∗mylist)
len(a)number of elementslen([1, 2, 3]) is 3
all(a)all elements true or empty listall([1, 1, 0]) is false
any(a)at least one true elementany([1, 1, 0]) is true
range(s,e,t)create a list with values from a rangerange(3) is [0,1,2]

Arithmetic operations are applied on the all elements recursively, therefore [1, 2] + 1 is [2, 3], and abs([1, −1]) is [1, 1].

Arithmetic operations between lists are applied one by one on their elements, so [1, 2] + [3, 4] is [4, 6].

When lists form an array and columns/rows are missing the smaller array is stretched to fill in the gaps if possible, so [[1], [2]] ∗ [3, 4] is [[3, 4], [6, 8]].

Indexing elements with positive integers start with zero. Negative indexes are transformed to positive by adding the number of elements to them, therefor −1 is the last element. Indexing with list of integers is possible as well so [1, 2, 3][(−1, 0, 1)] is [3, 1, 2].

Slicing is an operation when parts of list or tuple are extracted from a start position to an end position with a step value. These parameters are separated with colons enclosed in square brackets and are all optional. Their default values are [start:maximum:step=1]. Negative start and end elements are converted to positive internally by adding the number of elements to them. Negative step operates in reverse direction, non single steps will jump over elements.

mylist  = [1, 2, "whatever"]
mytuple = (cmd_e, cmd_g)

mylist  = ("e", cmd_e, "g", cmd_g, "i", cmd_i)
keys    .text mylist[::2]    ; keys ("e", "g", "i")
call_l  .byte <mylist[1::2]-1; routines (<cmd_e−1, <cmd_g−1, <cmd_i−1)
call_h  .byte >mylist[1::2]-1; routines (>cmd_e−1, >cmd_g−1, >cmd_i−1)

The range(start, end, step) built-in function can be used to create lists of integers in a range with a given step value. At least the end must be given, the start defaults to 0 and the step to 1. Sounds not very useful, so here are a few examples:

;Bitmask table, 8 bits from left to right
        .byte %10000000 >> range(8)
;Classic 256 byte single period sinus table with values of 0−255.
        .byte 128.5 + 127 * sin(range(256) * rad(360.0/256))
;Screen row address tables
-       = $400 + range(0, 1000, 40)
scrlo   .byte <(-)
scrhi   .byte >(-)

Dictionaries

Dictionaries are unsorted lists holding key and value pairs. Definition is done by collecting key:value pairs separated by comma between braces {1:"value", "key":1, :"optional default value"}.

Looking up a non existing key is normally an error unless a default value is given. An empty dictionary is {}. Currently this type is immutable. Numeric and string keys are accepted, the value can be anything.

Dictionary operators and functions
x[i]value lookup{"1":2}["1"] is 2
y in xis a key1 in {1:2} is true
len(x)number of elementslen({1:2, 3:4]) is 2

A dictionary has a truth value of true if it contains at least one key value pair.

        .text {1:"one", 2:"two"}[2]; "two"

Code

Code holds the result of compilation in binary and other enclosed objects. In an arithmetic operation it's used as the numeric address of the memory where it starts. The compiled content remains static even if later parts of the source overwrite the same memory area.

Indexing and slicing of code to access the compiled content might be implemented differently in future releases. Use this feature at your own risk for now, you might need to update your code later.

Label operators and functions
.memberlabel.locallabel
a[i]element from startlabel[1]
a[i]element from endlabel[−1]
a[s]copy as tuplelabel[:]
a[s]cut off start, as tuplelabel[1:]
a[s]cut off end, as tuplelabel[:−1]
a[s]reverse, as tuplelabel[::−1]
len(a)number of elementslen(label)
size(a)size in bytessize(label)

A code object has a truth value of true when it's address is non-zero.

mydata  .word 1, 4, 3
mycode  .block
local   lda #0
        .bend

        ldx #size(mydata) ;6 bytes (3∗2)
        ldx #len(mydata)  ;3 elements
        ldx #mycode[0]    ;lda instruction, $a9
        ldx #mydata[1]    ;2nd element, 4
        jmp mycode.local  ;address of local label

Addressing modes

Addressing modes are used for determining addressing modes of instructions.

There must be no white space between the comma and the register letter, otherwise the operator is not recognized. On the other hand put a space between the comma and a single letter symbol in a list to avoid it being recognized as an operator.

Addressing mode operators
#immediate
(indirect
[long indirect
,bdata bank indexed
,ddirect page indexed
,kprogram bank indexed
,rdata stack pointer indexed
,sstack pointer indexed
,xx register indexed
,yy register indexed
,zz register indexed

Parentheses are used for indirection and square brackets for long indirection. These operations are only available after instructions and functions to not interfere with their normal use in expressions.

Several addressing mode operators can be combined together. Currently the complexity is limited to 3 operators. This is enough to describe all addressing modes of the supported CPUs.

Valid addressing mode operator combinations
#immediatelda #$12
#addr,#addrmovemvp #5,#6
addrdirect or relativelda $12, lda $1234, bne $1234
addr,addrdirect page bitrmb 5,$12
addr,addr,addrdirect page bit relative jumpbbs 5,$12,$1234
(addr)indirectlda ($12), jmp ($1234)
(addr),yindirect y indexedlda ($12),y
(addr),zindirect z indexedlda ($12),z
(addr,x)x indexed indirectlda ($12,x), jmp ($1234,x)
[addr]long indirectlda [$12], jmp [$1234]
[addr],ylong indirect y indexedlda [$12],y
addr,bdata bank indexedlda 0,b
addr,b,xdata bank x indexedlda 0,b,x
addr,b,ydata bank y indexedlda 0,b,y
addr,ddirect page indexedlda 0,d
addr,d,xdirect page x indexedlda 0,d,x
addr,d,ydirect page y indexedldx 0,d,y
(addr,d)direct page indirectlda ($12,d)
(addr,d,x)direct page x indexed indirectlda ($12,d,x)
(addr,d),ydirect page indirect y indexedlda ($12,d),y
(addr,d),zdirect page indirect z indexedlda ($12,d),z
[addr,d]direct page long indirectlda [$12,d]
[addr,d],ydirect page long indirect y indexedlda [$12,d],y
addr,kprogram bank indexedjsr 0,k
(addr,k,x)program bank x indexed indirectjmp ($1234,k,x)
addr,rdata stack indexedlda 1,r
(addr,r),ydata stack indexed indirect y indexedlda ($12,r),y
addr,sstack indexedlda 1,s
(addr,s),ystack indexed indirect y indexedlda ($12,s),y
addr,xx indexedlda $12,x
addr,yy indexedlda $12,y

The direct page indexed addressing mode is not affected by the .dpage directive and always forces the 8 bit address as is. It's only usable for direct/zero page instructions.

The data bank indexed addressing mode is not affected by the .databank directive and always forces the 16 bit address as is. It's only usable with data bank accessing instructions.

The program bank indexed addressing mode is not affected by the current program bank and always generates the 16 bit constant value as is. It's only usable with jump instructions.

Normally addressing mode operators are used right after instructions. They can also be used for defining stack variable symbols when using a 65816, or to force a specific addressing modes.

param   = 1,s             ;define a stack variable
const   = #1              ;immediate constant
        lda 0,b           ;always "absolute" lda $0000
        lda param         ;results in lda $01,s
        lda param+1       ;results in lda $02,s
        lda (param),y     ;results in lda ($01,s),y
        ldx const         ;results in ldx #$01

Symbols

Symbols are used to reference objects. Regularly named, anonymous and local symbols are supported. These can be constant or re-definable.

Scopes are where symbols are stored and looked up. The global scope is always defined and it can contain any number of nested scopes.

Symbols must be uniquely named in a scope, therefore in big programs it's hard to come up with useful and easy to type names. That's why local and anonymous symbols exists. And grouping certain related symbols into a scope makes sense sometimes too.

Scopes are usually created by .proc and .block directives, but there are a few other ways. Symbols in a scope can be accessed by using the dot operator, which is applied between the name of the scope and the symbol (e.g. myconsts.math.pi).

Regular symbols

Regular symbol names are starting with a letters and containing letters, numbers and underscores. There's no restriction on the length of symbol names.

Care must be taken to not use the duplicate names in the same scope when the symbol is used as a constant.

Duplicate names in parent scopes are never a problem, they'll just be shadowed. This could be either good by reducing collisions and gives the ability to override defaults defined in lower scopes. On the other hand it's possible to mix-up the new symbol with a old one by mistake, which is hard to notice.

A regular symbol is looked up first in the current scope, then in lower scopes until the global scope is reached.

f       .block
g        .block
n        nop            ;jump here
	 .bend
	.bend

        jsr f.g.n       ;reference from a scope
f.x     = 3             ;create x in scope f with value 3

Local symbols

Local symbols have their own scope between two regularly named code symbols and are assigned to the code symbol above them.

Therefore they're easy to reuse without explicit scope declaration directives.

Not all regularly named symbols can be scope boundaries just plain code symbol ones without anything or an opcode after them (no macros!). Symbols defined as procedures, blocks, macros, functions, structs and unions are ignored. Also symbols defined by .var or = don't apply, and there are a few more exceptions, so stick to using plain code labels.

The name must start with an underscore (_), otherwise the same character restrictions apply as for regular symbols. There's no restriction on the length of the name.

Care must be taken to not use the duplicate names in the same scope when the symbol is used as a constant.

A local symbol is only looked up in it's own scope and nowhere else.

incr	inc ac
        bne _skip
        inc ac+1
_skip	rts

decr	lda ac
        bne _skip
        dec ac+1
_skip	dec ac          ;symbol reused here
        jmp incr._skip  ;this works too, but is not advised

Anonymous symbols

Anonymous symbols don't have a unique name and are always called as a single plus or minus sign. They are also called as forward (+) and backward () references.

When referencing them means the first backward, −− means the second backwards and so on. It's the same for forward, but with +. In expressions it may be necessary to put them into brackets.

        ldy #4
-       ldx #0
-       txa
        cmp #3
	bcc +
        adc #44
+       sta $400,x
	inx
	bne -
	dey
	bne --

Excessive nesting or long distance references create poorly readable code. It's also very easy to copy-paste a few lines of code with these references into a code fragment already containing similar references. The result is usually a long debugging session to find out what went wrong.

These references are also useful in segments, but this can create a nice trap when segments are copied into the code with their internal references.

	bne +
        #somemakro      ;let's hope that this segment does
+	nop             ;not contain forward references...

A anonymous symbols are looked up first in the current scope, then in lower scopes until the global scope is reached.

Constant and re-definable symbols

Constant symbols can be created with the equal sign. These are not re-definable. Forward referencing to them is allowed as they retain the objects over compilation passes.

Symbols in front of code or certain assembler directives are created as constant symbols too. They are binded to the object following them.

Re-definable symbols can be created by the .var directive. These are also called as variables as they don't carry their content over from the previous pass it's not possible to use them before definition.

border  = $d020         ;a constant
        inc border      ;inc $d020
variabl	.var 1          ;a variable
        .rept 10
        .byte variabl
variabl	.var variabl+1  ;increment it
	.next

Uninitialized memory

There's a special value for uninitialized memory, it's represented by a question mark. Whenever it's used to generate data it creates a hole where the previous content of memory is visible.

Uninitialized memory holes without previous content are not saved unless it's really necessary for the output format, in that case it's replaced with zeros.

It's not just data generation statements (e.g. .byte) that can create uninitialized memory, but filling, alignment or address manipulation as well.

        *= $200         ;bytes as necessary
	.word ?         ;2 bytes
	.fill 10        ;10 bytes
	.align 64       ;bytes as necessary
	.offs 16        ;16 bytes

Conditional expressions

Boolean conditional operators give false (0) or true (1) or one of the operands as the result. True is defined as a non-zero number, anything else is false.

The ternary operator (?:) gives the first (x) result if c is true or the second (y) if c is false.

Logical and conditional operators
x || yif x is true then x otherwise y
x ^^ y if both false or true then false otherwise x || y
x && yif x is true then y otherwise x
!xif x is true then false otherwise true
!!xif x is true then true otherwise false
c ? x : yif c is true then x otherwise y
;Silly example for 1=>"simple", 2=>"advanced", else "normal"
        .text MODE == 1 && "simple" || MODE == 2 && "advanced" || "normal"
        .text MODE == 1 ? "simple" : MODE == 2 ? "advanced" : "normal"

Please note that these are not short circuiting operations and both sides are calculated even if thrown away later.

Expressions

Parenthesis (( )) can be used to override operator precedence. Don't forget that they also denote indirect addressing mode for certain opcodes.

        lda #(4+2)*3

Built-in functions are identifiers followed by parentheses. They accept variable number of parameters separated by comma. For math functions see the floating point constants section.

General built-in functions
abs(a)absolute value
all(a)all elements true or no elements
any(a)any elements true
bool(a)truth value
float(a)convert to floating point
format(s, ∗)convert to string with formatting
int(a)convert to integer
len(a)number of elements
range(s, e, t)create a list with values from a range
repr(a)representation to string
sign(a)sign value (−1, 0, 1)
size(a)size in bytes
str(a)convert to string

Special addressing mode forcing operators in front of an expression can be used to make sure the expected addressing mode is used.

Address size forcing
@bto force 8 bit address
@wto force 16 bit address
@lto force 24 bit address (65816)
        lda @w$0000

Compiler directives:

Controlling the compile offset and program counter

Two counters are used while assembling. The compile offset is where the data and code ends up in memory, while the program counter is what labels will be set or what the special star label gets when referenced.

∗= <expression>
The compile offset is moved so that the program counter will match the one in the expression.
.offs <expression>
Add an offset to the compile offset (create a gap). The program counter stays the same as before.
.logical <expression>
.here
Changes the program counter, the compile offset is not changed. Can be nested of course.
.align <modulo>[, <fill>]
Align code to a dividable program counter address by inserting uninitialized memory or repeated bytes.
        *= $1000        ;set program counter (and offset)

        .offs 100       ;gap of 100, PC still the same

        .logical $300   ;set PC to $300
drive   lda #$80
        sta $00
        jmp drive       ;it's jmp $300
	rts
	.here

        .align $100
irq     inc $d019       ;this will be on a page boundary, after skipping bytes
        .align 4, $ea
loop    adc #1          ;padding with "nop" for DTV burst mode

Here's an example how .logical and ∗= works together:

        *= $0800       ;Compile: $0800, PC: $0800
        .logical $1000 ;Compile: $0800, PC: $1000
        *= $1200       ;Compile: $0a00, PC: $1200
	.here          ;Compile: $0a00, PC: $0a00

Dumping data

Storing numeric data

Multi byte numeric data is stored in the little-endian order, which is the natural byte order for 65xx processors. Numeric ranges are enforced depending on the directives used.

When using lists or tuples their values will be used one by one. Uninitialized data creates holes of different sizes. Small string constants are converted using the current encoding.

.byte <expression>[, <expression>, …]
Create bytes from 8 bit unsigned constants (0–255)
.char <expression>[, <expression>, …]
Create bytes from 8 bit signed constants (−128–127)
        .byte 255	; $ff
        .byte ?	        ; reserve 1 byte of space

;Compact computed jumps using self modifying code
        lda jumps,x
        sta smod+1
smod    bne *

jumps   .char (routine1, routine2)-smod-2 ;Routines nearby (−128–127 bytes)
.word <expression>[, <expression>, …]
Create bytes from 16 bit unsigned constants (0–65535)
.int <expression>[, <expression>, …]
Create bytes from 16 bit signed constants (−32768–32767)
        .word $2342, $4555
        .word ?	        ; reserve 2 bytes of space
        .int -533, 4433

;Computed jumps with jump table
        lda jumps,x
        sta ind
        lda jumps+1,x
        sta ind+1
        jmp (ind)

;Computed jumps with jump table (65C02)
        jmp (jumps,x)

jumps   .word routine1, routine2
;On 65816 subtract the current program bank
jumps2  .word (routine1, routine2) - (* & ~$ffff)
.rta <expression>[, <expression>, …]
Create return address constants, which are 16 bit unsigned constants (usually addresses) but with one subtracted.
;Computed jumps by using stack
	asl
	tax
        lda rets+1,x
	pha
        lda rets,x
	pha
	rts
rets	.rta $fce2, routine1, routine2
;On 65816 subtract the current program bank
rets2	.rta ($fce2, routine1, routine2) - (* & ~$ffff)
.long <expression>[, <expression>, …]
Create bytes from 24 bit unsigned constants (0–16777215)
.lint <expression>[, <expression>, …]
Create bytes from 24 bit signed constants (−8388608–8388607)
        .long $123456
        .long ?	        ; reserve 3 bytes of space
        .lint -533, 4433

;Computed long jumps with jump table (65816)
        lda jumps,x
        sta ind
        lda jumps+1,x
        sta ind+1
        lda jumps+2,x
        sta ind+2
        jmp [ind]
jumps   .long routine1, routine2
;Store 8.16 signed fixed point constants
        .lint (-3.44, 3.4, 3.52) * (1 << 16)
.dword <expression>[, <expression>, …]
Create bytes from 32 bit constants (0–4294967295)
.dint <expression>[, <expression>, …]
Create bytes from 32 bit signed constants (−2147483648–2147483647)
        .dword $12345678
        .dword ?        ; reserve 4 bytes of space
;Store 8.24 signed fixed point constants
        .dint (-3.44, 3.4, 3.52) * (1 << 24)

Storing text data

Texts are stored as a string of bytes. Small numeric constants can be mixed in to represent control characters.

.text <expression>[, <expression>, …]
Assemble strings and small constants into bytes:
        .text "oeU"	; text, "" means $22
        .text 'oeU'	; text, '' means $27
        .text 23, $33	; bytes
        .text %00011111	; more bytes
        .text ^OEU	; the decimal value as string (^23 is $32,$33)
.fill <length>[, <fill>]
Skip bytes (using uninitialized data), or fill with repeated bytes. For complex multi byte patterns use .rept!
        .fill $100      ;no fill, just reserve $100 bytes
        .fill $4000, 0  ;16384 bytes of 0
        .fill 8000, [$55, $aa];8000 bytes of alternating $55, $aa
.shift <expression>[, <expression>, …]
Same as .text, but the last byte will have the highest bit set. Any character which already has the most significant bit set will cause an error.
        ldx #0
loop	lda txt,x
	php
        and #$7f
        jsr $ffd2
	inx
	plp
        bpl loop
	rts
txt	.shift "some text"
.shiftl <expression>[, <expression>, …]
Same as .text, but all bytes are shifted to left, and the last character gets the lowest bit set. Any character which already has the most significant bit set will cause an error as this would be cut off.
        ldx #0
loop	lda txt,x
	lsr
        sta $400,x      ;screen memory
	inx
        bcc loop
	rts
	.enc screen
txt	.shiftl "some text"
	.enc none
.null <expression>[, <expression>, …]
Same as .text, but adds a null at the end, null in string is an error.
txt	.text "lot of stuff"
        .null "to write"
        lda #<txt
        ldy #>txt
        jsr $ab1e
.ptext <expression>[, <expression>, …]
Same as .text, but prepend the number of bytes in front of the string (pascal style string). Longer than 255 bytes are not allowed.
        lda #<txt
        ldx #>txt
        jsr print
	rts

print	sta $fb
        stx $fc
        ldy #0
        lda ($fb),y
        beq null
	tax
-	iny
        lda ($fb),y
        jsr $ffd2
	dex
	bne -
null	rts

txt	.ptext "note"

Text encoding

64tass supports sources written in UTF-8, UTF-16 (be/le) and RAW 8 bit encoding. To take advantage of this capability custom encodings can be defined to map Unicode characters to 8 bit values in strings.

.enc <name>
Selects text encoding, predefined encodings are none and screen (screen code), anything else is user defined. All user encodings start without any character or escape definitions, add some as required.
	.enc screen	;screen code mode
        .text "text with screen codes"
        cmp #"u"	;compare screen code
	.enc none	;normal mode again
        cmp #"u"	;compare ASCII
.cdef <start>, <end>, <coded> [, <start>, <end>, <coded>, …]
.cdef "<start><end>", <coded> [, "<start><end>", <coded>, …]
Assigns characters in a range to single bytes.

This is a simple single character to byte translation definition. It is applied to a range as characters and bytes are usually assigned sequentially. The start and end positions are Unicode character codes either by numbers or by typing them. Overlapping ranges are not allowed.

.edef "<escapetext>", <value> [, "<escapetext>", <value>, …]
Assigns strings to byte sequences as a translated value.

When these substrings are found in a text they are replaced by bytes defined here. When strings with common prefixes are used the longest match wins. Useful for defining non-typeable control code aliases, or as a simple tokenizer.

        .enc petscii	;define an ascii->petscii encoding
        .cdef " @", 32  ;characters
        .cdef "AZ", $c1
        .cdef "az", $41
        .cdef "[[", $5b
        .cdef "££", $5c
        .cdef "]]", $5d
        .cdef "ππ", $5e
        .cdef $2190, $2190, $1f;left arrow

        .edef "\n", 13  ;one byte control codes
        .edef "{clr}", 147
        .edef "{crlf}", [13, 10];two byte control code
        .edef "<nothing>", [];replace with no bytes

        .text "{clr}Text in PETSCII\n"

Structured data

Structures and unions can be defined to create complex data types. The offset of fields are available by using the definition's name. The fields themselves by using the instance name.

The initialization method is very similar to macro parameters, the difference is that unset parameters always return uninitialized data (?) instead of an error.

Structure

Structures are for organizing sequential data, so the length of a structure is the sum of lengths of all items.

.struct [<name>][=<default>]][, [<name>][=<default>] …]
.ends [<result>][, <result> …]
Structure definition, with named parameters and default values
.dstruct <name>[, <initialization values>]
.<name> [<initialization values>]
Create instance of structure with initialization values
        .struct         ;anonymous structure
x       .byte 0         ;labels are visible
y       .byte 0         ;content compiled here
        .ends           ;useful inside unions

nn_s    .struct col, row;named structure
x       .byte \col      ;labels are not visible
y       .byte \row      ;no content is compiled here
        .ends           ;it's just a definition

nn      .dstruct nn_s, 1, 2;structure instance, content here

        lda nn.x        ;direct field access
        ldy #nn_s.x     ;get offset of field
        lda nn,y        ;and use it indirectly

Union

Unions can be used for overlapping data as the compile offset and program counter remains the same on each line. Therefore the length of a union is the length of it's longest item.

.union [<name>][=<default>]][, [<name>][=<default>] …]
.endu
Union definition, with named parameters and default values
.dunion <name>[, <initialization values>]
.<name> [<initialization values>]
Create instance of union with initialization values
        .union          ;anonymous union
x       .byte 0         ;labels are visible
y       .word 0         ;content compiled here
	.endu

nn_u    .union          ;named union
x       .byte ?         ;labels are not visible
y       .word \1        ;no content is compiled here
        .endu           ;it's just a definition

nn      .dunion nn_u, 1 ;union instance here

        lda nn.x        ;direct field access
        ldy #nn_u.x     ;get offset of field
        lda nn,y        ;and use it indirectly

Combined use of structures and unions

The example below shows how to define structure to a binary include.

        .union
        .binary "pic.drp", 2
        .struct
color	.fill 1024
screen	.fill 1024
bitmap	.fill 8000
backg	.byte ?
        .ends
	.endu

Anonymous structures and unions in combination with sections are useful for overlapping memory assignment. The example below shares zeropage allocations for two separate parts of a bigger program. The common subroutine variables are assigned after in the zp section.

        *= $02
        .union          ;spare some memory
         .struct
          .dsection zp1 ;declare zp1 section
         .ends
         .struct
          .dsection zp2 ;declare zp2 section
         .ends
        .endu
        .dsection zp    ;declare zp section

Macros

Macros can be used to reduce typing of frequently used source lines. Each invocation is a copy of the macro's content with parameter references replaced by the parameter texts.

.segment [<name>][=<default>]][, [<name>][=<default>] …]
.endm [<result>][, <result> …]
Copies the code segment as it is, so symbols can be used from outside, but this also means multiple use will result in double defines unless anonymous labels are used.
.macro [<name>][=<default>]][, [<name>][=<default>] …]
.endm [<result>][, <result> …]
The code is enclosed in it's own block so symbols inside are non-accessible, unless a label is prefixed at the place of use, then local labels can be accessed through that label.
#<name> [<param>][[,][<param>] …]
.<name> [<param>][[,][<param>] …]
Invoke the macro after # or . with the parameters. Normally the name of the macro is used, but it can be any expression.
;A simple macro
copy    .macro
        ldx #size(\1)
lp      lda \1,x
        sta \2,x
	dex
        bpl lp
	.endm

        #copy label, $500

;Use macro as an assembler directive
lohi    .macro
lo	.byte <(\@)
hi	.byte >(\@)
        .endm

var     .lohi 1234, 5678

        lda var.lo,y
        ldx var.hi,y

Parameter references

The first 9 parameters can be referenced by \1\9. The entire parameter list including separators is \@.

name	.macro
        lda #\1 	;first parameter 23+1
	.endm

        #name 23+1	;call macro

Parameters can be named, and it's possible to set a default value after an equal sign which is used as a replacement when the parameter is missing.

These named parameters can be referenced by \name or \{name}. Names must match completely, if unsure use the quoted name reference syntax.

name	.macro first, b=2, , last
        lda #\first	;first parameter
        lda #\b 	;second parameter
        lda #\3 	;third parameter
        lda #\last 	;fourth parameter
	.endm

        #name 1, , 3, 4	;call macro

Text references

In the original turbo assembler normal references are passed by value and can only appear in place of one. Text references on the other hand can appear everywhere and will work in place of e.g. quoted text or opcodes and labels. The first 9 parameters can be referenced as text by @1@9.

name    .macro
        jsr print
        .null "Hello @1!";first parameter
	.endm

        #name "wth?"	;call macro

Custom functions

Beyond the built-in functions mentioned earlier it's possible to define custom ones for frequently used calculations.

.function <name>[=<default>]][, <name>[=<default>] …][, ∗<name>]
.endf [<result>][, <result> …]
Defines a user function
#<name> [<param>][[,][<param>] …]
.<name> [<param>][[,][<param>] …]
<name> [<param>][[,][<param>] …]
Invoke a function like a macro, directive or pseudo instruction.

Parameters are assigned to constant symbols in the function scope on invocation. The default values are calculated at function definition time only, and these values are used at invocation time when a parameter is missing.

Extra parameters are not accepted, unless the last parameter symbol is preceded with a star, in this case these parameters are collected into a tuple. Multiple values are returned are also returned as tuple.

Functions can span multiple lines but unlike macros they can't create new code. Only those external variables and functions are available which were accessible at the place of definition, but not those at the place of invocation.

wpack   .function a, b=0
        .endf a+b*256

        .word wpack(1), wpack(2, 3)

If a function is used as macro, directive or pseudo instruction and there's a label in front then the returned value is assigned to it. If nothing is returned then it's used as regular label. Of course when used like this it can create code and access local variables.

mva     .function s, d
        lda s
        sta d
        .endf

        mva #1, label

Conditional assembly

To prevent parts of source from compiling conditional constructs can be used. This is useful when multiple slightly different versions needs to be compiled from the same source.

If, else if, else

.if <expression>
Compile, if result is true (not zero)
.elsif <expression>
Compile if the previous conditions were all skipped and the result is true (not zero)
.else
Compile if the previous conditions were all skipped
.fi
.endif
End of conditional compile
.ifne <value>
Compile, if value is not zero (or true)
.ifeq <value>
Compile, if value is zero (or false)
.ifpl <value>
Compile, if value is greater or equal zero
.ifmi <value>
Compile, if value is less than zero

The .ifne, .ifeq, .ifpl and .ifmi directives exists for compatibility only, in practice it's better to use comparison operators instead.

        .if wait==2	;2 cycles
	nop
        .elsif wait==3	;3 cycles
        bit $ea
        .elsif wait==4	;4 cycles
        bit $eaea
        .else		;else 5 cycles
        inc $2
	.fi

Switch, case, default

Similar to the .if/.elsif/.else construct, but the compared value needs to be written only once in the switch statement.

.switch <expression>
Evaluate expression and remember it
.case <expression>[, <expression> …]
Compile if the previous conditions were all skipped and one of the values equals
.default
Compile if the previous conditions were all skipped
.endswitch
End of conditional compile
        .switch wait
        .case 2	        ;2 cycles
	nop
        .case 3	        ;3 cycles
        bit $ea
        .case 4	        ;4 cycles
        bit $eaea
        .default	;else 5 cycles
        inc $2
	.endswitch

Repetitions

.for <variable>=<expression>, <expression>, <variable>=<expression>
.next
Compile loop, only anonymous references are allowed as labels inside
        ldx #0
        lda #32
lp      .for ue = 0, ue < $400, ue = ue + $100
        sta ue,x
	.next
        dex
        bne lp
.rept <expression>
.next
Repeated compile, only anonymous references are allowed as labels inside
        .rept 100
	nop
	.next
.break
Exit current loop immediately
.continue
Continue current loop's next iteration
.lbl
Creates a special jump label that can be referenced by .goto
.goto <labelname>
Causes assembler to continue assembling from the jump label. No forward references of course, handle with care. Typically used in classic TASM sources for creating loops.
i	.var 100
loop	.lbl
        nop
i       .var i - 1
        .ifne i
        .goto loop       ;generates 100 nops
        .fi

Including files

Longer sources are usually separated into multiple files for easier handling. Precomputed binary data can also be included directly without converting it into source code first.

Search path is relative to the location of current source file. If it's not found there the include search path is consulted for further possible locations.

To make your sources portable please always use forward slashes (/) as a directory separator and use lower/uppercase consistently in filenames!

.include <filename>
Include source file here.
.binclude <filename>
Include source file here in it's local block. If the directive is prefixed with a label then all labels are local and are accessible through that label only, otherwise not reachable at all.

	.include "macros.asm"       ;include macros
menu    .binclude "menu.asm"        ;include in a block
	jmp menu.start
.binary <filename>[, <offset>[, <length>]]
Include raw binary data from file. By using offset and length it's possible to break out chunks of data from a file separately, like bitmap and colors for example.
        .binary "stuffz.bin"        ;simple include, all bytes
        .binary "stuffz.bin", 2     ;skip start address
        .binary "stuffz.bin", 2, 1000;skip start address, 1000 bytes max

        *= $1000	            ;load music to $1000 and
        .binary "music.sid", $7e    ;strip SID header

Scopes

Scopes may contain symbols or other scopes nested. They are useful to avoid symbol clashes as the same symbol name can repeated as long as it's in a different scope.

In nested scopes the symbol lookup starts from the local scope and goes in the direction of the global scope. This means that local variables will shadow global one with the same name.

.proc
.pend
Procedure start and end of procedure.

If it's label is not used then the code won't be compiled at all. This is very useful to avoid a lot of .if blocks to exclude unused sections of code.

All labels inside are local enclosed in a scope and are accessible through the prefixed label. Useful for building libraries.

ize     .proc
	nop
cucc    nop
	.pend

        jsr ize
        jmp ize.cucc
.block
.bend
Block start and block end.

All labels inside a block are local enclosed in a scope. If prefixed with a label local variables are accessible through that label using the dot notation, otherwise not at all.

	.block
        inc count + 1
count	ldx #0
	.bend
.weak
.endweak
Weak symbol area

Any symbols defined inside can be overriden by stronger symbols in the same scope from outside. Can be nested as necessary.

This gives the possibility of giving default values for symbols which might not always exist without resorting to .ifdef/.ifndef or similar directives in other assemblers.

symbol	= 1            ;stronger symbol than the one below
	.weak
symbol	= 0            ;default value if the one above does not exists
	.endweak
        .if symbol     ;almost like an .ifdef ;)

Other use of weak symbols might be in included libraries to change default values or replace stub functions and data structures.

If these stubs are defined using .proc/.pend then their default implementations will not even exists in the output at all when a stronger symbol overrides them.

Multiple definition of a symbol with the same strength in the same scope is of course not allowed and it results in double definition error.

Please note that .ifdef/.ifndef directives are left out from 64tass for of technical reasons, so don't wait for them to appear anytime soon.

Sections

Sections can be used to collect data or code into separate memory areas without moving source code lines around. This is achieved by having separate compile offset and program counters for each defined section.

.section <name>
.send [<name>]
Defines a section fragment. The name at .send must match but it's optional.
.dsection <name>
Collect the section fragments here.

All .section fragments are compiled to the memory area allocated by the .dsection directive. Compilation happens as the code appears, this directive only assigns enough space to hold all the content in the section fragments.

The space used by section fragments is calculated from the difference of starting compile offset and the maximum compile offset reached. It is possible to manipulate the compile offset in fragments, but putting code before the start of .dsection is not allowed.

        *= $02
        .dsection zp   ;declare zeropage section
        .cerror * > $30, "Too many zeropage variables"

        *= $334
        .dsection bss   ;declare uninitialized variable section
        .cerror * > $400, "Too many variables"

        *= $0801
        .dsection code   ;declare code section
        .cerror * > $1000, "Program too long!"

        *= $1000
        .dsection data   ;declare data section
        .cerror * > $2000, "Data too long!"
;−−−−−−−−−−−−−−−−−−−−
        .section code
        .word ss, 2005
        .null $9e, ^start
ss	.word 0

start   sei
        .section zp     ;declare some new zeropage variables
p2	.word ?         ;a pointer
        .send zp
        .section bss    ;new variables
buffer	.fill 10        ;temporary area
        .send bss

        lda (p2),y
        lda #<label
        ldy #>label
        jsr print

        .section data   ;some data
label   .null "message"
        .send data

        jmp error
        .section zp     ;declare some more zeropage variables
p3	.word ?         ;a pointer
        .send zp
        .send code

The compiled code will look like:

>0801	 0b 08 d5 07			        .word ss, 2005   
>0805	 9e 32 30 36 31 00		        .null $9e, ^start
>080b	 00 00				ss      .word 0          

.080d	 78				start   sei

>0002					p2      .word ?         ;a pointer
>0334					buffer  .fill 10        ;temporary area

.080e	 b1 02				        lda (p2),y
.0810	 a9 00				        lda #<label
.0812	 a0 10				        ldy #>label
.0814	 20 1e ab			        jsr print

>1000	 6d 65 73 73 61 67 65 00	label   .null "message"

.0817	 4c e2 fc			        jmp error

>0004					p2      .word ?         ;a pointer

Sections can form a hierarchy by nesting a .dsection into another section. The section names must only be unique within a section but can be reused otherwise. Parent section names are visible for children, siblings can be reached through parents.

In the following example the included sources don't have to know which code and data sections they use, while the bss section is shared for all banks.

;First 8K bank at the beginning, PC at $8000
        *= $0000
        .logical $8000
        .dsection bank1
        .cerror * > $a000, "Bank1 too long"
	.here

bank1	.block          ;Make all symbols local
        .section bank1
        .dsection code  ;Code and data sections in bank1
        .dsection data
        .section code   ;Pre-open code section
	.include "code.asm"; see below
	.include "iter.asm"
        .send code
        .send bank1
	.bend

;Second 8K bank at $2000, PC at $8000
        *= $2000
        .logical $8000
        .dsection bank2
        .cerror * > $a000, "Bank2 too long"
	.here

bank2	.block          ;Make all symbols local
        .section bank2
        .dsection code  ;Code and data sections in bank2
        .dsection data
        .section code   ;Pre-open code section
	.include "scr.asm"
        .send code
        .send bank2
	.bend

;Common data, avoid initialized variables here!
        *= $c000
        .dsection bss
        .cerror * > $d000, "Too much common data"
;−−−−−−−−−−−−− The following is in "code.asm"
code    sei

        .section bss   ;Common data section
buffer	.fill 10
        .send bss

        .section data  ;Data section (in bank1)
routine .word print
        .send bss

65816 related

.as
.al
Select short (8 bit) or long (16 bit) accumulator immediate constants.
	.al
        lda #$4322
.xs
.xl
Select short (8 bit) or long (16 bit) index register immediate constants.
	.xl
        ldx #$1000
.databank <expression>
Set data bank (65816). Absolute addressing is used only for symbols in this bank, anything else (except direct page) is using long addressing.
        .databank $10   ;$10xxxx
.dpage <expression>
Set direct page. Direct or zero page addressing is only used for addresses in the following 256 byte range, anything else is using absolute or long addressing.
        .dpage $400

Controlling errors

.page
.endp
Gives an error on page boundary crossing, e.g. for timing sensitive code.
	.page
table   .byte 0, 1, 2, 3, 4, 5, 6, 7
	.endp
.option allow_branch_across_page
Switches error generation on page boundary crossing during relative branch. Such a condition on 6502 adds 1 extra cycle to the execution time, which can ruin the timing of a carefully cycle counted code.
        .option allow_branch_across_page = 0
        ldx #3          ;now this will execute in
-       dex             ;16 cycles for sure
	bne -
        .option allow_branch_across_page = 1
.error <message> [, <message>, …]
.cerror <condition>, <message> [, <message>, …]
Exit with error or conditionally exit with error
        .error "Unfinished here..."
        .cerror * > $1200, "Program too long by ", * - $1200, " bytes"
.warn <message> [, <message>, …]
.cwarn <condition>, <message> [, <message>, …]
Display a warning message always or depending on a condition
        .warn "FIXME: handle negative values too!"
        .cwarn * > $1200, "This may not work!"

Target

.cpu <cpuname>
Selects cpu
	.cpu 6502	;standard 65xx
	.cpu 65c02	;CMOS 65C02
	.cpu 65ce02	;CSG 65CE02
	.cpu 6502i	;NMOS 65xx
	.cpu 65816	;W65C816
	.cpu 65dtv02	;65dtv02
	.cpu 65el02	;65el02
	.cpu r65c02	;R65C02
	.cpu w65c02	;W65C02
	.cpu default	;cpu set on commandline

Misc

.end
Terminate assembly. Any content after this directive is ignored.
.eor <expression>
XOR output with a 8 bit value. Useful for reverse screen code text for example, or for silly encryption.
.var <expression>
Defines a variable identified by the label preceding, which is set to the value of expression or reference of variable.
.comment
.endc
Comment block start and comment block end.
	.comment
        lda #1          ;this won't be compiled
	sta $d020
	.endc
.assert
.check
Do not use these, the syntax will change in next version!

Printer control

.pron
.proff
Turn on or off source listing on part of the file.
        .proff           ;Don't put filler bytes into listing
        *= $8000
        .fill $2000, $ff ;Pre-fill ROM area
	.pron
        *= $8000
        .word reset, restore
        .text "CBM80"
reset   cld
.hidemac
.showmac
Ignored for compatibility

Pseudo instructions

For writing short code there are some special pseudo instructions for always taken branches. These are automatically compiled as relative branches when the jump distance is short enough and as JMP or BRL when longer. The names are derived from conditional branches and are: GEQ, GNE, GCC, GCS, GPL, GMI, GVC, and GVS.

There's one more called GRA for CPUs supporting BRA, which is expanded to BRL (if available) or JMP.

.0000    a9 03          lda #$03        in1     lda #3
.0002    d0 02          gne $0006               gne at          ;branch always
.0004    a9 02          lda #$02        in2     lda #2
.0006    4c 00 10       gne $1000       at      gne $1000       ;branch further

If the branch would skip only one byte then the opposite condition is compiled and only the first byte is emitted. This is now a never executed jump, and the relative distance byte after the opcode is the jumped over byte.

If the branch would not skip anything at all then no code is generated.

.0009                   geq $0009               geq in3         ;zero length "branch"
.0009    18             clc             in3     clc
.000a    b0             gcc $000c               gcc at2         ;one byte skip, as bcs
.000b    38             sec             in4     sec             ;sec is skipped!
.000c    20 0f 00       jsr $000f       at2     jsr func
.000f                                   func

Please note that expressions like Gxx ∗+2 or Gxx ∗+3 are not allowed as the compiler can't figure out if it has to create no code at all, the 1 byte variant or the 2 byte one. Therefore use normal or anonymous labels defined after the jump instruction when jumping forward!

To avoid branch too long errors the assembler also supports long branches, it can automatically convert conditional relative branches to it's opposite and a JMP or BRL. This can be enabled on the command line using the --long-branch option.

.0000    ea             nop                     nop
.0001    b0 03 4c 00 10 bcc $1000               bcc $1000      ;long branch
.0006    ea             nop                     nop

Please note that forward jump expressions like Bxx ∗+130, Bxx ∗+131 and Bxx ∗+132 are not allowed as the compiler can't decide between a short/long branch. Of course these destinations can be used, but only with normal or anonymous labels defined after the jump instruction.


Original turbo assembler compatibility

How to convert source code for use with 64tass

Currently there are two options, either use TMPview by Style to convert the sourcefile directly, or do the following:

The resulting file should then (with the restrictions below) assemble using the following command line:

64tass -C -T -a -W -i source.asm -o outfile.prg

Differences to the original turbo ass macro on the C64

64tass is nearly 100% compatible with the original Turbo Assembler, and supports most of the features of the original Turbo Assembler Macro. The remaining notable differences are listed here.

Labels

The original turbo assembler uses case sensitive labels, use the -C, --case-sensitive option to enable this behaviour.

Expression evaluation

There are a few differences which can be worked around by the -T, --tasm-compatible option. These are:

The original expression parser has no operator precedence, but 64tass has. That means that you will have to fix expressions using braces accordingly, for example 1+2∗3 becomes (1+2)∗3.

The following operators used by the original Turbo Assembler are different:

TASM Operator differences
.bitwise or, now |
:bitwise eor, now ^
!force 16 bit address, now @w

The default expression evaluation is not limited to 16 bit unsigned numbers anymore.

Macros

Macro parameters are referenced by \1\9 instead of using the pound sign.

Parameters are always copied as text into the macro and not passed by value as the original turbo assembler does, which sometimes may lead to unexpected behaviour. You may need to make use of braces around arguments and/or references to fix this.

Bugs

Some versions of the original turbo assembler had bugs that are not reproduced by 64tass, you will have to fix the code instead.

In some versions labels used in the first .block are globally available. If you get a related error move the respective label out of the .block


Command line options

Output options

-o <filename>
Place output into <filename>. The default output filename is a.out. This option changes it.
64tass a.asm -o a.prg
no option
Outputs CBM format binaries

The first 2 bytes are the little endian address of the first valid byte (start address). Overlapping blocks are flattened and uninitialized memory is filled up with zeros. Uninitialized memory before the first and after the last valid bytes are not saved.

Used for C64 binaries.

-b, --nostart
Output data only without start address

Overlapping blocks are flattened and uninitialized memory is filled up with zeros. Uninitialized memory before the first and after the last valid bytes are not saved.

Useful for small ROM files.

-f, --flat
Flat address space output mode.

Overlapping blocks are flattened and uninitialized memory is filled up with zeros. Uninitialized memory after the last valid byte is not saved.

Useful for creating huge multi bank ROM files (over 64K). See sections for an example.

-n, --nonlinear
Generate nonlinear output file.

Overlapping blocks are flattened. Blocks are saved in sorted order and uninitialized memory is skipped.

Used for linkers.

64tass --nonlinear a.asm
        *= $1000
        lda #2
        *= $2000
	nop
Result of compilation
$02, $00little endian length, 2 bytes
$00, $10little endian start $1000
$a9, $02code
$01, $00little endian length, 1 byte
$00, $20little endian start $2000
$eacode
$00, $00end marker (length=0)
-X, --long-address
Use 3 byte address/length for CBM and nonlinear output instead of 2 bytes.
64tass --long-address --m65816 a.asm
--atari-xex
Generate a Atari XEX output file.

Overlapping blocks are kept, continuing blocks are concatenated. Saving happens in the definition order without sorting, and uninitialized memory is skipped in the output.

64tass --atari-xex a.asm
        *= $02e0
        .word start      ;run address
        *= $2000
start	rts
Result of compilation
$ff, $ffheader, 2 bytes
$e0, $02little endian start $02e0
$e1, $02little endian last byte $02e1
$00, $20start address word
$00, $20little endian start $2000
$00, $20little endian last byte $2000
$60code

Operation options

-a, --ascii
Use ASCII/Unicode text encoding instead of raw 8-bit

Normally no conversion takes place, this is for backwards compatibility with a DOS based Turbo Assembler editor, which could create PETSCII files for 6502tass. (including control characters of course)

Using this option will change the default none and screen encodings to map 'a'–'z' and 'A'–'Z' into the correct PETSCII range of $41–$5A and $C1–$DA, which is more suitable for an ASCII editor. It also adds predefined petcat style PETSCII literals to the default encodings.

For writing sources in UTF-8/UTF-16 encodings this option is required! The symbol names are still limited to ASCII, but custom string encodings can take advantage of the full Unicode set.

64tass a.asm

.0000    a9 61          lda #$61        lda #"a"

>0002    31 61 41                       .text "1aA"
>0005    7b 63 6c 65 61 72 7d 74        .text "{clear}text{return}more"
>000e    65 78 74 7b 72 65 74 75
>0016    72 6e 7d 6d 6f 72 65

64tass --ascii a.asm

.0000    a9 41          lda #$41        lda #"a"
>0002    31 41 c1                       .text "1aA"
>0005    93 54 45 58 54 0d 4d 4f        .text "{clear}text{return}more"
>000e    52 45
-B, --long-branch
Automatic BXX ∗+5 JMP xxx. Branch too long messages can be annoying sometimes, usually they'll need to be rewritten to BXX ∗+5 JMP xxx. 64tass can do this automatically if this option is used. But BRA is not converted.
64tass a.asm
        *= $1000
        bcc $1233	;error...

64tass a.asm
        *= $1000
        bcs *+5		;opposite condition
        jmp $1233	;as simple workaround

64tass --long-branch a.asm
        *= $1000
        bcc $1233	;no error, automatically converted to the above one.
-C, --case-sensitive
Case sensitive labels. Labels are non case sensitive by default, this option changes that.
64tass a.asm
label	nop
Label	nop	;double defined...

64tass --case-sensitive a.asm
label   nop
Label	nop	;Ok, it's a different label...
-D <label>=<value>
Define <label> to <value>. Defines a label to a value. Same syntax is allowed as in source files. Be careful with string quoting, the shell might eat some of the characters.
64tass -D ii=2 a.asm
        lda #ii ;result: $a9, $02
-w, --no-warn
Suppress warnings. Disables warnings during compile.
64tass --no-warn a.asm
-q, --quiet
Suppress messages. Disables header and summary messages.
64tass --quiet a.asm
-T, --tasm-compatible
Enable TASM compatible operators and precedence

Switches the expression evaluator into compatibility mode. This enables ., : and ! operators and disables 64tass specific extensions, disables precedence handling and forces 16 bit unsigned evaluation (see differences to original Turbo Assembler below)

-I <path>
Specify include search path

If an included source or binary file can't be found in the directory of the source file then this path is tried. More than one directories can be specified by repeating this option. If multiple matches exist the first one is used.

Target selection on command line

These options will select the default architecture. It can be overridden by using the .cpu directive in the source.

--m65xx
Standard 65xx (default). For writing compatible code, no extra codes. This is the default.
64tass --m65xx a.asm
        lda $14		;regular instructions
-c, --m65c02
CMOS 65C02. Enables extra opcodes and addressing modes specific to this CPU.
64tass --m65c02 a.asm
        stz $d020	;65c02 instruction
-c, --m65ce02
CSG 65CE02. Enables extra opcodes and addressing modes specific to this CPU.
64tass --m65ce02 a.asm
        inz
-i, --m6502
NMOS 65xx. Enables extra illegal opcodes. Useful for demo coding for C64, disk drive code, etc.
64tass --m6502 a.asm
        lax $14		;illegal instruction
-t, --m65dtv02
65DTV02. Enables extra opcodes specific to DTV.
64tass --m65dtv02 a.asm
        sac #$00
-x, --m65816
W65C816. Enables extra opcodes, and full 16 MiB address space. Useful for SuperCPU projects. Don't forget to use --word-start for small ones ;)
64tass --m65816 a.asm
        lda $123456,x
-e, --m65el02
65EL02. Enables extra opcodes, useful RedPower CPU projects. Probably you'll need --nostart as well.
64tass --m65el02 a.asm
        lda 0,r
--mr65c02
R65C02. Enables extra opcodes and addressing modes specific to this CPU.
64tass --mr65c02 a.asm
        rmb 7,$20
--mw65c02
W65C02. Enables extra opcodes and addressing modes specific to this CPU.
64tass --mw65c02 a.asm
        wai

Source listing options

-l <file>
List labels into <file>. List global used labels to a file.
64tass -l labels.txt a.asm
        *= $1000
label   jmp label

result (labels.txt):
label           = $1000
-L <file>
List into <file>. Dumps source code and compiled code into file. Useful for debugging, it's much easier to identify the code in memory within the source files.
64tass -L list.txt a.asm
        *= $1000
        ldx #0
loop	dex
        bne loop
	rts

result (list.txt):

;64tass Turbo Assembler Macro V1.5x listing file of "a.asm"
;done on Fri Dec  9 19:08:55 2005


.1000	 a2 00		ldx #$00		ldx #0
.1002	 ca		dex		loop	dex
.1003	 d0 fd		bne $1002		bne loop
.1005	 60		rts			rts

;∗∗∗∗∗∗  end of code
-m, --no-monitor
Don't put monitor code into listing. There won't be any monitor listing in the list file.
64tass --no-monitor -L list.txt a.asm

result (list.txt):

;64tass Turbo Assembler Macro V1.5x listing file of "a.asm"
;done on Fri Dec  9 19:11:43 2005


.1000	 a2 00					ldx #0
.1002	 ca				loop	dex
.1003	 d0 fd					bne loop
.1005	 60					rts

;∗∗∗∗∗∗  end of code
-s, --no-source
Don't put source code into listing. There won't be any source listing in the list file.
64tass --no-source -L list.txt a.asm

result (list.txt):

;64tass Turbo Assembler Macro V1.5x listing file of "a.asm"
;done on Fri Dec  9 19:13:25 2005


.1000	 a2 00		ldx #$00
.1002	 ca		dex
.1003	 d0 fd		bne $1002
.1005	 60		rts

;∗∗∗∗∗∗  end of code

Other options

-?, --help
Give this help list. Prints help about command line options.
--usage
Give a short usage message. Prints short help about command line options.
-V, --version
Print program version

Messages

Faults and warnings encountered are sent to standard error for logging. To redirect them into a file append 2>filename.log after the command. The format of messages is the following:

<filename>:<line>:<character>: <severity>: <message>

a.asm:4:8: error: page error at $0007d0

Warnings

Top of memory exceeded
compile continues at the bottom ($0000)
Memory bank exceeded
compile continues in the next 64 KiB bank, however execution may not
Possible jmp ($xxff) bug
yet another 65xx feature...
Long branch used
Branch too long, so long branch was used (bxx ∗+5 jmp)
Directive ignored
An assembler directive was ignored for compatibility reasons.

Errors

Double defined
double use of label/macro
Not defined
not defined label
Extra characters on line
there's some garbage on the end of line
Constant too large
the number was too big
General syntax
can't do anything with this
X expected
X may be missing
Expression syntax
syntax error
Branch too far
can't relative branch that far
Missing argument
no argument given
Illegal operand
can't be used here
Requirements not met:
Not all features are provided, at least one is missing
Conflict:
at least one feature is provided, which shouldn't be there
Division by zero
Cannot calculate value

Fatal errors

Can't locate file
cannot open file
Out of memory
won't happen ;)
Can't write object file:
cannot write the result
Can't write listing file:
cannot write the list file
Can't write label file:
cannot write the label file
File recursion
wrong use of .include
Macro recursion too deep
wrong use of nested macros
Unknown CPU
CPU type not known
Ooops! Too many passes...
With a carefully crafted source file it's possible to create unresolvable situations. Fix your code.

Credits

Original written for DOS by Marek Matula of Taboo, then ported to ANSI C by BigFoot/Breeze, and finally added 65816 support, DTV, illegal opcodes, optimizations, multi pass compile and a lot of features by Soci/Singular. Improved TASS compatibility, PETSCII codes by Groepaz.

Additional code: my_getopt command-line argument parser by Benjamin Sittler, avl tree code by Franck Bui-Huu, ternary tree code by Daniel Berlin, snprintf Alain Magloire, Amiga OS4 support files by Janne Peräaho.

Main developer and maintainer: soci at c64.rulez.org


Default translation and escape sequences

Raw 8-bit source

By default raw 8-bit encoding is used and nothing is translated or escaped. This mode is for compiling sources which are already PETSCII.

The none encoding for raw 8-bit

Does no translation at all, no translation table, no escape sequences.

The screen encoding for raw 8-bit

The following translation table applies, no escape sequences.

Built-in PETSCII to PETSCII screen code translation table
InputByte InputByte
00–1F80–9F 20–3F20–3F
40–5F00–1F 60–7F40–5F
80–9F80–9F A0–BF60–7F
C0–FE40–7E FF5E

Unicode and ASCII source

Unicode encoding is used when the -a option is given on the command line.

The none encoding for Unicode

This is a Unicode to PETSCII mapping, including escape sequences for control codes.
Built-in Unicode to PETSCII translation table
GlyphUnicodeByte GlyphUnicodeByte
 –@U+0020–U+004020–40 A–ZU+0041–U+005AC1–DA
[U+005B5B ]U+005D5D
a–zU+0061–U+007A41–5A £U+00A35C
πU+03C0FF U+21905F
U+21915E U+2500C0
U+2502DD U+250CB0
U+2510AE U+2514AD
U+2518BD U+251CAB
U+2524B3 U+252CB2
U+2534B1 U+253CDB
U+256DD5 U+256EC9
U+256FCB U+2570CA
U+2571CE U+2572CD
U+2573D6 U+2581A4
U+2582AF U+2583B9
U+2584A2 U+258CA1
U+258DB5 U+258EB4
U+258FA5 U+2592A6
U+2594A3 U+2595A7
U+2596BB U+2597AC
U+2598BE U+259ABF
U+259DBC U+25CBD7
U+25CFD1 U+25E4A9
U+25E5DF U+2660C1
U+2663D8 U+2665D3
U+2666DA U+2713BA
Built-in PETSCII escape sequences
EscapeByte EscapeByte EscapeByte
{bell}07 {black}90 {blk}90
{blue}1F {blu}1F {brn}95
{brown}95 {cbm-∗}DF {cbm-+}A6
{cbm-−}DC {cbm-0}30 {cbm-1}81
{cbm-2}95 {cbm-3}96 {cbm-4}97
{cbm-5}98 {cbm-6}99 {cbm-7}9A
{cbm-8}9B {cbm-9}29 {cbm-@}A4
{cbm-^}DE {cbm-a}B0 {cbm-b}BF
{cbm-c}BC {cbm-d}AC {cbm-e}B1
{cbm-f}BB {cbm-g}A5 {cbm-h}B4
{cbm-i}A2 {cbm-j}B5 {cbm-k}A1
{cbm-l}B6 {cbm-m}A7 {cbm-n}AA
{cbm-o}B9 {cbm-pound}A8 {cbm-p}AF
{cbm-q}AB {cbm-r}B2 {cbm-s}AE
{cbm-t}A3 {cbm-up arrow}DE {cbm-u}B8
{cbm-v}BE {cbm-w}B3 {cbm-x}BD
{cbm-y}B7 {cbm-z}AD {clear}93
{clr}93 {control-0}92 {control-1}90
{control-2}05 {control-3}1C {control-4}9F
{control-5}9C {control-6}1E {control-7}1F
{control-8}9E {control-9}12 {control-:}1B
{control-;}1D {control-=}1F {control-@}00
{control-a}01 {control-b}02 {control-c}03
{control-d}04 {control-e}05 {control-f}06
{control-g}07 {control-h}08 {control-i}09
{control-j}0A {control-k}0B {control-left arrow}06
{control-l}0C {control-m}0D {control-n}0E
{control-o}0F {control-pound}1C {control-p}10
{control-q}11 {control-r}12 {control-s}13
{control-t}14 {control-up arrow}1E {control-u}15
{control-v}16 {control-w}17 {control-x}18
{control-y}19 {control-z}1A {cr}0D
{cyan}9F {cyn}9F {delete}14
{del}14 {dish}08 {down}11
{ensh}09 {esc}1B {f10}82
{f11}84 {f12}8F {f1}85
{f2}89 {f3}86 {f4}8A
{f5}87 {f6}8B {f7}88
{f8}8C {f9}80 {gray1}97
{gray2}98 {gray3}9B {green}1E
{grey1}97 {grey2}98 {grey3}9B
{grn}1E {gry1}97 {gry2}98
{gry3}9B {help}84 {home}13
{insert}94 {inst}94 {lblu}9A
{left arrow}5F {left}9D {lf}0A
{lgrn}99 {lower case}0E {lred}96
{lt blue}9A {lt green}99 {lt red}96
{orange}81 {orng}81 {pi}FF
{pound}5C {purple}9C {pur}9C
{red}1C {return}0D {reverse off}92
{reverse on}12 {rght}1D {right}1D
{run}83 {rvof}92 {rvon}12
{rvs off}92 {rvs on}12 {shift return}8D
{shift-∗}C0 {shift-+}DB {shift-,}3C
{shift-−}DD {shift-.}3E {shift-/}3F
{shift-0}30 {shift-1}21 {shift-2}22
{shift-3}23 {shift-4}24 {shift-5}25
{shift-6}26 {shift-7}27 {shift-8}28
{shift-9}29 {shift-:}5B {shift-;}5D
{shift-@}BA {shift-^}DE {shift-a}C1
{shift-b}C2 {shift-c}C3 {shift-d}C4
{shift-e}C5 {shift-f}C6 {shift-g}C7
{shift-h}C8 {shift-i}C9 {shift-j}CA
{shift-k}CB {shift-l}CC {shift-m}CD
{shift-n}CE {shift-o}CF {shift-pound}A9
{shift-p}D0 {shift-q}D1 {shift-r}D2
{shift-space}A0 {shift-s}D3 {shift-t}D4
{shift-up arrow}DE {shift-u}D5 {shift-v}D6
{shift-w}D7 {shift-x}D8 {shift-y}D9
{shift-z}DA {space}20 {sret}8D
{stop}03 {swlc}0E {swuc}8E
{tab}09 {up arrow}5E {up/lo lock off}09
{up/lo lock on}08 {upper case}8E {up}91
{white}05 {wht}05 {yellow}9E
{yel}9E

The screen encoding for Unicode

This is a Unicode to PETSCII screen code mapping, including escape sequences for control code screen codes.
Built-in Unicode to PETSCII screen code translation table
GlyphUnicodeTranslated GlyphUnicodeTranslated
 –?U+0020–U+003F20–3F @U+004000
A–ZU+0041–U+005A41–5A [U+005B1B
]U+005D1D a–zU+0061–U+007A01–1A
£U+00A31C πU+03C05E
U+21901F U+21911E
U+250040 U+25025D
U+250C70 U+25106E
U+25146D U+25187D
U+251C6B U+252473
U+252C72 U+253471
U+253C5B U+256D55
U+256E49 U+256F4B
U+25704A U+25714E
U+25724D U+257356
U+258164 U+25826F
U+258379 U+258462
U+258C61 U+258D75
U+258E74 U+258F65
U+259266 U+259463
U+259567 U+25967B
U+25976C U+25987E
U+259A7F U+259D7C
U+25CB57 U+25CF51
U+25E469 U+25E55F
U+266041 U+266358
U+266553 U+26665A
U+27137A
Built-in PETSCII screen code escape sequences
EscapeByte EscapeByte EscapeByte
{cbm-∗}5F {cbm-+}66 {cbm-−}5C
{cbm-0}30 {cbm-9}29 {cbm-@}64
{cbm-^}5E {cbm-a}70 {cbm-b}7F
{cbm-c}7C {cbm-d}6C {cbm-e}71
{cbm-f}7B {cbm-g}65 {cbm-h}74
{cbm-i}62 {cbm-j}75 {cbm-k}61
{cbm-l}76 {cbm-m}67 {cbm-n}6A
{cbm-o}79 {cbm-pound}68 {cbm-p}6F
{cbm-q}6B {cbm-r}72 {cbm-s}6E
{cbm-t}63 {cbm-up arrow}5E {cbm-u}78
{cbm-v}7E {cbm-w}73 {cbm-x}7D
{cbm-y}77 {cbm-z}6D {left arrow}1F
{pi}5E {pound}1C {shift-∗}40
{shift-+}5B {shift-,}3C {shift-−}5D
{shift-.}3E {shift-/}3F {shift-0}30
{shift-1}21 {shift-2}22 {shift-3}23
{shift-4}24 {shift-5}25 {shift-6}26
{shift-7}27 {shift-8}28 {shift-9}29
{shift-:}1B {shift-;}1D {shift-@}7A
{shift-^}5E {shift-a}41 {shift-b}42
{shift-c}43 {shift-d}44 {shift-e}45
{shift-f}46 {shift-g}47 {shift-h}48
{shift-i}49 {shift-j}4A {shift-k}4B
{shift-l}4C {shift-m}4D {shift-n}4E
{shift-o}4F {shift-pound}69 {shift-p}50
{shift-q}51 {shift-r}52 {shift-space}60
{shift-s}53 {shift-t}54 {shift-up arrow}5E
{shift-u}55 {shift-v}56 {shift-w}57
{shift-x}58 {shift-y}59 {shift-z}5A
{space}20 {up arrow}1E

Opcodes

Standard 6502 opcodes

The standard 6502 opcodes
ADC61 65 69 6D 71 75 79 7D AND21 25 29 2D 31 35 39 3D
ASL06 0A 0E 16 1E BCC90
BCSB0 BEQF0
BIT24 2C BMI30
BNED0 BPL10
BRK00 BVC50
BVS70 CLC18
CLDD8 CLI58
CLVB8 CMPC1 C5 C9 CD D1 D5 D9 DD
CPXE0 E4 EC CPYC0 C4 CC
DECC6 CE D6 DE DEXCA
DEY88 EOR41 45 49 4D 51 55 59 5D
INCE6 EE F6 FE INXE8
INYC8 JMP4C 6C
JSR20 LDAA1 A5 A9 AD B1 B5 B9 BD
LDXA2 A6 AE B6 BE LDYA0 A4 AC B4 BC
LSR46 4A 4E 56 5E NOPEA
ORA01 05 09 0D 11 15 19 1D PHA48
PHP08 PLA68
PLP28 ROL26 2A 2E 36 3E
ROR66 6A 6E 76 7E RTI40
RTS60 SBCE1 E5 E9 ED F1 F5 F9 FD
SEC38 SEDF8
SEI78 STA81 85 8D 91 95 99 9D
STX86 8E 96 STY84 8C 94
TAXAA TAYA8
TSXBA TXA8A
TXS9A TYA98
Aliases, pseudo instructions
ASL0A BGEB0
BLT90 GCC4C 90
GCS4C B0 GEQ4C F0
GGE4C B0 GLT4C 90
GMI30 4C GNE4C D0
GPL10 4C GVC4C 50
GVS4C 70 LSR4A
ROL2A ROR6A
SHL06 0A 0E 16 1E SHR46 4A 4E 56 5E

6502 illegal opcodes

This processor is a standard 6502 with the NMOS illegal opcodes.

Additional opcodes
ANC0B ANE8B
ARR6B ASR4B
DCPC3 C7 CF D3 D7 DB DF ISBE3 E7 EF F3 F7 FB FF
JAM02 LAXA3 A7 AB AF B3 B7 BF
LDSBB NOP04 0C 14 1C 80
RLA23 27 2F 33 37 3B 3F RRA63 67 6F 73 77 7B 7F
SAX83 87 8F 97 SBXCB
SHA93 9F SHS9B
SHX9E SHY9C
SLO03 07 0F 13 17 1B 1F SRE43 47 4F 53 57 5B 5F
Additional aliases
AHX93 9F ALR4B
AXSCB DCMC3 C7 CF D3 D7 DB DF
INSE3 E7 EF F3 F7 FB FF ISCE3 E7 EF F3 F7 FB FF
LAEBB LASBB
LXAAB TAS9B
XAA8B

65DTV02 opcodes

This processor is an enhanced version of standard 6502 with some illegal opcodes.

Additionally to 6502 illegal opcodes
BRA12 SAC32
SIR42
Additional pseudo instruction
GRA12 4C    
These illegal opcodes are not valid
ANC0B JAM02
LDSBB NOP04 0C 14 1C 80
SBXCB SHA93 9F
SHS9B SHX9E
SHY9C
These aliases are not valid
AHX93 9F AXSCB
LAEBB LASBB
TAS9B

Standard 65C02 opcodes

This processor is an enhanced version of standard 6502.

Additional opcodes
ADC72 AND32
BIT34 3C 89 BRA80
CMPD2 DEC3A
EOR52 INC1A
JMP7C LDAB2
ORA12 PHXDA
PHY5A PLXFA
PLY7A SBCF2
STA92 STZ64 74 9C 9E
TRB14 1C TSB04 0C
Additional aliases and pseudo instructions
CLR64 74 9C 9E DEA3A
GRA4C 80 INA1A

R65C02 opcodes

This processor is an enhanced version of standard 65C02.

Additional opcodes
BBR0F 1F 2F 3F 4F 5F 6F 7F BBS8F 9F AF BF CF DF EF FF
RMB07 17 27 37 47 57 67 77 SMB87 97 A7 B7 C7 D7 E7 F7

W65C02 opcodes

This processor is an enhanced version of R65C02.

Additional opcodes
STPDB WAICB
Additional aliases
HLTDB      

W65816 opcodes

This processor is an enhanced version of W65C02.

Additional opcodes
ADC63 67 6F 73 77 7F AND23 27 2F 33 37 3F
BRL82 CMPC3 C7 CF D3 D7 DF
COP02 EOR43 47 4F 53 57 5F
JMP5C DC JSL22
JSRFC LDAA3 A7 AF B3 B7 BF
MVN54 MVP44
ORA03 07 0F 13 17 1F PEAF4
PEID4 PER62
PHB8B PHD0B
PHK4B PLBAB
PLD2B REPC2
RTL6B SBCE3 E7 EF F3 F7 FF
SEPE2 STA83 87 8F 93 97 9F
TCD5B TCS1B
TDC7B TSC3B
TXY9B TYXBB
XBAEB XCEFB
Additional aliases
CSP02 CLPC2
JML5C DC SWAEB
TAD5B TAS1B
TDA7B TSA3B

65EL02 opcodes

This processor is an enhanced version of standard 65C02.

Additional opcodes
ADC63 67 73 77 AND23 27 33 37
CMPC3 C7 D3 D7 DIV4F 5F 6F 7F
ENT22 EOR43 47 53 57
JSRFC LDAA3 A7 B3 B7
MMUEF MUL0F 1F 2F 3F
NXA42 NXT02
ORA03 07 13 17 PEAF4
PEID4 PER62
PHDDF PLDCF
REA44 REI54
REPC2 RER82
RHA4B RHI0B
RHX1B RHY5B
RLA6B RLI2B
RLX3B RLY7B
SBCE3 E7 F3 F7 SEA9F
SEPE2 STA83 87 93 97
STPDB SWAEB
TADBF TDAAF
TIXDC TRXAB
TXI5C TXR8B
TXY9B TYXBB
WAICB XBAEB
XCEFB ZEA8F
Additional aliases
CLPC2 HLTDB

65CE02 opcodes

This processor is an enhanced version of R65C02.

Additional opcodes
ASR43 44 54 ASWCB
BCC93 BCSB3
BEQF3 BMI33
BNED3 BPL13
BRA83 BSR63
BVC53 BVS73
CLE02 CPZC2 D4 DC
DEWC3 DEZ3B
INWE3 INZ1B
JSR22 23 LDAE2
LDZA3 AB BB NEG42
PHWF4 FC PHZDB
PLZFB ROWEB
RTS62 SEE03
STA82 STX9B
STY8B TAB5B
TAZ4B TSY0B
TYS2B TZA6B
Additional aliases
ASR43 BGEB3
BLT93 NEG42
RTN62
This alias is not valid
CLR64 74 9C 9E      

Appendix

Assembler directives

.al .align .as .assert .bend .binary .binclude .block .break .byte .case .cdef .cerror .char .check .comment .continue .cpu .cwarn .databank .default .dint .dpage .dsection .dstruct .dunion .dword .edef .else .elsif .enc .end .endc .endf .endif .endm .endp .ends .endswitch .endu .endweak .eor .error .fi .fill .for .function .goto .here .hidemac .if .ifeq .ifmi .ifne .ifpl .include .int .lbl .lint .logical .long .macro .next .null .offs .option .page .pend .proc .proff .pron .ptext .rept .rta .section .segment .send .shift .shiftl .showmac .struct .switch .text .union .var .warn .weak .word .xl .xs

Built-in functions

abs acos all any asin atan atan2 bool cbrt ceil cos cosh deg exp float floor format frac hypot int len log log10 pow rad range repr round sign sin sinh size sqrt str tan tanh trunc