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:

Table 5.1 Predefined processor symbols

Preprocessor symbol

Description

__CALYPSI_NUT__

An integer that is 1 when the Nut target (including NEWT variant) is used.

__CALYPSI_ASM__

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.

Table 5.2 Operators

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

.section section-name, argument-list

Generate code for given section. The argument list is optional and consists of words separated by commas, see section kinds and modifiers below.

.fat symbol

Specify an entry in the function address table.

.fatrpn symbol

Specify an entry in the function address table for an RPN program.

.name string

Define function header name.

.name string, prompt-bits

Define function header name with prompt bits.

.messl string

Format a text for mainframe entry MESSL.

.text string

Text literal, lcd character encoding.

.equ expr

Define a symbol value.

.equlab expr

Define a symbol value that corresponds to a memory location.

.public symbol-list

Export symbols to the linker.

.pubweak symbol-list

Export weak symbols to the linker.

.extern symbol-list

Import symbol from other module.

.require symbol-list

Require symbol from other module.

.suppress

Suppress warning on the next source line.

.rtmodel symbol, string

Define a run-time model attribute.

.shadow expr

Define placement relative to another section.

.newt_timing_start

Annotate following instructions for execution at normal speed (NEWT only).

.newt_timing_end

End annotating instructions for execution at normal speed. (NEWT only).

.macro parameter-list

Defines a macro

.endm

Ends a macro definition

.argdelim delimiters

Define delimiters for macro arguments that contains a comma

.end

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 main().

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

noreorder

Obey the relative order between section fragments of the same name as encountered in a translation unit.

reorder

Allow section fragment to be placed in arbitrary order relative to other sections with the same name. (This is the default).

root

Always include this section fragment in the program. This is the default for object files.

noroot

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.

Table 5.3 Section operators

Operator

Precedence

Purpose

.sectionStart sectionName

9

The first address of the given section.

.sectionEnd sectionName

9

The last address of the given section.

.sectionSize sectionName

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.