Creation of an if2 cartridge for the zx target.
The test program is a simple implementation of a LISP interpreter. The source is lengthy so grab it from the clisp subdirectory in this zip file:
https://drive.google.com/open?id=0B6XhJ ... authuser=0
The first step was to simply compile the program for one of the if2 targets:
zcc +zx -vn -startup=40 -clib=new clisp.c -o clisp
"-startup=40" selects an if2 target with an fzx driver instantiated on standard output and an in_inkey() driver on stdin. The entire clib is independent of the spectrum rom so there are no concerns about having to have the zx rom paged in while the program runs. The crt for the if2 target has an org of 0 and fills in the typical z80 restarts (by default they just 'ret') which can be customized by your program. We don't need any restarts so we just take the default.
The currenty pre-made crts and their corresponding startup numbers are all enumerated in the target's main crt file. For the zx, that's:
http://z88dk.cvs.sourceforge.net/viewvc ... iew=markup
You can scroll down to "startup=40" to read the crt description.
The result of the compile is three files:
2015-03-25 11:23 PM 9,996 clisp_BSS.bin
2015-03-25 11:23 PM 20,101 clisp_CODE.bin
2015-03-25 11:23 PM 429 clisp_DATA.bin
The if2 target uses the compressed rom model. What this means is it generates separate CODE, DATA and BSS segments. The compressed rom model expects the DATA segment to be compressed and appended to the CODE segment to form the binary image. The BSS segment is mapped to ram and will be automatically initialized to zero by the crt. So the BSS.bin file is irrelevant other than to show you how much ram is taken by the bss variables (the initially zeroed variables). Likewise, the DATA segment contains the initialized variables and its size is how much space it occupies in RAM. Because the DATA segment contains non-zero data, the crt must copy this into RAM to properly initialize the program. That is why the rom image will contain first the CODE.bin file and then the compressed DATA.bin file appended to it. That image is what would be placed in the if2 cartridge.
However, we have a problem. The CODE binary is 20101 bytes and the DATA binary (before compression) is 429 bytes. IF2 cartridges are only 16k in size. We could try to scrape together 4k in savings somehow but there is another way to fit this program onto the cartridge that would be even easier.
We can compile the program to run in RAM, and then store a compressed version of the program in the cartridge that is decompressed into ram before execution.
In the interest of avoiding too much effort, let's try that.
Since we want to execute the program in ram we're not using an if2 target anymore. We'll compile assuming we're running the program in ram with a few pragmas added:
#pragma output CRT_MODEL = 1
#pragma output CRT_ORG_CODE = 28000
#pragma output CRT_ORG_DATA = 50000
#pragma output REGISTER_SP = 0
#pragma output CRT_ENABLE_RESTART = 1
#pragma output CLIB_EXIT_STACK_SIZE = 0
#pragma output CLIB_MALLOC_HEAP_SIZE = 0
#pragma output CLIB_STDIO_HEAP_SIZE = 0
We're choosing the uncompressed ROM model (CRT_MODEL = 1) which means the crt will initialize the DATA segment from an uncompressed stored copy appended to the CODE segment and it will zero the BSS segment at startup. These two things will allow the program to execute more than once since on execution it always initializes all C variables to their expected values before calling main. This clisp program does exit if a CTRL-D (end of file) is entered at the keyboard. The clib allows a CTRL-D to be generated with CAPS+SYM+D on the zx target. So we also enable "CRT_ENABLE_RESTART" which indicates to the crt that the C program does not exit to basic but instead restarts. Since the basic ROM is not going to be paged in (this is an if2 cart remember), trying to exit to basic would be disastrous and instead the CTRL-D exit option will cause the program to reset and restart.
We set the CODE org for where our program will be run once it is decompressed into RAM. Similarly we specify the location in RAM of the DATA segment. The BSS segment will immediately follow the DATA segment since it's org is not specified. These numbers were picked out of thin air and we will have to confirm those are viable addresses after the compile.
Other pragmas place the stack at the top of ram and eliminate the heaps since clisp.c neither performs an memory allocation or opens any files.
Next we compile using the typical zx ram model:
zcc +zx -vn -startup=8 -clib=new clisp.c -o clisp
2015-03-25 11:45 PM 9,998 clisp_BSS.bin
2015-03-25 11:45 PM 20,056 clisp_CODE.bin
2015-03-25 11:45 PM 429 clisp_DATA.bin
The rom model expects the final binary to consist of the CODE section with the DATA section appended (this is the uncompressed rom model so the DATA section is not compressed first).
copy /b clisp_CODE.bin+clisp_DATA.bin clisp_IMG.bin
(non windows users can use an equivalent cp)
2015-03-25 11:47 PM 20,485 clisp_IMG.bin
This is our executable image that needs to be loaded at address 28000 (CODE org). It will occupy addresses 28000-48484. The DATA segment in RAM was org'd at 50000 and will occupy addresses 50000-50428. The BSS segment will immediately follow occupying addresses 50429-60426. The stack will be located at the top of memory and grow downward toward the end of the BSS segment.
This is all satisfactory as no segments overlap and there is plenty of stack space. Had the memory map been unsatisfactory we could use the now-known sizes of the CODE, DATA and BSS segments to choose appropriate addresses for each.
z88dk comes with a compression tool written by Einar Saukus called zx7. It consists of a data compression tool run on the PC and a data decompression routine written in z80 and made available in the c library. So the magic moment has come -- can this image fit into a 16k cartridge if compressed?
zx7 clisp_IMG.bin
2015-03-25 11:54 PM 10,407 clisp_IMG.bin.zx7
And the answer is: with room to spare!
Next step is how do we deal with this compressed image? The idea is to write a short assembly stub that will execute from address 0 and decompress this image into ram. Here's the stub:
"clisp_if2.asm"
Code: Select all
; z80asm -b -ic:\z88dk\libsrc\_DEVELOPMENT\lib\zx_asm.lib clisp_if2.asm
org 0
EXTERN asm_dzx7_standard
main:
di
ld hl,clisp_image
ld de,28000
call asm_dzx7_standard
jp 28000
clisp_image:
BINARY "clisp_IMG.bin.zx7"
The program is simple. After disabling interrupts (this is necessary as we don't have an interrupt service routine nor are we sure what the interrupt enable state is when the cartridge is started), we decompress the stored compressed image to its start location at address 28000 and then run it.
This is an assembly program so we invoke z80asm directly to assemble it. We have to link to the zx library explicitly since that's not done for us automatically unless we use zcc to compile. The library being linked to is "zx_asm" which is the asm-only zx library. It contains all the same functions as the c library but without any c headers.
z80asm -b -ic:\z88dk\libsrc\_DEVELOPMENT\lib\zx_asm.lib clisp_if2.asm
(non-windows users will have to specify the correct path to the library)
2015-03-26 12:02 AM 10,489 clisp_if2.bin
2015-03-26 12:02 AM 2,124 clisp_if2.map
2015-03-26 12:02 AM 10,593 clisp_if2.obj
2015-03-26 12:02 AM 392 clisp_if2.sym
The assembler generated several files we don't care about. "clisp_if2.bin" is the binary we are interested in.
Next we create an if2 cartridge rom. An if2 cartridge rom is simply a 16k rom with the executable org'd at 0 inside it. To make this rom, appmake provides some helpful tools.
appmake +rom -s 16384 -o if2_blank.rom
This creates a 16k if2 blank:
2015-03-26 12:04 AM 16,384 if2_blank.rom
Next we insert our if2 image into the blank at address 0:
appmake +inject -b if2_blank.rom -i clisp_if2.bin -s 0 -o if2_clisp.rom
And that creates our final if2 cartridge image:
2015-03-26 12:07 AM 16,384 if2_clisp.rom
Many spectrum emulators recognize the ".rom" suffix as an if2 cartridge or rom replacement. Dragging and dropping the if2 image into many emulators will automatically execute it. Other emulators may require you to load it via a menu option.
Here is a short lisp program that can compute factorials:
Code: Select all
(defun fact (n)
(cond ((< n 1)
1)
(t
(* n (fact (- n 1))))))
(fact 6)
Also try entering "caps+sym+d" at the keyboard to cause the lisp interpretter to exit and the crt to restart the program.