5. Nut assembler¶
The Nut assembler accepts a source file with HP mnemonics and generates a relocatable object file as output. This is the same mnemonics 1 used in the VASM listings 2 which makes it easier to follow them, compared to if you adopt another mnemonic set.
There are at least two other instructions sets in use, Jacobs-DeArras
and ZENROM. The assembler can be configured to accept Jacobs-DeArras
mnemonics using the --jda option, but not ZENROM.
The Nut assembler is a command line tool named asnut. Running it from the command line with the ``–help`` option will give a sign-on message and display accepted options:
Calypsi assembler for Hewlett-Packard Nut version 5.16
Usage: asnut [--version] [-o|--output-file OUTPUT-FILE] [-l]
[--list-file LIST-FILE] [-I DIRECTORY] [-D IDENTIFIER]
[-U IDENTIFIER] [-g|--debug] [--rtattr NAME=VALUE] [--weak-symbols]
[--jda] [--core CORE] FILE
use 'asnut --help' for detailed help
Available options:
--version Display version number
-o,--output-file OUTPUT-FILE
Name of output file
-l Generate a list file, named by appending '.lst' to
input file
--list-file LIST-FILE Generate list file, using given name
-I DIRECTORY Include directory
-D IDENTIFIER Predefine a macro
-U IDENTIFIER #undef a predefined macro
-g,--debug Produce debugging information
--rtattr NAME=VALUE Define a runtime attribute (identifier or quoted
string value accepted)
--weak-symbols Make all public symbols entries weak
--jda Use Jacobs-DeArras mnemonics
--core CORE Core, one of 'Nut' or 'NEWT' (defaults to 'Nut')
-h,--help Show this help text
Footnotes
- 1
HP allows space inside some mnemonics, which is quite non-standard in assembly languages of today. This habit is only partially supported here in that you can optionally do it for instructions with an empty argument field.
- 2
These are list files from HP covering major portions of the HP-41 internals and is an important source of information about its inner workings.
5.1. Source file format¶
Source lines to the assembler follow the traditional style, with a label field starting in the first column, followed by an instruction and any needed operands. Comments must be preceded by a semicolon:
[label[:]] [instruction [operands]] [; comment]
The instruction can either be a mnemonic (target instruction name) or a directive. Directives start with a dot.
A label is a symbol that describes a source location. It can be 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 using the .extern
directive.
Pre-defined words used in instructions (mnemonics and operands) are case insensitive, however symbols used in operands are case sensitive. Some examples of source lines follows:
; Comments start with a semi-colon
;
lab: c=0 w
nop ; a comment
GOTO lab
Symbol syntax¶
Symbols are case sensitive and can be of arbitrary length.
A symbol starts with a letter or underscore and can be followed by
letters, underscores and digits. Examples of symbols are _4,
a_symbol, abc and A1.
If you want to use other characters in a symbol, it is possible to do
so by surrounding the symbol by back quotes. Such symbols can be
`another symbol` and `Table: 5`.
5.2. Preprocessor¶
The assembler uses a full featured C preprocessor to handle the input source file. The preprocessor provides the normal features you will find in a C preprocessor, the ability to include header files, macro expansions, conditional compilation and use of C style comments.
Wikipedia is a good place to look for an introduction with many examples on how to use the C preprocessor.
When used in the assembler, the following macros are predefined:
Preprocessor symbol |
Description |
|---|---|
|
An integer that is 1 when the Nut target (including NEWT variant) is used. |
|
An integer that is 1 when the assembler is used. |
5.3. Sections¶
The assembly source file is divided into sections using the
.section directive. A section is a unit of code or data that
cannot be split up in smaller pieces.
Sections are laid out in memory by the linker according to rules
provided by a rules file.
Before reading any input, an implicit .section code is
applied. This means you will start off in a section that is named
code.
Using multiple sections allow more flexible placement of the code at link time compared to using a single section. A section name can be used multiple times and each use results in an individual section fragment.
All sections are relocatable and must be defined in the linker rules file.
5.4. Expressions¶
Numeric expressions work in signed (2-complement) mode with range checking depending on how the end result is used. If the result exceeds the allowed range, it is reported as an error. The Operators table shows the existing standard operators. In addition to these, there are also some rather specialized relocation and section operators, refer to Relocation operators and Section operators for more details.
You can use 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 |
Numeric constants¶
Integer constants values can be entered in decimal, binary, octal or
hexadecimal. A sequence of digits that do not start with a zero is
considered to be a decimal integer value. Any sequence starting with
0x is considered a hexadecimal integer value, a 0b prefix is
considered a binary integer value and any other sequence of digits
starting with 0 is considered to be an octal integer value.
Character constants can also be used and they are replaced by their corresponding ASCII value.
Some examples:
.con 0x1ff ; hexadecimal number
.con 077 ; octal number (corresponds to decimal 63)
.con 65 ; decimal 65
.con 'A' ; ASCII 65 (decimal)
.con 0b1011 ; binary (corresponds to decimal 11)
.con 0 ; zero (actually octal, but it is written
; the same way in decimal)
5.5. Location counter¶
The assembler converts the source program into machine code that can run
on the target processor. Each instruction will end up at some location
in memory in consecutive order until the next .section directive
(or end of file is reached). The address of the current instruction
can be accessed by a single period (.), usually referred to as “dot”.
The dot label which contains the current instruction location is also called the location counter and it is incremented after each instruction so that it always contains the value of the start address of the current instruction.
The actual address value of the location counter is not known before the program is linked, but it can still be used in expressions. Short branches can be expressed using the location counter:
gonc .+2 ; skip next instruction
c=c+1 x
However, it is often better to use labels or local labels instead, see below.
5.6. Local labels¶
Local labels exists in two variants. They are useful for local branch destinations in assembly source files. The first style is dollar postfix alphanumeric labels and the other variant makes use of plus and minus sign characters.
Dollar postfix style¶
Local labels end with a single $. The label name can either be an
identifier or numeric, i.e. loop$ or 3$.
A local label is active between two non-local labels and
cannot be exported to the linker. They are meant to be used as
temporary locations for short distance branching, typically short skips
or local loops.
a=a-c x ; adjust counter to be 0-7
gonc skip$
20$: c=c+c m ; shift one left A.X steps
skip$: a=a-1 x
gonc 20$
After a non-local label, you can no longer refer to any local label before it:
foo: c=0 w
gonc 20$ ; error, will no longer know about 20$ above
As an alternative, you can just use ordinary labels and perhaps 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 needs to use the same character and
the length is used when matching.
References to labels with minus characters goes backwards to the closest matching label and references to labels with plus characters goes forward to the closest matching label.
This makes it easy to see whether the destination label is before or after an instruction. Sign style local labels are allowed to pass over non-local labels.
+
goto + ; this one goes to first + below
--
goc -- ; backward
+: goc ++++ ; 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,
5.7. Defining constants¶
Constants are defined using the .equ directive. As with
labels, you can use 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.
5.8. Constants that are locations¶
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:
PCTOC: .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.
5.9. Mainframe entry points¶
When writing MCODE programs for the HP-41, you will sooner or later find a need to call functions defined as entry points in the mainframe (HP-41 firmware).
In the include directory of the tools installation there is a
mainframe.h file for this purpose.
To include header files, use the #include preprocessor directive,
see Preprocessor.
The assembler is configured to find this file in the installation, so
a simple:
#include "mainframe.h"
early in the file will make all the mainframe entry points available.
5.10. Directives¶
All directives start with a single dot character to distinguish them from instructions.
The following table summarizes the directives known to the assembler:
Directive |
Purpose |
|---|---|
|
Generate code for given section. The argument list is optional and consists of words separated by commas, see section kinds and modifiers below. |
|
Specify an entry in the function address table. |
|
Specify an entry in the function address table for an RPN program. |
|
Define function header name. |
|
Define function header name with prompt bits. |
|
Format a text for mainframe entry
|
|
Text literal, lcd character encoding. |
|
Define a symbol value. |
|
Define a symbol value that corresponds to a memory location. |
|
Export symbols to the linker. |
|
Export weak symbols to the linker. |
|
Import symbol from other module. |
|
Require symbol from other module. |
|
Suppress warning on the next source line. |
|
Define a run-time model attribute. |
|
Define placement relative to another section. |
|
Annotate following instructions for execution at normal speed (NEWT only). |
|
End annotating instructions for execution at normal speed. (NEWT only). |
|
Defines a macro |
|
Ends a macro definition |
|
Define delimiters for macro arguments that contains a comma |
|
Stop processing the source file |
A symbol-list is a list of symbols separated by commas.
The section directive¶
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 something else
; in the compilation unit is imported).
.section table, rodata, root
Section kinds¶
There are four section kinds, Text, Data, RODdata, BSS and NoInit. If not specified, the section is assumed to be Text.
Section kinds and modifiers are case insensitive.
Section kind |
Description |
|---|---|
Text |
Executable code. |
Data |
An initialized data section in read/write memory (RAM). |
ROData |
An initialized data section in read only memory (ROM). |
BSS |
“Block Started by Symbol”, intended to hold
variables that are not given a value yet.
A C compiler would normally zero fill such area
before calling |
NoInit |
Similar to BSS, but do not zero fill it. |
Section modifiers¶
A section modifier can be used to describe further behavior of the section. There are two such modifiers which can be specified either as positive or negative.
Section modifier |
Description |
|---|---|
|
Obey the relative order between section fragments of the same name as encountered in a translation unit. |
|
Allow section fragment to be placed in arbitrary order relative to other sections 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 refer to a label inside it. This is the default for library files. |
Note
Section fragments with the same name an noreorder modifier are
combined by the linker into a placement group that is placed as a
single unit. The effect of reorder (or lack of noreorder)
is that the section fragment is given its own placement group.
This also have the effect that any following section fragments
are combined in a new group separate from any section fragments
before it. Thus, a section fragment with reorder not only causes
it to be placed separately from the previous ones, it also makes a placement
or noreorder sections before and after it separate placement groups.
Section alignment¶
The .align directive specifies a given alignment in address
units. It ensures that the next location will have the specified
alignment. This is done by advancing the location and inserting
fillers if needed.
; Ensure that "table" label is placed at an address that can be
; evenly divided by 4.
.section data
...
.align 4
table: ...
Function address table entry¶
Each non-banked 4K ROM page has its function address table (FAT) and
the .fat directive is used to define an entry in this table. It
points the first execution address of an MCODE function. To make a
proper function entry, you also need to precede it by the name of the
function using the .name directive.
.section fat
.con 1 ; XROM number
.con .fatsize FatEnd ; number of entry points
.fat entry_HEADER ; header word
.fat entry_CODE ; a function
FatEnd: .con 0,0 ; end of function address table
.section code
.name "-MY ROM 1A"
entry_HEADER: rtn
.section code
.name "CODE"
entry_CODE:
Each function table entry takes up two words in ROM. Normally a FAT entry will point to an MCODE function within the same 4K page, but it can actually point to another 4K page before or after. In that case they need to be at the same relative position to each other at run-time. 3
Banked pages do not follow the traditional page layout and should therefore not have any function address table.
Footnotes
- 3
Recall that most 4K pages are page independent and can move at run-time. If you have FAT entries going to another 4K page, they most move together. A traditional 8K plug-in module moves this way, though it may or may not have FAT entries that go between the 4K pages.
Naming functions¶
MCODE functions can be named using the .name directive that will
take care of laying out the characters in the correct way in memory. This
means convert the ASCII characters to the corresponding LCD
characters, reverse the order and mark the end character properly.
The previous example shows how to use it.
You can optionally follow the name with two values that are the
prompt bits. These two values must be between 0 and 3:
.name "FIXENG",1,3
;; code
rtn
.name "FS?S",2,2
;; code
rtn
ASCII and LCD characters¶
Character constants are encoded in ASCII while strings are translated to the corresponding LCD character set if needed, which are not necessarily identical to ASCII.
This is because it is judged to be more natural to deal with individual characters as ASCII since that is how it is in most programming languages and the tools can not anticipate from a single character constant how it will be used. It may be intended for the LCD (in which case you need to manually adjust it), but it may also be for the Alpha register, RPN program or intended for transmissions to other devices, such as a printer. In these case any automatic LCD character translation would be undesirable.
On the other hand, .messl and .name directives expects LCD
characters and the .text directives is provided as a more generic
directive of similar kind. Thus, they will all translate ASCII
characters to corresponding LCD characters.
To allow for ASCII strings to be encoded more easily than using
individual characters with the .con directive, a special case with
.con followed by a string is allowed:
.con 'A','B','C' ; ASCII ABC
.con "ABC" ; same as above
.text "ABC" ; This, however, will be converted to corresponding LCD characters
LCD messages¶
Messages to the display are often written using the .messl
function in the mainframe. This function expects the string in a
special format which is easy to achieve with the .messl directive:
.extern MESSL
gosub MESSL
.messl "NO BUF" ; string encoded in a way suitable for MESSL entry
Global symbols¶
Symbols are by default local to the file being assembled. Such symbols can be exposed to the global scope and imported by other source files.
For shared definitions, an alternative is to put them in an include file and
define them using the .equ directive.
Use the .public directive to export named symbols and the .extern directive
to import symbols exported from some other source file.
Both directives take a comma separated list of symbols.
.public BufNo, entry_HEADER
.extern MESSL, NFRPU, BCDBIN
BufNo: .equ 7
.name "BUF ROM 1B"
entry_HEADER: rtn
Weak symbols¶
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.
Required symbols¶
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 certain initialization code fragment exists,
can request that such code fragment becomes active.
Run-time model¶
It is possible to define run-time model attributes using the
.rtmodel directive. Such attributes are checked at link time to
ensure object file consistency.
One example could be that you have an attribute telling how it uses page 4. One source file that makes use of Library#4 could define an attribute:
; Can only be used with Library#4 in page 4
.rtmodel page4, "Library#4"
Another source file makes use of page 4 for a Forth system could have:
; Can only be used with some Forth in page 4
.rtmodel page4, "Forth"
If you try to link these two modules together will result in an error
message describing that run-time model attribute page4 has a
mismatch.
Source files that do not define the page attribute can be linked
with either. It is also possible to use the special * value which
also means it works with either. It essentially says that I know what
I am doing and it will work with whatever value is in this attribute:
; I work with whatever you decide to put in page 4
.rtmodel page4, "*"
Functionally it 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 run-time attribute affects the entire compilation unit it appears in. If you need to have a more narrow scope for some run-time model attribute, you need to break up the source file into smaller pieces.
Suppressing warnings¶
Due to bugs in the HP-41 microprocessor, some combinations of instructions cause unpredictable behaviors. The assembler will try to spot potential problem cases and report them as warnings. There may be false warnings as the analysis made is rather shallow. There is also a risk that some potential problems are not reported as these unpredictable behaviors are not that well documented.
Currently the assembler will warn if any of the following instructions follow an arithmetic class two instruction:
c=cora
c=c&a
Whether this is actually a problem depends on whether the previous instruction generates a carry out of digit 13.
If you are certain that the warning is false, you can use the .suppress directive on the line before to suppress the warning:
c=c-1 xl
.suppress
c=c&a
Another well known bug in the HP-41 microprocessor is related to setting the pointer to 13 and accessing the G register. This will cause a transfer between the G register and the byte pointed out in the C register. Since the highest pointer value is 13, it would mean it should wrap and use 0 for the upper 4 bits. This, however, will not happen. This is an example of a CPU bug that is not flagged by the assembler.
Shadowing sections¶
The .shadow directive makes it easy to align code in different
banks to be located relative to each other in a simple, yet flexible
way.
Refer to Bank switching for examples on how it can be used and a discussion on different approaches to bank switching.
5.11. List files¶
List files are valuable output that allows you to get an overview of the program you are writing. Both the assembler and the linker can emit list files.
Given the following example source file:
;;; Mainframe entry points
.extern CLA, APPEND
;;; ************************************************************
;;;
;;; DECODE - Decode the number in X into a hex number and
;;; append it to ALPHA. X may contain any binary data.
;;;
;;; ************************************************************
.name "DECODE"
entry_DECODE: st=1? 13 ; clear ALPHA if called from keyboard
gsubnc CLA
c=regn x
m=c
ldi 13 ; counter
bcex x
1$: pt= 0 ; loop start
c=m ; get next digit
rcr 13
m=c
rcr 1 ; digit to s
ldi 3 ; 0x30-0x3f
rcr 13 ; to C[2:0]
acex x
ldi 0x3a
?a<c x ; 0-9?
goc 2$ ; yes
ldi 7 ; no, A-F, adjust value
a=a+c x
2$: acex x
g=c ; ASCII to g (for append)
gosub APPEND ; append character to ALPHA
abex x ; decrement counter
a=a-1 x
rtnc ; done
abex x
goto 1$
The generated list file will look as follows:
###############################################################################
# #
# Calypsi assembler for Hewlett-Packard Nut version 5.16 #
# 14/Apr/2026 16:42:37 #
# Command line: -l decode.s #
# #
###############################################################################
0001 ;;; Mainframe entry points
0002 .extern CLA, APPEND
0003
0004 ;;; ************************************************************
0005 ;;;
0006 ;;; DECODE - Decode the number in X into a hex number and
0007 ;;; append it to ALPHA. X may contain any binary data.
0008 ;;;
0009 ;;; ************************************************************
0010
0011 0000 085004 .name "DECODE"
0011 0002 00f003
0011 0004 005004
0012 0006 2cc entry_DECODE: st=1? 13 ; clear ALPHA if called from keyboard
0013 0007 ...... gsubnc CLA
0014 0009 0f8 c=regn x
0015 000a 158 m=c
0016 000b 13000d ldi 13 ; counter
0017 000d 0e6 bcex x
0018 000e 39c 1$: pt= 0 ; loop start
0019 000f 198 c=m ; get next digit
0020 0010 2fc rcr 13
0021 0011 158 m=c
0022 0012 33c rcr 1 ; digit to s
0023 0013 130003 ldi 3 ; 0x30-0x3f
0024 0015 2fc rcr 13 ; to C[2:0]
0025 0016 0a6 acex x
0026 0017 13003a ldi 0x3a
0027 0019 306 ?a<c x ; 0-9?
0028 001a 027 goc 2$ ; yes
0029 001b 130007 ldi 7 ; no, A-F, adjust value
0030 001d 146 a=a+c x
0031 001e 0a6 2$: acex x
0032 001f 058 g=c ; ASCII to g (for append)
0033 0020 ...... gosub APPEND ; append character to ALPHA
0034 0022 066 abex x ; decrement counter
0035 0023 1a6 a=a-1 x
0036 0024 360 rtnc ; done
0037 0025 066 abex x
0038 0026 343 goto 1$
##########################
# #
# Memory sizes (decimal) #
# #
##########################
Executable (Text): 39 words
The list file starts with a header that contains the name of the tool that generated it, the command line and the time it was generated.
The body contains four columns, a) the line number; b) the location counter in the current section; c) the generated opcodes; and d) the source line.
If the opcodes cannot be resolved by the assembler, they will be represented by dots.
5.12. 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,
ignoring the order the parameters are given. 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 do:
waitfield .macro field
1$: c=c-1 \field
gonc 1$
.endm
ldi 100
waitfield x
1$: c=0 w
Which would create the following list file:
###############################################################################
# #
# Calypsi assembler for Hewlett-Packard Nut version 5.16 #
# 14/Apr/2026 16:42:38 #
# Command line: -l macro-local.s #
# #
###############################################################################
0001 waitfield .macro field
0002 1$: c=c-1 \field
0003 gonc 1$
0004 .endm
0005
0006 0000 130064 ldi 100
0007 waitfield x
\ 0002 266 `1$`: c=c-1 x
\ 0003 3fb gonc `1$`
0008 0004 04e 1$: c=0 w
0009
##########################
# #
# Memory sizes (decimal) #
# #
##########################
Executable (Text): 5 words
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.
5.13. Object file format¶
The object file format used is ELF (Executable and Linkable Format). While ELF is a flexible and extensible format, it imposes certain limitations on what can be represented in the format.
All sections are relocatable and are given their location by the linker (or loader). There is no support for absolute sections. As a result, there are no directive to create a section that starts at a fixed address in this assembler.
Expressions that need to be resolved at link time are limited in what they can contain. They may contain a location or external symbol, and optionally a fixed offset. This basically means that you can only have a symbol, or a symbol with an constant added to or subtracted from it. The following expressions are allowed:
; Examples of accepted expressions
.extern NFRPU, buffer
gsbp buffer
golong NFRPU + 1
The following expressions will result in errors:
; Examples of rejected expressions
.extern NFRPU, buffer, offset
gsbp buffer + offset ; error: offset not known
golong NFRPU * 2 ; error: only add or subtract allowed
The assembler will simplify expressions making it possible to use non-trivial expressions, but in the end any value used with an external symbol must be possible to reduce down to a fixed offset.
5.14. Relocations¶
A relocation is an entity stored in the object file format that indicates a location in the generated code that needs to be altered by the linker. Relocations are generated automatically by the assembler and you do not normally need to think about them.
A relocation entry contains the location in the generated code, expression to be relocated and which kind of relocation to perform.
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.
For the LC instruction which loads a nibble, you can use the
.nib0, .nib1, .nib2 and .nib3 operators to specify
that you want to get a specific nibble of a relocatable expression:
; Load the address of a location inside a module page.
gosub PCTOC ; Get my address (page)
pt= 5
lc .nib2 table ; set lower 12 bits to 'table'
lc .nib1 table
lc .nib0 table
For the LDI instruction and the .CON directive the .low10
operator allows you to get the lower 10 bits of a relocatable
expression.
; Load the lower (10 bits) part of an address
ldi .low10 table
The .low12 operator works in a similar way, but it allows the
address to be anywhere inside a 4K page, provided that is aligned on
an address that is a multiple of 4. It uses the available 10 bits to
store the lower 12 bits, assuming that the last two bits are 0. If you
use it to form an address, you need to scale the address:
; Load full address of a table in my own 4K page
gosub PCTOC ; C[6:3]= my own location
rcr 3 ; C[3]= page
ldi .low12 table
c=c+c x ; scale the page offset
c=c+c x
; C[3:0]= address of table
...
.align 4
table: .con data, etc
There are also .low8 and .high8 operators to extract the lower
and upper half of a relocatable expression. They work similar to
.low10, but extracts 8 bits at a time from different places to
cover a 16-bit address.
For the .CON directive, the .fatsize operator can be used to
get the size of the FAT. Simply use the .fatsize operator where
you put the size of the FAT of your module (at location 1) and give it
the address of the FAT end marker as argument. Here is an example:
;;; Start of module
.section HEADER
.con 21 ; XROM number
.con .fatsize fatend ; The size of FAT
.fat header
;;; End marker for function address table
.section FATEND
fatend: .con 0,0
The .fatsize operator does not enforce that it should be at
location 1, so feel free to use for other creative purposes. What it
does is to take the address of its argument, subtract its own location
and divide by 2.
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.
lc .nib2 (foo + 2)
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.
;;; 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 of the given name. Different sections that are interleaved inside are silently included in the address range given by these operators.