64tass v1.58 r2974 reference manual

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

Contrary how the length of this document suggests 64tass can be used with just basic 6502 assembly knowledge in simple ways like any other assembler. If some advanced functionality is needed then this document can serve as a reference.

This is a development version. Features or syntax may change as a result of corrections in non-backwards compatible ways in some rare cases. It's difficult to get everything right first time.

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

The page hosts the latest and older versions with sources and a bug and a feature request tracker.


Table of Contents


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 command line option 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, format("%4d", 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      .addr ?         ;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 zero page
        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 a 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 decimal digits of arbitrary length. An underscore can be used between digits as a separator for better readability of long numbers. The following operations are accepted:

Integer operators and functions
x + yadd x to y2 + 2 is 4
xysubtract y from x41 is 3
xymultiply 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
~xx1~3 is −4
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

Integers are automatically promoted to float as necessary in expressions. Other types can be converted to integer using the integer type int.

        .byte 23        ; as unsigned
        .char -23       ; as signed

; using negative integers as immediate values
        ldx #-3         ; works as '#-' is signed immediate
num     = -3
        ldx #+num       ; needs explicit '#+' for signed 8 bits

        lda #((bitmap >> 10) & $0f) | ((screen >> 6) & $f0)
        sta $d018

Bit string constants

Bit string constants can be entered in hexadecimal form with a leading dollar sign or in binary with a leading percent sign. An underscore can be used between digits as a separator for better readability of long numbers. 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 %1
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
x << ybitwise shift left$0f << 4 is $0f0
x >> ybitwise shift right~$f4 >> 4 is ~$f

Length of bit string constants are defined in bits and is calculated from the number of bit 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.

Bit strings support indexing and slicing. This is explained in detail in section Slicing and indexing.

Other types can be converted to bit string using the bit string type bits.

        .byte $33       ; 8 bits in hexadecimal
        .byte %00011111 ; 8 bits in binary
        .text $1234     ; $34, $12 (little endian)

        lda $01
        and #~$07       ; 8 bits even after inversion
        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. An underscore can be used between digits as a separator for better readability. The following operations can be used:

Floating point operators and functions
x + yadd x to y2.2 + 2.2 is 4.4
xysubtract y from x4.11.1 is 3.0
xymultiply 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 to power of y2.0 ∗∗ −1 is 0.5
xnegated value2.0 is −2.0
+xunchanged+2.0 is 2.0
~xalmost x~2.1 is almost −2.1
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

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

The only predefined constant is pi.

Floating point numbers are automatically truncated to integer as necessary. Other types can be converted to floating point by using the type float.

Fixed point conversion can be done by using the shift operators. For example an 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)
        .sint 12.2p8       ; 8:8 fixed point number (12.2)

Character string constants

Character 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 string literals escapes them and results in a single quote.

Character 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[:]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"

Character 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.

Other types can be converted to character strings by using the type str or by using the repr and format functions.

Character strings support indexing and slicing. This is explained in detail in section Slicing and indexing.

mystr   = "oeU"         ; character string constant
        .text 'it''s'   ; it's
        .word "ab"+1    ; conversion result is "bb" usually

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

Byte string constants

Byte strings are like character strings, but hold bytes instead of characters.

Quoted character strings prefixing by b, l, n, p, s, x or z characters can be used to create byte strings. The resulting byte string contains what .text, .shiftl, .null, .ptext and .shift would create. Direct hexadecimal entry can be done using the x prefix and z denotes a z85 encoded byte string. Spaces can be used between pairs of hexadecimal digits as a separator for better readability.

Byte string operators and functions
y .. xconcatenate stringsx"12" .. x"34" is x"1234"
y in xis substring ofx"34" in x"1234" is true
a x nrepeatx"ab" x 3 is x"ababab"
a[i]byte from startx"abcd12"[1] is x"cd"
a[−i]byte from endx"abcd"[−1] is x"cd"
a[:]no changex"abcd"[:] is x"abcd"
a[s:]cut off startx"abcdef"[1:] is x"cdef"
a[:−s]cut off endx"abcdef"[:−1] is x"abcd"
a[s]reversex"abcdef"[::−1] is x"efcdab"

Byte strings support indexing and slicing. This is explained in detail in section Slicing and indexing.

Other types can be converted to byte strings by using the type bytes.

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

        .text mystr     ;text as originally encoded
        .text s"p1"     ;convert to bytes like .shift
        .text l"p2"     ;convert to bytes like .shiftl
        .text n"p3"     ;convert to bytes like .null
        .text p"p4"     ;convert to bytes like .ptext

Binary data may be embedded in source code by using hexadecimal byte strings. This is more compact than using .byte followed by a lot of numbers. As expected 1 byte becomes 2 characters.

        .text x"fce2"   ;2 bytes: $fc and $e2 (big endian)

If readability is not a concern then the more compact z85 encoding may be used which encodes 4 bytes into 5 characters. Data lengths not a multiple of 4 are handled by omitting leading zeros in the last group.

        .text z"FiUj*2M$hf";8 bytes: 80 40 20 10 08 04 02 01

For data lengths of multiple of 4 bytes any z85 encoder will do. Otherwise the simplest way to encode a binary file into a z85 string is to create a source file which reads it using the line label = binary('filename'). Now if the labels are listed to a file then there will be a z85 encoded definition for this label.

Lists 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. 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[:]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)
... op aleft fold... + (1, 2, 3) is ((1+2)+3)
a op ...right fold(1, 2, 3) - ... is (1-(2-3))

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]].

Lists and tuples support indexing and slicing. This is explained in detail in section Slicing and indexing.

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)

Although lists elements of variables can't be changed using indexing (at the moment) the same effect can be achieved by combining slicing and concatenation:

lst     := lst[:2] .. [4] .. lst[3:]; same as lst[2] := 4 would be

Folding is done on pair of elements either forward (left) or reverse (right). The list must contain at least one element. Here are some folding examples:

minimum = size([part1, part2, part3]) <? ...
maximum = size([part1, part2, part3]) >? ...
sum     = size([part1, part2, part3]) + ...
xorall  = list_of_numbers ^ ...
join    = list_of_strings .. ...
allbits = sprites.(left, middle, right).bits | ...
all     = [true, true, true, true] && ...
any     = [false, false, false, true] || ...

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 + 127.5 * sin(range(256) * pi / 128)
;Screen row address tables
_       := $400 + range(0, 1000, 40)
scrlo   .byte <_
scrhi   .byte >_

Dictionaries

Dictionaries hold key and value pairs. Definition is done by collecting key:value pairs separated by comma between braces {"key":"value", :"default value"}.

Looking up a non-existing key is normally an error unless a default value is given. An empty dictionary is {}. This type is immutable. There are limitations what may be used as a key but the value can be anything.

Dictionary operators and functions
y .. xcombine dictionaries{1:2, 3:4} .. {2:3, 3:1} is {1:2, 2:3, 3:1}
x[i]value lookup{"1":2}["1"] is 2
x.isymbol lookup{.ONE:1, .TWO:2}.ONE is 1
y in xis a key1 in {1:2} is true
; Simple lookup
        .text {1:"one", 2:"two"}[2]; "two"
; 16 element "fader" table 1->15->12->11->0
        .byte {1:15, 15:12, 12:11, :0}[range(16)]
; Symbol accessible values. May be useful as a function return value too.
coords  = {.x: 24, .y: 50}
        ldx #coords.x
        ldy #coords.y

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
a.bb member of alabel.locallabel
.b in aif a has symbol b