Directives

Instructions to the assembler rather than to the Z80: where code goes, what data to lay down, what to include, and what to assemble only sometimes.

The ! prefix is optional on every directive. org $8000 and !org $8000 are the same, as are !device and device. fantasm init writes !device and a plain ORG, which is habit rather than a rule. Use whichever reads better, but be consistent.

Directive names are case-insensitive: ORG, org and Org all work.

A directive with optional arguments takes them as name=value, separated by commasassert_mem addr=$9000, length=256, fill=$00. That is the convention across the language rather than one directive’s form. ⚠ The separator is = and not a space because a value is an expression: !opt takes one option per line so its value can run to the end, but with several pairs length PAGES * 8192 fill 0 has two readings. A line uses the positional form or the named one, never both (E1107). See Testing.

Code Placement

Directive
org <addr>Assemble from this address.

Code emitted before any org lands at $0000 and warns as W1069 — usually a code-bearing include sitting above your org. See Diagnostics.

Data Definition

DirectiveAliasesEmits
dbdefb, byteBytes, or a string’s characters
dwdefw, word16-bit words, little-endian
dzA string plus a terminating zero
dhhexRaw bytes from a hex string
dsblockReserved space
        db  1, 2, $FF           ; 01 02 FF
        db  "AB"                ; 41 42
        dw  $1234, 2            ; 34 12 02 00
        dz  "hi"                ; 68 69 00
        dh  "DEADBEEF"          ; DE AD BE EF
        ds  4                   ; 00 00 00 00
        ds  3, $AA              ; AA AA AA

ds takes an optional fill byte, and zero-fills without one. db assembles a constant that holds text as its text, so db __NAME__ lays down the project name — see The Project File.

Includes

DirectiveAliases
include "file"Assemble another source file here
incbin "file"binaryInsert a file’s bytes verbatim

Both search the include path — the source’s own directory, then anything given with -I or include_dirs. A file that cannot be found is E1004; if it happens to be another target’s output, you get the more useful E1078 instead (Building Several Images).

The directory you ran fantasm from is not searched in its own right. It was until 2.0, and ahead of everything else, so a file asking for a header by basename got the copy at the build root rather than the one beside it — and the same source could build differently depending on where you started it. A source named without a directory has the working directory as its own, so nothing changes for the entry file.

SIZEOF answers the length of an incbin’ed file, so you need not count it yourself.

Conditional Assembly

DirectiveAliases
if <expression>#ifAssemble the block when the expression is not zero
ifdef <name>#ifdefAssemble when the constant is defined
ifndef <name>#ifndefAssemble when it is not
else#else
endif#endif

if takes any expression. Comparisons yield 1 and 0, so equality reads as you would expect and so does everything else:

VAL     equ 5
        if VAL = 5
        db  $AA                 ; assembled
        else
        db  $BB
        endif

        if VAL > 1
        if (VAL & 1)
        if 1

Anything non-zero is true, negatives included. A label may be tested as well as a constant.

The value has to be settled where the if is written. if chooses what to assemble, so it cannot wait for a name defined further down the file the way an instruction operand can — that is E1003, and the diagnostic explains it rather than only saying “undefined”.

All five have a #-prefixed spelling, and the two forms pair either way round — #if may be closed by endif.

ifdef tests constants — anything from equ, =, -D on the command line, or [defines] in the project file.

Conditionals nest, to any depth, in either arm. A conditional inside a branch that is not being assembled is counted but never read, so its condition may name something that exists only in the configuration that branch guards:

        ifdef USE_FAST_PATH
        if  FAST_PATH_SIZE > 256    ; only evaluated when USE_FAST_PATH is defined
        ld  a,1
        endif
        else
        ld  a,2
        endif

One else per level — a second is E1113.

#define

#define RETURN ret
        RETURN                  ; assembles C9

#define <name> <body> makes a parameterless macro, not a constant. It expands when the name starts a line, and is not a value: ld a, WIDTH after #define WIDTH 32 is E1003, because there is no constant called WIDTH.

ifdef cannot see a #define. The two are named after the C pair and do not work like it — ifdef tests constants and #define makes a macro, so #define F followed by ifdef F is false.

FantASM warns rather than deciding quietly:

Warning [W1079]: `F` is a macro; ifdef and ifndef test constants and cannot see one.
                 Use `F equ 1` if it should count as defined

The ifdef still evaluates false. A wrong one removes code, and the binary that results is smaller, valid, and wrong somewhere else entirely.

Macros

DirectiveAliases
macro <name> [params]Begin a macro
endendmEnd it

Only local labels (leading .) are allowed inside a macro body, so an expansion used twice does not define the same name twice.

Neither the macro’s name nor a parameter’s may be a reserved word, and the trap is that single letters are reserved: m is a condition code, c is both a register and a condition.

    macro m size        ; E1060 — `m` is a reserved word and cannot be a macro name
    macro org n         ; E1060 — so is `org`
    macro fill c        ; E1061 — `c` cannot be a macro parameter name
    macro fill size     ; fine

The diagnostic names the word, which is the only reason this is findable. See Diagnostics for the full list.

A macro body may invoke another macro, and that one another:

        macro READ_NEXTREG reg
        ld  bc,$243B
        ld  a,reg
        out (c),a
        inc b
        in  a,(c)
        endm

        macro SET_PALETTE_BIT bits
        READ_NEXTREG $43        ; a macro call inside a macro body
        or  bits
        nextreg $43,a
        endm

Depth is a chain of different macros, not a count of calls. Calling one macro a thousand times costs nothing; sixty-four nested inside each other is E1116, which almost always means a macro invokes itself.

Enums and Structs

        enum Colour 0
Red
Green
Blue
        ende

        db  Colour.Red, Colour.Green, Colour.Blue   ; 00 01 02

enum <name> [start [, step]] numbers each member on its own line. Members are reached as Name.Member — a bare Red is E1003.

A member may give its own value with = <expression>, and counting resumes from it. The start, the step and a member’s value are all expressions:

BASE = $40
        enum Sprite BASE, 4
Player                       ; $40
Enemy                        ; $44
Bullet  = BASE + $20         ; $60
Spare                        ; $64
        ende

Each has to be settled where it is written, as if does, so a name defined further down the file is E1003 rather than a value.

        struct Sprite
x       byte
y       byte
addr    word
        ends

        db  Sprite.x, Sprite.y, Sprite.addr         ; 00 01 02

struct <name> declares members with a size directive each; Name.member is that member’s byte offset. The struct’s own name is not a symbol — db Sprite is E1003, and the diagnostic suggests Sprite.y.

A size may also be written as a dotted suffix on the member’s name: x.b is the same member as x byte. Either form takes b, db, defb, byte for one byte and w, dw, defw, word for two; any other suffix is E1047.

A member may be named after a reserved word. device, ld, a and nz are all legal member names — a member is only ever read as Type.member, which is one name and never meets the keyword table. The spelling and case that resolve are the ones written: slot and page are one directive but not one member, and Device is not device.

A Struct Inside a Struct

A member’s type may be another struct, whose members are taken into this one at that name:

        struct Pt
x       db
y       db
        ends

        struct Rect
tl      Pt              ; Rect.tl.x is 0, Rect.tl.y is 1
br      Pt              ; Rect.br is 2
kind    db              ; Rect.kind is 4, and sizeof(Rect) is 5
        ends

        org $8000
r:
        Rect 1,2,3,4,9  ; one value per byte the shape holds
        ld  a,(r.br.x)

The sub-object is named as well as its members, so Rect.br is an offset without knowing what a Pt contains — change what a point is and everything holding one follows.

The initialisers stay flat. Nesting is in the declaration, not the instance: a Rect takes five values in the order its members were declared, and there is no grouped form.

The type has to be defined above the member holding it. A name where a size belongs that is not a type is E1119; a struct holding itself is E1118, its size having no end. A held struct and a dotted member can also name the same thing — tl Pt and tl.b both make Rect.tl — and writing both is E1013, reported at the second.

Symbols

Directive
global <name>Export the symbol to the labels and SLD files
keep <name>Pin a label against --gc-modules, and against the unused warning

global controls export, not visibility. Inside a module every label is reachable from outside by qualified name whether or not it is global.

Build and Output

DirectiveAliases
device <machine>zx16, zx48, zx128, zxnext
format <fmt>bin, hex, sna, nex
opt <option> <value>#pragmaSet a setting mid-source
message "text {expr}"Print during assembly, substituting each brace

opt reaches the same settings as the command line — opt z80n on, opt device zxnext. Its options are verbose, cspect, z80n, maxcodesize, case_insensitive, device and format.

opt case_insensitive takes an underscore. A hyphen is the operator it is everywhere else, so case-insensitive is three tokens and not an option name — unlike --case-insensitive on the command line, which is a switch and may have one.

message takes no argument list. A name or expression goes in braces inside the template — message "COUNT is {COUNT}", message "twice is {COUNT * 2}". Written with a comma, message "COUNT is ", COUNT prints COUNT is and drops the rest with W1025. The value must be a number; a constant holding text is E1075.

Five names answer with text instead, and are the exception to that last rule:

NameValue
{_file}The source file being assembled
{_device}The machine
{_format}The output format
{_date}The build date, YYYY-MM-DD
{_time}The build time, HH:MM:SS

{_target} was the spelling of {_device} and still works, saying what replaced it once (W1103).

Documented Elsewhere

DirectivesPage
!assert, !debugDebugging a Build
module, endmodule, keepModules
rhaiRhai Scripting
test, endtest, init_reg, init_mem, init_cycle_limit, assert_reg, assert_mem, assert_checksum, assert_cyclesTesting
bank, page, slotMemory and Banking
nex, sna, formatOutput Formats

Documentation