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 | |
|---|---|
org | Assemble from an address. |
align | Pad to the next multiple of a boundary. |
section | Open a run of lines a linker places as one. |
virtual | Assemble 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
retalign
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: ; $8100The 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
endmoduleA 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, 0virtual
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
endvirtualSee Virtual Origins, which documents it in full.
Data Definition
| Directive | Aliases | Emits |
|---|---|---|
db | defb, byte | Bytes, or a string’s characters |
dw | defw, word | 16-bit words, little-endian |
dz | A string plus a terminating zero | |
dh | hex | Raw bytes from a hex string |
ds | block | A run of one byte, filling the space it takes |
rs | Nothing — 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 alldb
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 W1009dw
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 00dz
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 00dh
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 BCds
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 AArs
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 longReach 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
| Directive | Aliases | |
|---|---|---|
include | Assemble another source file here | |
incbin | binary | Insert 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
| Directive | Aliases | |
|---|---|---|
if | #if | Assemble the block when the expression is not zero |
ifdef | #ifdef | Assemble when the constant is defined |
ifndef | #ifndef | Assemble when it is not |
else | #else | Open the other arm |
endif | #endif | Close 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
endifA 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
endififdef
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
endifMacros
| Directive | Aliases | |
|---|---|---|
macro | Begin a macro | |
end | endm | End it |
#define | Make 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 ; fineA 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
endmDepth 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 C9Repetition
| Directive | Aliases | |
|---|---|---|
rep | Assemble an instruction, or a block, several times | |
endr | end | Close 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
endrA 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
endrEnums 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 02If BASE is $40 then:
BASE = $40
enum Sprite BASE, 4
Player ; $40
Enemy ; $44
Bullet = BASE + $20 ; $60
Spare ; $64
endestruct
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 02A 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 03An 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 | |
|---|---|
global | Export the symbol to the labels and SLD files, and out of an object |
extern | Declare a symbol another object defines |
keep | Pin 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 filesextern
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
retiBuild and Output
| Directive | Aliases | |
|---|---|---|
device | Name the machine | |
format | Name the output format | |
opt | #pragma | Set a setting mid-source |
message | Print 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 zxnextformat
Syntax: format <fmt>
format names the output format. fmt is one of bin, hex, sna, nex and obj. See Output Formats.
format nexopt
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 zxnextmessage
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:
| Name | Value |
|---|---|
{_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
| Directives | Page |
|---|---|
!assert, !debug | Debugging a Build |
module, endmodule, keep | Modules |
rhai | Rhai Scripting |
test, endtest, init_reg, init_mem, init_cycle_limit, assert_reg, assert_mem, assert_checksum, assert_cycles | Testing |
bank, page, slot | Memory and Banking |
virtual, endvirtual | Virtual Origins |
nex, sna, format | Output Formats |
section, extern | Building in Pieces |