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 commas — assert_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
| 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 | Reserved 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 AAds 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
| Directive | Aliases | |
|---|---|---|
include "file" | Assemble another source file here | |
incbin "file" | 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. 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
| Directive | Aliases | |
|---|---|---|
if <expression> | #if | Assemble the block when the expression is not zero |
ifdef <name> | #ifdef | Assemble when the constant is defined |
ifndef <name> | #ifndef | Assemble 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 1Anything 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
endifOne 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 definedThe ifdef still evaluates false. A wrong one removes code, and the binary that results is smaller, valid, and wrong somewhere else entirely.
Macros
| Directive | Aliases | |
|---|---|---|
macro <name> [params] | Begin a macro | |
end | endm | End 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 ; fineThe 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 02enum <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 02struct <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
| Directive | Aliases | |
|---|---|---|
device <machine> | zx16, zx48, zx128, zxnext | |
format <fmt> | bin, hex, sna, nex | |
opt <option> <value> | #pragma | Set 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:
| 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).
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 |
nex, sna, format | Output Formats |