Directives

A directive is an instruction 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.

This page is the reference for the directives FantASM handles itself. Those belonging to a subject with a page of its own are listed under Documented Elsewhere and documented there.

Conventions

Each entry below gives the directive’s name, its syntax, what it does, the kind of each operand, its boundary conditions, and a worked example.

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. Syntax lines here are written without the prefix.

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

A directive with optional arguments takes them as name=value, separated by commas. The separator is = rather than a space, a value being an expression: length PAGES * 8192 fill 0 has two readings. A line uses the positional form or the named one, never both, which is E1107. See Testing.

Code Placement

Directive
orgAssemble from an address.
alignPad to the next multiple of a boundary.
sectionOpen a run of lines a linker places as one.
virtualAssemble a block for an address it is not placed at.

org

Syntax: org <address>

org assembles the lines following it from address. address is an expression settled where the directive is written.

Code emitted before the first org lands at $0000 and warns as W1069, which is usually a code-bearing include sitting above it. An address above $FFFF is truncated to sixteen bits with W1010. An org inside a section is E1155.

        org     $8000
start:                          ; start = $8000
        ret

align

Syntax: align <boundary> [, <fill>]

align pads to the next multiple of boundary, emitting fill for each byte of padding. boundary and fill are expressions; fill is a byte, and zero where none is given, as it is for ds.

A line already on the boundary emits nothing. A boundary that is not a power of two is rounded up to the next, with W1148 naming both figures, so the alignment is never weaker than the one written. A boundary outside 1…32768 is E1121. Inside a virtual block the address aligned is the one the code will run at rather than the one its bytes sit at.

If the origin is $8003 then:

        org     $8003
        align   256
table:                          ; $8100

The padding is settled where the line lands rather than where it is written, so it is correct inside a module or !library body, whose bytes are placed after the program:

        module  Gfx
        align   256
vectors:                        ; aligned wherever the body ends up
        dw      handler, handler
handler:
        ret
        endmodule

A ds cannot do that. A size decides where everything after it goes, so it is settled while the source is read, and a body that then moves keeps the length it computed beforehand — which is W1073, and align is the answer that warning names.

The padding is emitted rather than skipped, so the image stays contiguous and a .nex bank reads as zero across the gap.

⚠ An align inside a routine the sweep drops goes with it. The line sits within that label’s extent, so --gc-modules removes the padding along with the code it was aligning.

sjasmplus refuses a non-power-of-two boundary outright rather than rounding it.

section

Syntax: section <name> [, <option>…]

section opens a run of lines that a linker places as one unit. name is an identifier. It is for an object, where placement is somebody else’s to decide.

There is no closing directive: the next section ends the last. An org inside one is E1155, and a bank or page is E1158, each being an absolute placement the section has already answered for. See Building in Pieces.

        section code
start:
        ret
        section data            ; ends `code`
table:
        db      0, 0

virtual

Syntax: virtual <address> [, <page>] … endvirtual

virtual assembles a block for an address it is not placed at: its labels resolve at the address it will run at while its bytes stay inline. address and page are expressions.

It is for a routine copied somewhere at run time.

        virtual $C000
shadow:
        ret                     ; shadow = $C000, and the byte sits where it was written
        endvirtual

See Virtual Origins, which documents it in full.

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
dsblockA run of one byte, filling the space it takes
rsNothing — it takes the space and leaves it unwritten
        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
        rs  4                   ; nothing at all

db

Syntax: db <value> [, <value>…]

db emits one byte per value. Each value is an expression or a string literal; a string emits one byte per character. A constant that holds text emits that text, so db __NAME__ lays down the project name — see The Project File.

A value outside 0…255 is truncated to its low byte and warns as W1009.

        db  1, 2, $FF           ; 01 02 FF
        db  "AB"                ; 41 42
        db  300                 ; 2C, and W1009

dw

Syntax: dw <value> [, <value>…]

dw emits one 16-bit word per value, low byte first. Each value is an expression.

A value above 65535 is truncated to its low sixteen bits and warns as W1012.

        dw  $1234, 2            ; 34 12 02 00

dz

Syntax: dz <value> [, <value>…]

dz emits each value as db does, and follows each string with a zero byte. Each value is an expression or a string literal.

The zero follows a string, not the line: a numeric operand gets none.

        dz  "hi"                ; 68 69 00
        dz  "a", "b"            ; 61 00 62 00

dh

Syntax: dh "<digits>"

dh emits the bytes that digits spells in hexadecimal. The operand is a string literal.

An odd number of digits is padded on the left, so the last digit is a low nibble. A string holding anything that is not a hexadecimal digit is E1022, and so is an operand that is not a string.

        dh  "DEADBEEF"          ; DE AD BE EF
        dh  "ABC"               ; 0A BC

ds

Syntax: ds <count> [, <fill>]

ds emits count copies of fill, filling the space it takes. count and fill are expressions; fill is a byte, and zero where none is given.

A count outside 0…65536 is E1121.

        ds  4                   ; 00 00 00 00
        ds  3, $AA              ; AA AA AA

rs

Syntax: rs <count>

rs moves every address after it by count and writes nothing. count is an expression. A 4 KiB buffer declared with ds puts 4 KiB of zeros in the file; declared with rs it puts none.

It takes no fill, ds taking one because ds writes; there is nothing here for a fill to name. A count outside 0…65536 is E1121. A reservation with code after it cannot be written to a .bin: the flat file is bytes from the origin and has no way to say a gap, so the build is refused with E1085 rather than writing an image that is not the program. .nex and .sna carry each region’s address themselves and accept it.

        org     $8000
start:
        ret
buffer:
        rs      4096            ; buffer = $8001, and the file is one byte long

Reach for rs where the program writes the space before it reads it — a buffer, a stack, a decompression window. Reach for ds where the contents matter, and put reservations at the end, which is where a buffer belongs anyway.

⚠ Nothing initialises reserved space. ds 16, 0 guarantees sixteen zeros; rs 16 guarantees sixteen addresses and whatever was already there. To a running program it is ordinary RAM, so a !test may write it and read it back — see Testing.

This is not the 68000 RS. Devpac, vasm and SNASM use the name with RSSET and RSRESET for an offset counter that lays out structure fields; this one reserves image space, and structure layout is struct. A struct member may not be declared rs: a structure is a template, and its bytes are laid down where an instance is placed, which is what ds does.

Includes

DirectiveAliases
includeAssemble another source file here
incbinbinaryInsert a file’s bytes verbatim

Both search the include path: the source’s own directory, then anything given with -I or include_dirs. The directory fantasm was run 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. A source named without a directory has the working directory as its own, so nothing changes for the entry file.

include

Syntax: include "<file>"

include assembles file at the point the directive sits. file is a string literal, resolved against the include path.

A file that cannot be found is E1004. One that is another target’s output is E1078 instead, which says so — see Building Several Images.

        include "sprites.asm"

incbin

Syntax: incbin "<file>"

incbin inserts the bytes of file verbatim. file is a string literal, resolved against the include path as include resolves one.

SIZEOF answers the length of the file through the label carrying the incbin, so it need not be counted by hand. A file that cannot be found is E1004, or E1078 where it is another target’s output.

tiles:
        incbin  "tiles.bin"
TILE_BYTES  equ SIZEOF(tiles)

Where the binary is a routine built separately because it runs elsewhere, Virtual Origins writes it inline instead and keeps its symbols.

Conditional Assembly

DirectiveAliases
if#ifAssemble the block when the expression is not zero
ifdef#ifdefAssemble when the constant is defined
ifndef#ifndefAssemble when it is not
else#elseOpen the other arm
endif#endifClose the conditional

All five have a #-prefixed spelling, and the two forms pair either way round: a #if may be closed by endif. Conditionals nest to any depth, in either arm.

if

Syntax: if <expression> … [else] … endif

if assembles the block when expression is not zero. expression is any expression; comparisons yield 1 and 0, anything non-zero is true, negatives included, and 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”. A condition the assembler cannot read at all is E1147, which quotes it as written and lists the operators an expression takes; the block is skipped, else arm included, neither branch having been chosen. One else per level; a second is E1113.

If VAL is 5 then:

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

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

ifdef

Syntax: ifdef <name> … [else] … endif

ifdef assembles the block when a constant called name is defined. name is an identifier. A constant is anything from equ, =, -D on the command line, or [defines] in the project file.

Naming a macro rather than a constant is W1079, and the test still evaluates false.

        ifdef DEBUG
        call    trace
        endif

⚠ 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 says so rather than deciding quietly —

Warning [W1079]: `ifdef` cannot test macro `F`
help: `ifdef` and `ifndef` test constants. Define `F equ 1`

— and a wrong ifdef removes code, so the binary that results is smaller, valid, and wrong somewhere else entirely.

ifndef

Syntax: ifndef <name> … [else] … endif

ifndef assembles the block when no constant called name is defined. name is an identifier. It tests constants on the same terms as ifdef, and names a macro on the same terms too.

        ifndef  MEM_SLOT
MEM_SLOT    =   4
        endif

Macros

DirectiveAliases
macroBegin a macro
endendmEnd it
#defineMake a parameterless macro

macro

Syntax: macro <name> [<param>…] … end

macro begins a macro body, which is assembled wherever name is written. name and each param are identifiers.

Only local labels — those with a leading . — are allowed inside a 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: the first is E1060 and the second E1061. The trap in that is that single letters are reserved — m is a condition code, and c is both a register and a condition. The diagnostic names the word, which is the only reason this is findable; see Diagnostics for the full list.

    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

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 rather than a count of calls. Calling one macro a thousand times costs nothing; going past sixty-four nested inside each other is E1116, which almost always means a macro invokes itself.

#define

Syntax: #define <name> <body>

#define makes a parameterless macro called name whose expansion is body. name is an identifier and body is the rest of the line. It expands when the name starts a line.

It does not make a value: after #define WIDTH 32, ld a, WIDTH is E1003, there being no constant called WIDTH. ifdef cannot test one.

#define RETURN ret
        RETURN                  ; assembles C9

Repetition

DirectiveAliases
repAssemble an instruction, or a block, several times
endrendClose a rep block

rep

Syntax: rep <count>, <instruction> — rep <count> … endr

rep assembles instruction count times. With nothing after the count it opens a block instead, closed by endr or end, and repeats every line in that. count is an expression. rep 32, ldi is an unrolled copy, carrying none of ldir’s loop.

Only an instruction may be repeated, and anything else is E1125, whose hint names ds <count>, <fill> for a run of identical bytes. The one-line form needs its comma: rep 4 nop is E1127. A count outside 0…65536 is E1126, and so is a block whose count multiplied by its length passes that figure — rep 40000 around two lines is 80,000 statements. An endr with nothing open is E1128, and a block the source never closes is E1074.

        org     $8000
        rep     4, nop          ; 00 00 00 00
        rep     3
        ldi                     ; ED A0 ED A0 ED A0
        endr

A local label inside a block is named apart for each repetition, so a jump reaches its own copy rather than the first:

        rep     2
.spin:  djnz    .spin           ; 10 FE 10 FE — each djnz reaches its own .spin
        endr

Enums and Structs

enum

Syntax: enum <name> [<start> [, <step>]] … ende

enum numbers each member written on its own line, from start and in steps of step. name is an identifier; start and step are expressions, defaulting to 0 and 1.

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. Each of the three has to be settled where it is written, so a name defined further down the file is E1003 rather than a value.

        enum Colour 0
Red
Green
Blue
        ende

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

If BASE is $40 then:

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

struct

Syntax: struct <name> … ends

struct declares a template whose members each carry a size, and Name.member is that member’s byte offset. name is an identifier. Writing the struct’s own name is E1003, the template not being a symbol, and the diagnostic suggests a member.

A size is b, db, defb or byte for one byte and w, dw, defw or word for two. It may also be written as a dotted suffix on the member’s name, so x.b is the same member as x byte; any other suffix is E1047, which names the member and lists every form. A member may be named after a reserved word — device, ld, a and nz are all legal — a member being read only ever 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.

        struct Sprite
x       byte
y       byte
addr    word
        ends

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

A Member That Reserves Bytes

<name> ds <count> reserves count bytes — a name string, a sprite’s pixel rows, a scratch area. block is the same directive. The member’s offset and sizeof account for it, so the arithmetic a struct exists to stop is not written by hand.

The count is settled where the member is written, so it may be any expression resolvable at that point — pad ds WIDTH * 2 — the layout deciding where every later member sits. A count below one is E1009. The fill is not settled there, and may name a symbol defined further down the file, as every other member’s initialiser may.

        struct Sprite
x       byte
y       byte
name    ds 8            ; Sprite.name is 2
flags   byte            ; Sprite.flags is 10, and sizeof(Sprite) is 11
        ends

        org $8000
s:
        Sprite 1, 2, 0, 3   ; 01 02 00 00 00 00 00 00 00 00 03

An instance supplies one value per member, and it fills that member’s bytes the way ds <count>, <fill> does. Not one operand per byte, and not skipped — a skipped member would leave nothing on the line to say it was there.

A Member That Holds a Struct

A member’s type may be another struct, whose members are taken into this one at that name. 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 type has to be defined above the member holding it. A name where a size belongs that is not a type is E1119, and 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.

        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)

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

Symbols

Directive
globalExport the symbol to the labels and SLD files, and out of an object
externDeclare a symbol another object defines
keepPin a label against --gc-modules, and against the unused warning

global

Syntax: global <name>

global exports name to the labels and SLD files, and out of an object. name is a label or constant.

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

        global  draw
draw:
        ret                     ; `draw` appears in the labels and SLD files

extern

Syntax: extern <name>

extern declares that name is defined by another object. name is an identifier.

It is only legal when an object is being written, a loadable image having no later step to settle the name in; one there is E1156. A reference to an extern name assembles to zero and the object carries the name. The same name without the declaration is E1003, as it always was. See Building in Pieces.

        extern  putchar
        call    putchar         ; CD 00 00, and the object names `putchar`

keep

Syntax: keep <name>

keep pins name against --gc-modules and against the unused-label warning. name is a label.

What it means depends on what is being written rather than on the directive: in a direct build it pins a body against the sweep, and in an object it is written as a collection root for a linker to honour. It is documented with the sweep in Modules.

        keep    isr             ; reached by the interrupt vector, not by a call
isr:
        ei
        reti

Build and Output

DirectiveAliases
deviceName the machine
formatName the output format
opt#pragmaSet a setting mid-source
messagePrint during assembly

device

Syntax: device <machine>

device names the machine the program is assembled for. machine is one of zx16, zx48, zx128 and zxnext.

        device  zxnext

format

Syntax: format <fmt>

format names the output format. fmt is one of bin, hex, sna, nex and obj. See Output Formats.

        format  nex

opt

Syntax: opt <option> <value>

opt sets a setting from within the source, reaching the same settings as the command line. option is one of verbose, cspect, z80n, maxcodesize, case_insensitive, device and format; value is that setting’s value.

It takes one option per line, so a value may run to the end of it. 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.

        opt     z80n on
        opt     device zxnext

message

Syntax: message "<text>"

message prints text during assembly, substituting each braced expression in it. text is a string literal, and each expression inside braces must give 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).

If COUNT is 3 then:

        message "COUNT is {COUNT}"      ; COUNT is 3
        message "twice is {COUNT * 2}"  ; twice is 6

⚠ message takes no argument list. Written with a comma, message "COUNT is ", COUNT prints COUNT is and drops the rest with W1025.

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
virtual, endvirtualVirtual Origins
nex, sna, formatOutput Formats
section, externBuilding in Pieces

← Documentation