Directives
Failure
Not all directives have their synopsis, explanation, and examples
Listing related
LIST, NOLIST
Example:
org 0
LIST
ld a, 0
; visible in the listing output
NOLIST
ld a, 1
; Not visible in the listing ouput
Memory related
ALIGN
Example:
org 0x1234
align 256
assert $ == 0x1300
align 256
assert $ == 0x1300
nop
align 128, 3
assert $ == 0x1300 + 128
CONFINED
Confine a memory area of 256 bytes maximum in such a way that it is always possible to navigate in the data by only modifying the low byte address (i.e INC L always works).
;;
; Confined directive is inspired by confine from rasm.
; I guess confined is more ergonomic has it does not requires to manually specify the size of the confined area
org 0x0000
CONFINED
assert $ == 0
defs 128, 0xff
ENDCONFINED
CONFINED
assert $ == 256
defs 200, 0xff
ENDCONFINED
CONFINED
assert $ == 256 + 200
defs 20, 0xff
ENDCONFINED
ORG
LIMIT
On the code space ($), not physical space ($$)
Example of code that assembles:
org 0x100
limit 0x102
print {hex}$ : db 1 ; written in 0x100
print {hex}$ : db 2 ; written in 0x101
print {hex}$ : db 3 ; written in 0x102
;print {hex}$ : db 4 ; written in 0x103 => must fail
Example of code that fails:
PHASE, DEPHASE
; https://k1.spdns.de/Develop/Projects/zasm/Documentation/z71.htm
org 0x100
label_100
nop
label_101
assert $$ == 0x101
assert $ == 0x101
phase 0x200
assert $$ == 0x101
assert $ == 0x200
label_200
nop
label_201
dephase
label_102
assert label_100 == 0x100
assert label_101 == 0x101
assert label_102 == 0x102
assert label_200 == 0x200
assert label_201 == 0x201
PROTECT
Synopsis:
Description: Mark the memory between START and STOP as protected against write. Assembler fails when writting there.On the code space ($), not physical space ($$)
Example:
RANGE, SECTION
Description: RANGE allows to define named portion of the memory, while SECTION allows to chose the portion of interest.
Example:
; sarcasm inspiration https://www.ecstaticlyrics.com/electronics/Z80/sarcasm/
range $0080, $3FFF, code
range $4000, $7FFF, data
section code
ld hl, message_1
call print_message
section data
message_1: db "This is message #1.", $00
section code
ld hl, message_2
call print_message
section data
message_2: db "This is message #2.", $00
section code
print_message:
ld a, (hl)
or a
ret z
call 0xbb5a
inc hl
jr print_message
assert section_start("data") == 0x4000
assert section_length("data") == 0x4000
assert section_used("data") == 40
BANK
Description:
When used with no argument, a bank corresponds to a memory area outside of the snapshot. All things read&write in memory are thus uncorrelated to the snapshot. Sections do not apply in a bank.
BANK page
is similar to WRITE DIRECT -1 -1 page
Synopsis:
Example:
; Set up a unique value in various banks
BANK 0xc0
org 0x4000 + 0
db 0xc0
BANK 0xc4
org 0x4000 + 1
db 0xc4
BANK 0xc5
org 0x4000 + 2
db 0xc5
BANK 0xc6
org 0x4000 + 3
db 0xc6
BANK 0xc7
org 0x4000 + 4
db 0xc7
BANKSET 0
assert memory(0x4000 + 0) == 0xC0
BANKSET 1
assert memory(0x4000 + 2) == 0xC5
assert memory(0x8000 + 3) == 0xC6
assert memory(0xC000 + 4) == 0xC7
BANKSET
Synopsis:
Example:
BANKSET 0
org 0x0000
db 1,2,3,4
org 0x4000
db 5,6,7,8
org 0x8000
db 9,10,11,12
org 0xc000
db 13, 14, 15, 16
BANKSET 1
org 0x0000
db 10,20,30,40
org 0x4000
db 50,60,70,80
org 0x8000
db 90,100,110,120
org 0xc000
db 130, 140, 150, 160
BANKSET 0
assert memory(0x0000) == 1
assert memory(0x4000) == 5
assert memory(0x8000) == 9
assert memory(0xc000) == 13
save "good_bankset_0_0.o", 0x0000, 4
save "good_bankset_0_1.o", 0x4000, 4
save "good_bankset_0_2.o", 0x8000, 4
save "good_bankset_0_3.o", 0xc000, 4
BANKSET 1
assert memory(0x0000) == 10
assert memory(0x4000) == 50
assert memory(0x8000) == 90
assert memory(0xc000) == 130
save "good_bankset_1_0.o", 0x0000, 4
save "good_bankset_1_1.o", 0x4000, 4
save "good_bankset_1_2.o", 0x8000, 4
save "good_bankset_1_3.o", 0xc000, 4
WRITE DIRECT
Description: WRITE DIRECT is a directive from Winape that we have not fully reproduced. It's two first arguments need to be -1.
Example:
WRITE DIRECT -1, -1, 0xc0
org 0x4000+0
db 0xc0
WRITE DIRECT -1, -1, 0xc4
org 0x4000+1
db 0xc4
WRITE DIRECT -1, -1, 0xc5
org 0x4000+2
db 0xc5
WRITE DIRECT -1, -1, 0xc6
org 0x4000+3
db 0xc6
WRITE DIRECT -1, -1, 0xc7
org 0x4000+4
db 0xc7
BANKSET 0
assert memory(0x4000 + 0) == 0xC0
BANKSET 1
assert memory(0x4000 + 2) == 0xC5
assert memory(0x8000 + 3) == 0xC6
assert memory(0xC000 + 4) == 0xC7
Labels related
=, SET
Description:
Assign an expression to a label. Assignement can be repeated several times.
Synopsis:
Example:
label = 1
label =3
.label=2
assert label == 3
label = 5
assert label == 5
label <<= 1
assert label == 10
EQU
Description: Assign an expression to a label. Assignement cannot be repeated several times.
Synopsis:
Example:
label = 1
label =3
.label=2
assert label == 3
label = 5
assert label == 5
label <<= 1
assert label == 10
MAP
MAP VALUE
define a map counter to the required value.
#` is used to assign the value to a given label and increment it of the appropriate amount.
Example:
; extract stolen here https://github.com/GuillianSeed/MetalGear/blob/master/Variables.asm#L10
map #c000
GameStatus: # 1
GameSubstatus: # 1
ControlConfig: # 1
; Bit6: 1=Enable music/Player control
TickCounter: # 1
WaitCounter: # 1
TickInProgress: # 1
ControlsTrigger: # 1
; 5 = Fire2 / M, 4 = Fire / Space, 3 = Right, 2 = Left, 1 = Down, 0 = Up
ControlsHold: # 1
; 5 = Fire2 / M, 4 = Fire / Space, 3 = Right, 2 = Left, 1 = Down, 0 = Up
Pause_1_F5_2: # 1
TutorialStatus: # 1
DemolHoldTime: # 1
UnusedVar1: # 1
DemoPlayId: # 1
DemoDataPointer: # 2
; Pointer to presaved demo controls
SprShuffleOffset: # 1
assert GameStatus = 0xc000
assert GameSubstatus = (0xc000 + 1)
assert ControlConfig = (0xc000 + 1 + 1)
assert TickCounter = (0xc000 + 1 + 1+1)
assert DemolHoldTime = (0xc000 + 10)
assert SprShuffleOffset = (0xc000 + 15)
SETN, NEXT
MAP
directive is probably easier to use
Example:
; http://www.aspisys.com/asm11man.htm
org 0x100
data set $
assert data == 0x100
data1 setn data ; data1 could be modified
data2 next data, 2 ; data2 cannot be modified
data3 next data
assert data1 == 0x100
assert data2 == 0x101
assert data3 == 0x103
assert data == 0x104
UNDEF
Example:
my_label = 1
ifndef my_label
fail "my_label must exist"
endif
undef my_label
ifdef my_label
fail "my_label must not exist"
endif
Data related
BYTE, TEXT, DB, DEFB, DM, DEFM
Example:
; defb tests
org 0x200
defb 1, 2, 3, 4
defb "hello", ' ', "world"
defb $, $ ; should generate 2 different values
db "Hello world", 0
WORD, DW, DEFW
DEFS
Example:
STR
Description: STR encodes string in AMSDOS format (i.e., adds 0x80 to the last char) and stores its bytes in memory.
Example:
org 0x1000
defb "hell"
defb 'o' + 0x80
org 0x2000
str "hello"
org 0x3000
db "Next one will be more complex"
db " \" et voila"
db " \" et voila"
db "\" et voila"
assert memory(0x1000) == memory(0x2000)
assert memory(0x1001) == memory(0x2001)
assert memory(0x1002) == memory(0x2002)
assert memory(0x1003) == memory(0x2003)
CHARSET
Example:
org 0x100
charset "abcdefghijklmnopqrstuvwxyz", 0
charset "AB", 100
db "aA"
ASSERT memory(0x100) == 0x00
ASSERT memory(0x101) == 100
org 0x200
charset
db "aA"
ASSERT memory(0x200) == 'a'
ASSERT memory(0x201) == 'A'
Conditional directives
IF, IFNOT
IFDEF, IFNDEF
Example:
IFUSED
Example:
Nested conditions
Conditions can be nested.
Example:
org 0x100
if 0 == 1
fail "not reachable"
else ifdef toto
fail "not reachable"
else ifndef toto
print "reached"
db 1
else
fail "not reachable"
endif
SWITCH, ENDSWITCH
Example:
org 0x100
switch 3
; one comment
case 1
db 1
break
case 3
db 3
; another comment
case 4
db 4
break
case 5
db 5
default
db 6
endswitch
switch 5
case 1
db 1
break
case 3
db 3
case 4
db 4
break
case 5
db 5
default
db 6
endswitch
Code duplication directives
FOR
Example:
; Takes inspiration from BRASS assembler
for count, 0, 10, 3
db {count}
endfor
for x, 0, 3
for y, 0, 3
db {x}*4 + {y}
fend
endfor
db 0
db 3
db 6
db 9
db 0*4 + 0
db 0*4 + 1
db 0*4 + 2
db 0*4 + 3
db 1*4 + 0
db 1*4 + 1
db 1*4 + 2
db 1*4 + 3
db 2*4 + 0
db 2*4 + 1
db 2*4 + 2
db 2*4 + 3
db 3*4 + 0
db 3*4 + 1
db 3*4 + 2
db 3*4 + 3
WHILE
REPEAT
REPEAT AMOUNT [, COUNTER [, START]] INNER LISTING REND
start
repeat 3, count
incbin 'AZERTY{{count}}.TXT'
rend
assert char(memory(start+0)) == 'A'
assert char(memory(start+1)) == 'Z'
assert char(memory(start+2)) == 'E'
assert char(memory(start+3)) == 'R'
assert char(memory(start+4)) == 'T'
assert char(memory(start+5)) == 'Y'
assert char(memory(start+6)) == 'U'
assert char(memory(start+7)) == 'I'
assert char(memory(start+8)) == 'O'
assert char(memory(start+9)) == 'P'
assert char(memory(start+10)) == 'Q'
assert char(memory(start+11)) == 'S'
assert char(memory(start+12)) == 'D'
assert char(memory(start+13)) == 'F'
assert char(memory(start+14)) == 'G'
assert char(memory(start+15)) == 'H'
assert char(memory(start+16)) == 'J'
assert char(memory(start+17)) == 'K'
assert char(memory(start+18)) == 'L'
assert char(memory(start+19)) == 'M'
assert char(memory(start+20)) == 'W'
assert char(memory(start+21)) == 'X'
assert char(memory(start+22)) == 'C'
assert char(memory(start+23)) == 'V'
assert char(memory(start+24)) == 'B'
assert char(memory(start+25)) == 'N'
ITERATE
The expression $i$ is evaluated after having generated the code of expression $i-1$. Take that into account if expressions use $.
Example:
; Glass inspiration http://www.grauw.nl/projects/glass/
iterate value, 1, 2, 10
add {value}
jr nz, @no_inc
inc c
@no_inc
call do_stuff
iend
iterate value in [11, 12, 110]
add {value}
jr nz, @no_inc
inc c
@no_inc
call do_stuff
iend
do_stuff
ret
add 1
jr nz, no_inc1
inc c
no_inc1
call do_stuff
add 2
jr nz, no_inc2
inc c
no_inc2
call do_stuff
add 10
jr nz, no_inc3
inc c
no_inc3
call do_stuff
add 11
jr nz, no_inc4
inc c
no_inc4
call do_stuff
add 12
jr nz, no_inc5
inc c
no_inc5
call do_stuff
add 110
jr nz, no_inc6
inc c
no_inc6
call do_stuff
do_stuff
ret
Code and data generation directives
MACRO
Example of standard macro:
; rasm inspired
macro LDIXREG register,dep
if {dep}<-128 || {dep}>127
push BC
ld BC,{dep}
add IX,BC
ld (IX+0),{register}
pop BC
else
ld (IX+{dep}),{register}
endif
mend
LDIXREG H,200
LDIXREG L,32
Example of macro using raw arguments:
macro BUILD_LABEL r#label
{label}_first
endm
BUILD_LABEL "HERE"
BUILD_LABEL "THERE"
ifndef HERE_first
fail "macro error"
endif
ifndef THERE_first
fail "macro error"
endif
macro BUILD_CODE r#code
{code}
endm
START_CODE1
BUILD_CODE "xor a"
BUILD_CODE "ld hl, 0xc9fb : ld (0x38), hl"
END_CODE1
START_CODE2
xor a
ld hl, 0xc9fb : ld (0x38), hl
END_CODE2
assert END_CODE2 - START_CODE2 == END_CODE1 - START_CODE1
assert END_CODE2 - START_CODE2 == 7
assert memory(START_CODE1) == memory(START_CODE2)
assert memory(START_CODE1+1) == memory(START_CODE2+1)
assert memory(START_CODE1+2) == memory(START_CODE2+2)
assert memory(START_CODE1+3) == memory(START_CODE2+3)
assert memory(START_CODE1+4) == memory(START_CODE2+4)
assert memory(START_CODE1+5) == memory(START_CODE2+5)
assert memory(START_CODE1+6) == memory(START_CODE2+6)
STRUCT
Description:
Structures allow to defined data blocs with semantic.
In practice, they replace bunches of DEFB
, DEFW
directives and enforce checks at assembling (you cannot add more data than expected or forget some).
If a label is used before the use of a struct, it is necessary to postfix it by :.
Otherwise the assembler thinks the label is a macro or structure call.
Synopsis
STRUCT <name>
<filed1> DB|DW|STR|<other struct> [<value>]
...
<filedn> DB|DW|<other struct> [<value>]
ENDSTRUCT
[<label>:] <name> <arg1>, ... , <argn>
Standard example:
struct color
r db 1
g db 2
b db 3
endstruct
struct point
x db 4
y db 5
endstruct
col0: color (void)
pt0: point (void)
col1: color 'a', 'b', 'c'
pt1: point 'd', 'e'
struct colored_point
col color 10, 20, 30
pt point 10, 20
endstruct
colored_point (void)
Example using default values:
;; Define a 3 fields structure
struct point
xx db 4
yy db 5
zz db 6
endstruct
assert point == 3
assert point.xx == 0
assert point.yy == 1
assert point.zz == 2
point 1, 2 , 3
point ,,8
point 9
; force values
; : after label name allows to disambiguate parser that does not try to check if label is a macro (less errors/faster)
my_point1: point 1, 2, 3
; use all default values
my_point2: point (void)
; use default at the end
my_point3: point 1
; use default at the beginning
my_point4: point ,,1
p1: point 1, 2 , 3
p2: point ,,8
p3: point 9
struct triangle
p1 point 1, 2 , 3
p2 point ,,8
p3 point 9 ; third point
endstruct
assert triangle == 9
assert triangle.p1 == 0
assert triangle.p2 == 3
assert triangle.p3 == 6
my_triangle2: triangle [1, 2, 3], [4, 5, 6], [7, 8 , 9]
if 0
my_triangle1: triangle
my_triangle2: triangle [1, 2, 3], , [7, 8 , 9]
endif
Data loading and transformation directives
Filenames are stored in a string. These string can do expansion of formulas embedded in {}.
basm embeds some files in its executable, they are access under the name "inner://" :
LZAPU, LZ48, LZ49
Example:
org 0x100
ld hl, CS_START
ld de, 0xc000
call aplib.depack
jp $
CS_START
LZAPU
INNER_START
defs 100
INNER_STOP
LZCLOSE
CS_STOP
assert INNER_STOP - INNER_START == 100
assert CS_STOP - CS_START < 100
include "inner://unaplib.asm" namespace "aplib"
INCBIN, BINCLUDE
INCBIN|BINCLUDE "fname" [[, SKIP], AMOUNT]
Fname can be build with variables.
Limitations:
- File is loaded fully in memory before being sliced depending on arguments.
Example:
here
incbin "AZERTY.TXT", 2, 3
there
assert peek(here) == 'E'
assert peek(here+1) == 'R'
assert there-here == 3
AZERTY.TXT
containing the text AZERTYUIOPQSDFGHJKLMWXCVBN
.
INCLUDE, READ
INCLUDE|READ [ONCE] "<fname>" [AS|MODULE|NAMESPACE "<module>"]
Fname can be build with variables.
Example with once:
org 0x4000
SIZE1_start
include once "include_once.asm"
SIZE1_stop
SIZE2_start
include once "include_once.asm"
SIZE2_stop
assert (SIZE1_stop - SIZE1_start) != 0
assert (SIZE2_stop - SIZE2_start) == 0
Example with namespace:
include "good_labels.asm" namespace "good"
ifndef good.outer1
fail "good.outer1 is undefined"
endif
ifdef outer1
fail "outer1 is defined"
endif
ifndef good.outer2.inner1
fail "good.outer2.inner1 is undedined"
endif
Files prefixed by inner://
are embedded by BASM
.
Example:
include "inner://opcodes_first_byte.asm"
org 0x4000
db opcode_inc_l
inc l
assert memory(0x4000) == memory(0x4001)
In case of conditional assembling, inclusion are only done in the executed branch. This code always assemble as it never includes 'unknonw' file.
Data saving and export
EXPORT, NOEXPORT
Example:
SAVE, WRITE
SAVE "<fname>", [[[START], [SIZE]], AMSDOS|BASIC|TAPE]
SAVE "<fname>", START, SIZE, DSK, "<fname.dsk>" [, SIDE]
SAVE "<fname>", START, SIZE, HFE, "<fname.hfe>" [, SIDE]
SAVE "<fname>", START, SIZE, DISC, "<fname.hfe>"|"<dname.dsk>" [, SIDE]
Unimplemented
TAPE option is not coded. Other options are not intensively tested
Example:
org 0x4000
run $
FIRST_ADDRESS
ld hl, txt
loop
ld a, (hl)
or a
jp z, $
push hl
call 0xbb5a
pop hl
inc hl
jp loop
txt
.start
defb "Hello World!"
defb 0
.stop
LAST_ADDRESS
save "good_save_whole_inner.bin" ; Save binary without header
save "hello.bin", FIRST_ADDRESS, LAST_ADDRESS-FIRST_ADDRESS, AMSDOS ; Save a binary with header
save "hello.bin", FIRST_ADDRESS, LAST_ADDRESS-FIRST_ADDRESS, DSK, "hello.dsk" ; Save binary with header INSIDE a dsk
if BASM_FEATURE_HFE
save "hello.bin", FIRST_ADDRESS, LAST_ADDRESS-FIRST_ADDRESS, HFE, "hello.hfe" ; Save binary with header INSIDE a hfe file
endif
save "good_save_txt.bin", txt.start, (txt.stop - txt.start) ; save text without header
; cmd line to generate the binary with header
; basm good_save.asm --binary -o run.bin
; cmd line to put it in a dsk
; dskmanager test.dsk format --format data42
; dskmanager test.dsk add run.bin
Debug directives
ASSERT
Example:
Amstrad CPC related directives
TICKER
Description: Compute the execution duration of a block of code
Synopsys:
Example 1:
; http://mads.atari8.info/mads_eng.html
TICKER START count
WAITNOPS 3
TICKER STOP
assert count == 3
TICKER START count2
nop
TICKER STOP
assert count2 == 1
Example 2:
TICKER START duration_varying_code
xor a
ld b, 1
TICKER STOP
assert duration_varying_code == 1 + 2
UNDEF duration_varying_code
TICKER START duration_varying_code
xor a
TICKER STOP
assert duration_varying_code == 1
UNDEF duration_varying_code
TICKER START duration_varying_code
TICKER STOP
assert duration_varying_code == 0
UNDEF duration_varying_code
assert duration(xor a) == 1
;assert duration(xor a : xor a) == 2 ; Does not compile yet Could be a good idea
TICKER START duration_varying_code
WAITNOPS 64
TICKER STOP
assert duration_varying_code == 64
UNDEF duration_varying_code
TICKER START duration_stable_code
TICKER START duration_varying_code
out (c), c
TICKER STOP
WAITNOPS 64 - duration_varying_code
TICKER STOP
assert duration_stable_code == 64
UNDEF duration_varying_code
MACRO BUILD_STABLE_CODE duration, r#code
TICKER START .my_count
{code}
TICKER STOP
ASSERT {duration} >= .my_count
WAITNOPS {duration}-.my_count
IFDEF DEBUG_EXPECTED_DURATION
ASSERT .my_count == DEBUG_EXPECTED_DURATION
ENDIF
UNDEF .my_count
ENDM
DEBUG_EXPECTED_DURATION = 2
BUILD_STABLE_CODE 64, "xor a : xor a"
WAITNOPS
Generate a list of instructions that do not modify any registers or memory but is executed with the expected amount of nops. (Currently it is synonym of NOP, but as soon as someone wants to provide clever rules to use less bytes, I'll implement them)
LOCOMOTIVE
LOCOMOTIVE start
10 REM Basic loader of binary exec
20 REM yeah !!
30 call {start}
ENDLOCOMOTIVE
start
ld hl, txt
.loop
ld a, (hl)
or a : jr z, .end
call #bb5a
inc hl
jr .loop
.end
jp $
txt
db "Hello world", 0
print "LOADER START IN ", {hex}start
save "LOADER.BAS",,,BASIC