21. Assembler¶
The assembler enables writing assembly source files for C projects or standalone assembly language projects.
21.1. Overview¶
Unlike Standard C, there is no standard assembly language. The syntax for assembly instructions used by the 65816 assembler mostly follows what is outlined in guides made by the vendor of the instruction set.
Assembly source expressions are heavily inspired by C-style operators and precedence rules.
The assembler processes assembly source code using the C preprocessor, allowing C comments, include files, and conditional inclusion.
Directives are largely specific to the Calypsi C compiler tool chain, appearing consistent across products from Calypsi. Directive names begin with a leading dot, a common practice in many, but not all, assemblers.
Note
The dot prefix is used to prevent potential future conflicts where a mnemonic name might clash with a commonly used directive in the product line. This also makes directives stand out in the source code.
21.2. Syntax¶
Source file format¶
Assembly source lines follow the traditional format: a label field in the first column, followed by an instruction that may take one or more operands. Comments are preceded by a semicolon:
[label[:]] [instruction [operands]] [; comment]
A label typically starts in the first column. If leading spaces are present, a label must be followed by a colon or enclosed in back quotes (see Symbol syntax below).
An instruction can be a mnemonic (target instruction name) or a directive. All directives begin with a leading dot as part of their name.
A label, a symbol describing a location, is defined by placing it in the first column of a source line, optionally followed by a colon.
Labels can also be declared by importing them with the .extern directive
or by value using the .equlab directive.
Predefined words in instructions (mnemonics and operands) are case insensitive, while symbols are case sensitive. Some examples follow:
; Assembler comments start with a semicolon
;
Loop: dex ; assembler comment
nop
BNE Loop
Predefined words are case-insensitive because both uppercase and lowercase assembly styles are commonly used.
Symbol syntax¶
Symbols are case sensitive and can be of arbitrary length. A symbol
starts with a letter or underscore, followed by letters, underscores, and
digits. Examples: _4, a_symbol, abc, Test1.
Any character is permitted in a symbol, provided the symbol is quoted with
a back-tick. Examples: `another symbol` and `Table: 5`.
Intermixing symbols with the same name but different cases is possible,
though considered poor style. Symbols in as65816 are case-sensitive
primarily to support the C compiler, which also has case-sensitive symbols.
This also encourages consistent mixed-case symbols in assembly source code,
which is considered good practice.
Preprocessor¶
The assembler uses a full-featured C preprocessor for input source files. It provides standard C preprocessor features, including header file inclusion, macro expansions, conditional compilation, and C-style comments.
For an introduction with examples on using the C preprocessor, refer to Wikipedia.
See Predefined macros for available macros, most of which are also relevant to the assembler.
21.3. 65816 assembly¶
The use of direct page is quite central on the 65816 target. If you have
an operand that cannot be solved at assembly time, the assembler will
use the longer absolute addressing. You can prefix an operand with
dp: to force the shorter direct page addressing, the following
example taken from a list file shows how it works:
0001 .extern foo
0002 000000 ad.... lda foo
0003 000003 a50a lda 10
0004 000005 a5.. lda dp:foo
Loading individual parts of an address can be done using relocation
operators, such as .word0 and .word2, see
Relocation operators for more information. In many 65816 assemblers
angle brackets are used for these (< and >). Here they are
named somewhat more uniform with directives and a notation that should
be fairly easy to read and understand.
0001 .extern foo, bar
0002 000000 a9.. lda ##.word0 foo
0003 000002 85.. sta dp:bar
0004 000004 a9.. lda ##.word2 foo
0005 000006 85.. sta dp:bar+2
Immediate size¶
The 65816 can operate in 8-bit or 16-bit mode. There are two such modes, accumulator mode and index register mode, which can be set to either 8-bit or 16-bit independently.
Contrary to some 65816 assemblers, the Calypsi assembler does not attempt to keep track of the current runtime mode settings when it processes instructions. Thus, each instruction is treated independently of each other and from the actual mode settings. Properly matching instructions with runtime mode settings is the sole responsibility of the developer.
This mainly affects the immediate addressing mode where the size of immediate operand depends on the active mode settings at runtime. The assembler can emit either 8-bit or 16-bit immediate instruction operands and you specify which by using either a single or double hash mark:
0001 000000 a23412 ldx ##0x1234 ; 16-bit immediate
0002 000003 a9ab lda #0xAB ; 8-bit immediate
Addressing mode range¶
As mentioned there is a dp: facility that can be put before an
address expression to specify that the direct page is intended. There
are also abs: and long: that can be used in to describe 16 and
24 bits addressing to be used.
For expressions that can be solved in the assembler, the shortest possible addressing is assumed if no such prefix modifier is present.
0001 000000 b50a lda dp:10,x
0002 000002 bd0a00 lda abs:10,x
0003 000005 bf0a0000 lda long:10,x
0004 000009 b50a lda 10,x
0005 00000b bd0010 lda 0x1000,x
0006 00000e bf001033 lda 0x331000,x
21.4. Sections¶
The assembly source file is divided into sections using the .section
directive. Each section, an indivisible unit of code or data, is laid out
in memory by the linker according to rules from a placement rules file.
If no .section directive is specified, the assembler defaults to a
.section code in the source file, meaning you start in a section named
code unless otherwise directed.
Multiple sections enable more flexible placement of code and data by the linker than a single section. A section name can be used multiple times, with each instance creating an individual section fragment.
All sections are relocatable due to the ELF file format and must be defined in the linker rules file.
Section kinds¶
The following table describes the supported section kinds. If unspecified,
the section defaults to text.
Section kinds and modifiers are case-insensitive.
Section kind |
Description |
|---|---|
|
Executable code. |
|
An initialized read/write data section in memory (RAM). |
|
An initialized read-only data section in memory (ROM). |
|
“Block Started by Symbol”; holds zero
initialized variables and variables not given
an initializer value.
The C runtime normally zero fill such
area before calling |
Section modifiers¶
Section modifiers describe further behavior and can be specified as positive or negative.
Section modifier |
Description |
|---|---|
|
Maintains the relative order of section fragments with the same name within a translation unit. |
|
Allows section fragments of the same name to be placed arbitrarily, regardless of other fragments with the same name. This is the default. |
|
Always include this section fragment in the program. This is the default for object files. |
|
Only include this section fragment in the program if someone refers to a label inside it. This is the default for library files. |
Note
Section fragments with the same name and the noreorder modifier
are combined by the linker into a single placement group, ensuring
contiguous placement. The absence of noreorder (or explicit
reorder) allows the linker to place each section fragment
arbitrarily, regardless of other fragments with the same name.
Section alignment¶
The .align directive specifies an alignment in address units.
It advances the location counter and inserts fillers if needed,
ensuring the next location has the specified alignment.
; Ensure that "table" label is placed at an address that can be
; evenly divided by 4.
.section data
.align 4
table: ...
21.5. Expressions¶
Numeric expressions operate in signed (2-complement) mode. A range check occurs based on value usage; exceeding the possible range results in an error. The Operators table lists standard operators, supplemented by specialized relocation and section operators (see Relocation operators and Section operators).
You can optionally use spaces between values and operators in an expression.
Operator |
Precedence |
Purpose |
|---|---|---|
|
9 |
Unary bit-wise not |
|
9 |
Unary logical not |
|
9 |
Unary negate |
|
9 |
Unary plus |
|
8 |
Multiply |
|
8 |
Divide |
|
8 |
Modulo |
|
7 |
Add |
|
7 |
Subtract |
|
6 |
Bit shift left |
|
6 |
Bit shift right |
|
5 |
Greater than |
|
5 |
Less than |
|
5 |
Greater than or equal |
|
5 |
Less than or equal |
|
4 |
Equal |
|
4 |
Not equal |
|
3 |
Bit-wise and |
|
2 |
Bit-wise exclusive or |
|
1 |
Bit-wise or |
21.6. Numeric constants¶
Integer constants can be entered in decimal, binary, octal, or
hexadecimal. Decimal integers are sequences of digits not starting
with zero. Prefixes 0x indicate hexadecimal, 0b binary, and
0 (followed by digits) octal.
Character constants are also supported, replaced by their ASCII value.
Some examples:
table: .byte 0x1ff ; hexadecimal number
.byte 077 ; octal number (corresponds to decimal 63)
.byte 65 ; decimal 65
.byte 'A' ; ASCII 65 (decimal)
.byte 0b1011 ; binary (corresponds to decimal 11)
.byte 0 ; zero (actually octal, but it is written
; the same way in decimal)
21.7. Location counter¶
The assembler converts source programs into machine code for the 65816
processor. Instructions occupy consecutive memory locations until the
next .section directive or end of file. The current instruction
address is accessed by a single period (.), often called “dot”.
The dot label, representing the current instruction location, is the location counter. It increments after each instruction, always holding the start address of the current instruction.
The location counter address is resolved by the linker but can be used in expressions. Short branches can use the location counter:
test: ldx #0
lda (table),y
bpl .+3 ; skip next instruction of positive
dex
However, using labels or local labels is often preferred (see below).
21.8. Local labels¶
Local labels are useful for local branch destinations in assembly source files and come in two variants, dollar-postfix alphanumeric and plus/minus sign character labels.
Dollar postfix style¶
Local labels end with a single $; the name can be an identifier or
numeric (e.g., loop$ or 3$). A local label is active only
between two non-local labels and cannot be exported to the linker.
They serve as temporary locations for short-distance branching,
typically for skips or local loops.
loop$: lda (ptr),y
beq 15$
iny
dex
bne loop$
bra 50$
15$: lda #7
After a non-local label, you can no longer refer to any local label
before it. Consider if the following code is added below the one
above, the presence of the non-local label foo resets the local
labels:
foo: ldx #5
bne loop$ ; error, loop$ above no longer visible
As an alternative, you can use ordinary labels related to the context and add a number to make it unique. What you do is mostly a matter of taste.
Sign style¶
Local labels can also be created using sequences of + or -
characters. The entire label name must use the same character, and its
length is used for matching. References to labels with minus characters
go backwards to the closest match, while plus characters go forward to
the closest match.
This allows easy determination of whether the destination label is before or after an instruction. Sign-style local labels can pass over non-local labels.
+ bne + ; this one goes to first + below
--
beq -- ; backward
+: beq ++++ ; goes over foobar
foobar: ; I am not in the way
nop
++++:
Note
Sign-style local labels are only usable with branch-style instructions. They are not allowed in more elaborate operands where an ordinary label is allowed.
21.9. Directives¶
All directives start with a single dot character.
The following table summarizes the directives known to the assembler:
Directive |
Purpose |
|---|---|
|
Generate code for given section |
|
Define a symbol value |
|
Define a symbol value that corresponds to a memory location |
|
Export symbols to the linker |
|
Synonym to |
|
Synonym to |
|
Export weak symbols to the linker |
|
Import symbol from other module |
|
Require symbol from other module |
|
Include a binary file as data |
|
Define a runtime model attribute |
|
Emit 8 bit values |
|
Emit 16 bit values |
|
Emit 24 bit values |
|
Emit 32 bit values |
|
Emit 64 bit values |
|
Emit the given string |
|
Emit the given string with a terminating 0 (C string) |
|
Fill memory with value |
|
Fill memory with value |
|
Specify alignment |
|
Define a macro |
|
End a macro definition |
|
Stop processing the source file |
A symbol-list is a list of symbols separated by commas. An expr-list is a list of expressions separated by commas.
Directives in detail¶
.section¶
The .section directive takes the name of the section as the first
argument. It can optionally be followed by a kind and modifiers to
describe the section further.
; A code section named "code" (text)
.section code
; A code section named "code" (text) that are not stored
; in relative order to other "code" section fragments in
; the same compilation unit.
.section code, reorder
; A data section named "storage"
.section storage, data
; A constant area in ROM that are always included in output,
; even when put in a library (provided that anything
; in the compilation unit is referenced).
.section table, rodata, root
.equ¶
Introduces a new symbol and gives it a value. The symbol appears in the label column and may have an optional colon after it:
BufNo .equ 7
BufSize: .equ 2 + ContentSize
Any expression can be used as a value, however using external symbols is subject to certain limitations imposed by relocations in the ELF object file format. In the case of valid expressions with external symbols, the value will be resolved by the linker.
.equlab¶
Constants can also be defined with the .equlab directive. This is
similar to the .equ directive, with the difference that it defines
a label, which describes a location, rather than a plain number:
MyLocation: .equlab 0xD7
Where this matters is in the interpretation of the debugging
information. Labels defined using .equlab are treated the same as
other location labels. The debugger will understand that a symbol
defined using .equlab can be used as a location when generating
disassembly listings, while symbols defined using .equ will not
be used for locations in the disassembly listing.
.public¶
Exports a symbol in the current file and makes in visible to other modules. Symbols are local to the file being assembled by default.
For shared definitions, an alternative is to put them in an include file and
define them using the .equ directive.
.global¶
Synonym to .public for compatibility with other assemblers.
.globl¶
Synonym to .public for compatibility with other assemblers.
.pubweak¶
A weak symbol is created using the .pubweak directive in a similar way to
.public. The difference is that a weak symbol may exist in multiple copies.
Of these potentially multiple copies, one is selected by the linker. If there
is a non-weak symbol among the weak ones, it will be picked by the linker.
Weak symbols serve a couple of purposes. They can be used for library replaceable objects where you can override a default library object using a non-weak public symbol. They are also useful for tools that generate assembly code where an identical construct may be generated multiple times, though only one is needed in the end.
.extern¶
Imports a symbol that is defined in some other module and make it visible in the current source file.
.require¶
A required symbol can be specified with the .require directive. It
works similar to the .extern directive, with the difference that
you do not need to actually use the symbol in any expression, it is
being pulled in by the linker regardless.
This is mostly of interest when building modular software using libraries.
The typical use of this is that you have initialization code somewhere
else built up using section fragments with the no-reorder
property. The no-reorder property ensures that the code fragments
appear next to each other. A section fragment is only active if
someone actually refers to it. In this case the .require directive
can be used to refer to it without actually using it. The result is
that code which relies on that initialization code fragment exists
can request that such code fragment becomes active.
.incbin¶
Include a binary file as embedded data directly into the program. This is useful if you have binary data assets as you can include them in the program without having to convert them to suitable source code.
my_data:
.incbin "rawdata.bin"
.rtmodel¶
It is possible to define runtime model attributes using the
.rtmodel directive. Such attributes are checked at link time to
ensure object file consistency. The attribute is an identifier and its
value is a string:
; activities suitable for winter
.rtmodel season, "winter"
Another source file could specify season to have another value:
; activities suitable for summer
.rtmodel season, "summer"
If you try to link these two modules together will result in an error
message describing that runtime model attribute season has a
mismatch.
Source files that do not define the season attribute can be linked
with either. It is also possible to use the special * value which
also means it works with either. It is an explicit way of saying that I aware of
this attribute and it works with whatever value it has:
; I can work with either season
.rtmodel season, "*"
Functionally it is equivalent to not having the attribute defined. The difference is more in the eye of the reader, you have actively considered the attribute and concluded it works with whatever interpretation there may be.
Note
A defined runtime attribute affects the entire compilation unit it appears in. If you need to have a more narrow scope for some runtime model attribute, you need to break up the source file into smaller pieces.
.byte¶
Define one or more 8 bit values expressed as a list of comma separated expressions.
.word¶
Define one or more 16 bit values expressed as a list of comma separated expressions.
.address¶
Defines one or more 24 bit values expressed as a list of comma separated expressions.
.long¶
Define one or more 32 bit values expressed as a list of comma separated expressions.
.quad¶
Define one or more 64 bit values expressed as a list of comma separated expressions.
.ascii¶
Take a string arguments and emit the bytes.
.asciz¶
Take a string arguments and emit the bytes followed by a terminating zero byte.
.fillto¶
Take an address offset and an optional filler value. The address value is relative to the start of the current section fragment. Emits the filler value until the location counter is equal address offset.
If the current section is bss, the filler value cannot be non-zero.
.space¶
Take an count value and an optional filler value. Emits count
number of filler values in the output. If the current section is bss,
the filler value cannot be non-zero and it only advances the location
counter by count.
.align¶
Emit zero fillers to bring the location counter in alignment with the specified alignment argument. To long word align a table, use:
.align 4
table .byte 1,2,3,4
Note
The .align directive also have the effect of applying alignment
on the section itself in the object file which forces the linker to
place the section according to the alignment. If there are multiple
.align directives with different alignment values in a section,
the assembler will determine the overall alignment needed and emit
that as the alignment of the section.
.end¶
End the source file and stop processing any further input. This directive is optional and processing will otherwise stop when the input file has been fully consumed.
.macro¶
Define a macro, see Macro language.
.endm¶
End a macro definition.
.argdelim¶
This directive defines delimiter characters that can be used include commas in an argument, see Macro language.
21.10. Relocations¶
The assembler and the C compiler produces relocatable output, which means that the program output does not have fixed addresses. The linker is responsible for placing sections at suitable memory addresses.
The compiler may need to refer to addresses that are unknown at compiler time. Such references are represented by relocations which are part of the object file. The linker processes the relocations after section placement to finalize the program.
Relocations are automatically generated by the assembler and C compiler when it encounters expressions or values that depend on final section placement.
Relocation operators¶
In certain contexts some additional prefix operators are available. They can be seen as relocation operators as they can be used to optionally introduce a relocation.
As they are relocations, they allow the operator to be executed at link time when the final addresses are known. However, they have the limitation that they must appear at the top level of the expression.
On the 65816 you often need to load word parts of an address. This
can be done with the .word0 and .word2 operators.
There are also .byte0, .byte1, .byte2 operators that
return individual bytes of an expression at link time.
; Set up a pointer
lda #.byte0 table
sta zp:ptr
lda #.byte1 table
sta zp:ptr+1
Note
Relocation operators have high precedence like other unary prefix operators. If you want to access an address with an offset, you need to surround the expression by parentheses.
.byte0¶
The .byte0 operator gives the first byte (count starts from 0)
and is allowed to be resolved at link time.
.byte1¶
The .byte1 operator gives the second byte (count starts from 0)
and is allowed to be resolved at link time.
.byte2¶
The .byte2 operator gives the third byte (count starts from 0)
and is allowed to be resolved at link time.
.word0¶
The .word0 operator gives the lower 16 bits word and is allowed to
be resolved at link time.
.word2¶
The .word2 operator gives the upper 16 bits word and is allowed to
be resolved at link time.
.tiny¶
This relocation gives the address on a symbol in the Tiny area, which
corresponds to the direct page.
Typically it is given an expression to be resolved at link time. The
expression is an address that is converted to a relative offset to the
linker defined direct page base symbol DirectPageStart:
0001 .extern foo
0002 000000 a9.. lda dp: .tiny (foo+2)
.near¶
This relocation gives the relative address of a symbol in the Near area, which
corresponds to the 16 bits absolute area.
Typically it is given an expression to be resolved at link time. The
expression is an address that is converted to a relative offset to the
linker defined Near base symbol NearBaseAddress:
0001 .extern foo
0002 000000 a9.... lda abs: .near (foo+2)
.kbank¶
This relocation is used for jmp and jsr inside the current
64K program bank. It takes an address and strips out the lower 16
bits, in the same way as .word0 does. In addition it checks that
the destination is within the same program bank (K register) as
the instruction itself.
0000 020034 4c.... jmp .kbank localLabel
Section operators¶
Section operators makes it possible to get hold of where a section is placed in memory.
Section names must be known to the assembler. If you want to refer to a section that is not used otherwise in the current assembly source file, simply declare it without inserting any code below it.
;;; Forward declaration
.section elsewhere
.section code
...
All these operators are resolved by the linker as placement is not known before link time. An error is given if a section spans multiple memories.
Operator |
Precedence |
Purpose |
|---|---|---|
|
9 |
The first address of the given section. |
|
9 |
The last address of the given section. |
|
9 |
The size of the given section. |
Note
Section size corresponds to 1 + end - start.
Warning
If you allow the linker to intermix different sections in the same allocation range, these operators will base their values on the first and last section fragment of the given name. Different sections that are interleaved inside are silently included in the address range given by these operators.
21.11. Macro language¶
The .macro directive allows you to generate new commands that can
create assembler output. A simple example follows:
foo .macro a, b
.byte \a
.word \b - 1
.long 0
.endm
This creates a new macro named foo which takes two arguments a
and b. To use an argument inside the macro, prefix the parameter
name with a backslash \.
Rules for argument substitutions¶
When looking for argument substitutions, the longest match is
favored. This means if you have parameters called a and aa,
substituting the longer name is always tried before shorter names.
As there is no way to
explicitly specify the end of a parameter name inside the body, a
parameter may accidently try to match characters that comes after the
parameter. A good rule of thumb is to make use of space to separate
entities whenever possible. This also tends to improve readability.
Use of local labels¶
Each macro expansion will create a new unique context for local labels inside the macro body. Any previous local label context is restored after the macro is expanded. Thus, local labels inside a macro will not clash or interfere with any local labels surrounding the use of the macro.
This also works when using nested macro expansions. If a macro uses another macro inside its body, that inner macro expansion will have its own private local label context, and the previous context of the outer macro expansion will be restored when the inner macro has been expanded.
Thus, you are able to use the same label name inside a macro and in the code that uses it without any clash:
waitreg .macro reg
1$: de\reg
bne 1$
.endm
ldx #100
waitreg x
1$: lda #0
Which would create the following list file:
###############################################################################
# #
# Calypsi assembler for 65816 version 5.16 #
# 14/Apr/2026 16:42:19 #
# Command line: example/macro-local.s -l #
# #
###############################################################################
0001 waitreg .macro reg
0002 1$: de\reg
0003 bne 1$
0004 .endm
0005
0006 000000 a264 ldx #100
0007 waitreg x
\ 000002 ca `1$`: dex
\ 000003 d0fd bne `1$`
0008 000005 a900 1$: lda #0
##########################
# #
# Memory sizes (decimal) #
# #
##########################
Executable (Text): 7 bytes
Arguments with comma¶
Arguments to a macro are comma separated. This poses a problem in a
situation where you want an argument to contain a comma character.
The .argdelim directive defines a start and stop character that
can be used to create an argument that contains a comma character.
.argdelim <>
access .macro arg1, arg2
...
.endm
access 0, <2,a>
Here arg1 is bound to 0 and arg2 is bound to the value
2,a.
The delimiter can be either one or two characters and you can pick any suitable character combination. By default there are no delimiter characters defined.
If a single character combination is not suitable, you can use two
characters, e.g. <- and -> which would be defined as follows:
.argdelim <-->
access .macro arg1, arg2
...
.endm
access 0, <-2,a->
All delimiter characters are stripped and arg2 is bound to 2,a
here as well.