Msx Assembly 101: 08 Using z88dk
Recently I found z88dk, “the C and assembler development kit that comes ready out-of-the-box to create programs for over 100 z80-family (8080, 8085, gbz80, z80, z180, ez80_z80, KC160, Rabbit 2000, 3000, 4000, 5000) machines”. Given that z88dk can create binaries for AgonLight computer which I have now (albeit only in Z80 mode with 64k of memory accessible), I naturally got interested.
First attempt
Let us take the following assembler file zfirst.asm
:
CHPUT equ 00A2h
ld a,'A'
call CHPUT
Compiling it with zcc +msx -subtype=rom2 -o zfirst.rom zfirst.asm
yields an
error about undefined symbol _main
. Of course, z88dk links the executable
with crt0
which expects (as it would happen with C program) that the entry
point _main
is defined.
Adding _main
So let us add an entry point to zfirst.asm
:
CHPUT equ 00A2h
global _main
_main:
ld a,'A'
call CHPUT
When we complie now with zcc +msx -subtype=rom2 -o zfirst.rom zfirst.asm
we
see no errors and get zfirst.rom
created. Looks good, except that the ROM file
does not work.
The reason is that cartridge files are expected to be 8k or 16k in size, padded with zeroes, if needed, and our file is not.
We can easily fix the problem with dd
tool:
- First, create zero-filled empty 8k cartridge file:
dd if=/dev/zero of=out.rom bs=8k count=1
(if you are using a system withoutdd
or/dev/zero
, just skipdd
experiments, there is a better solution below). - Now, overwrite the beginning of
out.rom
withzfirst.rom
, not truncating original file:dd if=zfirst.rom of=out.rom conv=notrunc
.
Now we got 8k out.rom
file which works. The start address is also correctly
set to 0x4000
. To verify it, modify zfirst.asm
:
CHPUT equ 00A2h
global _main
_main:
jp next
next:
ld a,'A'
call CHPUT
Now compile it and disassemble zfirst.rom
: z88dk-dis zfirst.rom
. You will
be able to find jp $4047
right before our code for outputting A
character:
ld a,$41
and call $00a2
(it is easy to find the place by searching for
cd a2 00
). This means that our next
label is at 0x4047
, and the code was
actually relocated to start from 0x4000
.
Better way to make cartridge files
As z88dk author kindly pointed to me, it is possible to do 16k cartridge ROM
without jumping through the hoops with dd
. Just add -create-app
option to
the compilation command:
zcc +msx -subtype=rom2 -o zfirst.rom -create-app zfirst.asm
. This will produce
16k zfirst.rom
file, which can be run.
Still, if you want 8k file, your best bet is probably to play with dd
.
Anyway, look at the disassembly of zfirst.rom
(using z88dk-dis
) and you
notice that besides cartridge header (16 bytes in the very start) and our short
program it contains a lot of boilerplate. Logically, our next step is to try
compilation without linking in crt0
.
Not linking with crt
We will use the following zfirst.asm
:
CHPUT equ 00A2h
ld a,'A'
call CHPUT
halt
Notice halt
in the end: crt usually does it for us. Compile with
zcc +msx -subtype=rom2 -o zfirst.rom -create-app --no-crt zfirst.asm
,
get 16k zfirst.rom
file, run it and notice that it does not work. Quick look
at ROM file shows the reason: ROM header is missing.
Adding ROM header
ROM header looks like this:
AB<address of entry point in the least significant byte first format>00...0
and has 16 bytes altogether. Let us try to add it manually to zfirst.asm
:
db "A","B"
db 10h,40h
ds 12,0
CHPUT equ 00A2h
ld a,'A'
call CHPUT
halt
Now compilation produces ROM file with the proper header, and it seems to work.
Modify file like this:
db "A","B"
db 10h,40h
ds 12,0
CHPUT equ 00A2h
jp next
next:
ld a,'A'
call CHPUT
halt
and generated ROM no longer works. What happened? Disassembly shows the problem:
the code is no longer relocated to 0x4000
, and jp next
actually tries to
jump to 0x0013
!
Starting the code in the right place
Since the cartridge code is loaded to 0x4000
, let us inform the assembler
about it. Moreover, instead of hardcoding entry point to 0x4010
we will mark
it with a label and use this label in the header:
org 4000h
db "A","B"
dw start
ds 12,0
CHPUT equ 00A2h
halt
start:
jp next
next:
ld a,'A'
call CHPUT
halt
We specify in the header with dw start
the entry point of the program
(initial halt
is to make sure we really go to start
), and org 4000h
specifies the location of the code. Compile with
zcc +msx -subtype=rom2 -o zfirst.rom -create-app --no-crt zfirst.asm
and you
will get working zfirst.rom
. Its disassembly shows that:
- Third and fourth bytes in the header specify the address of
start
label (0x4011
). - Our code starts at
0x4010
withhalt
instruction but never gets there. - The
jp
instruction in the code is followed by the correct address0x4014
.
Bonus: putting it all together
In this section I will show how to use BSD (not GNU!) make to automate the
process. I suppose that the directory contains exactly one file with asm
extension. I am going to have two targets: clean
, which removes the built ROM,
and run
(executed by default) which builds ROM and runs it in an emulator.
Here comes Makefile
(highligting is badly broken because of #
so it is
switched off):
LIST_SOURCES = ls *.asm
SOURCE = $(LIST_SOURCES:sh)
.if $(SOURCE:[#]) > 1
.error Too many sources - $(SOURCE)
.endif
.if empty(SOURCE)
.error Cannot find the source
.endif
ROM = $(SOURCE:R).rom
run: $(ROM)
@-fmsx -scale 4 -skip 0 $(ROM) >/dev/null
.SUFFIXES: .rom
.asm.rom:
@zcc +msx -subtype=rom2 --no-crt -o $@ -create-app $<
clean:
@rm -f *.rom
.PHONY: clean run
I am not going into details here. The usage: put Makefile
and exactly one file
(containing org
directive and ROM header) into the same directory, and run
make
to build ROM with z88dk and run it in fmsx.