Even though the compiler has been under development for several months now, it goes without saying that (im)proper use of the output code could very easily wipe or corrupt the memory of your Z88 - you have been warned! - Back up all data before doing anything!
New for v0.52 of the compiler are the following features:
New for v0.49 of the compiler are the following features:
The Z88 Development Kit justifies it name because it is a set of tools which enable the development of programs for the Cambridge Computers Z88 Computer in either C or assembly language.
The kit is built around the Z80 Module Assembler (hereafter known as z80asm) from Interlogic and a Small C+ compiler, it is supplied with a set of standard libraries for most common functions which may used from either machine code or from C.
The code output by the compiler is of a form suitable for use from the BBC BASIC application, though in the future, development of ROMable Z88 applications will be feasible.
Even though this project is entitled the Z88 Development Kit, it should really be titled the Z80 Development Kit for it is quite suitable for generating code for other Z80 based computers, though the implementation of the various libraries required to achieve this is "left as a an exercise for the reader".
When finally released, the entire package will be freeware, though copyright will be reserved on the Z80 Module Assembler and the library files, however any software produced using it will be royalty free - just send myself and Gunther a copy!
I hope that this will be last pre-release (really!), I would really like to get this thing out into the open by the middle of March (by then it will have had a gestation period of 7 months!) - this doesn't mean that development will have stopped - it just gives me a time to stand back, and see what other people make of it
I've barely started and already I have a list of acknowledgments!
Special thanks must go to Norweb and the University of Manchester for providing me with the opportunity to rewrite and hence improve code due to powercuts/trip switch tripping. Cheers, guys, really appreciate it!
The Z88 Development Kit was, and always will be, "Made with Amiga."
Once you have unarchived the package (I assume you must have been able to do this since you are reading this document) you will find the following directory structure:
bin/ Executable directory
doc/ Place for doc files
examples/ A couple of little example programs
include/ Header files for the C compiler
lib/ Where the runtime libraries and startup code live
libsrc/ Source for the runtime libraries
src/
src/z80asm Source for the z80asm Module Assembler
src/copt Source for the z80 code optimiser
src/cpp Source for the DECUS cpp
src/dz80 Source for the dz80 disassembler
src/dzasm Source for the dzasm disassembler (not provided)
src/sccz80 Source for the C compiler
src/zcc Source for the front end
Obviously you will need to compile the source code (if you use an Amiga you'll find some precompiled binaries in the bin directory), to do this, go to the root of the tree and enter the key:
makefile.sh
And with a bit of luck everything will be built for you and placed in the
bin/ directory. Of course before you do this you may need to tweak it
slightly for your compiler.
This script also builds the libraries for you and places them in the lib/ directory.
NB. All include files for z80asm are now kept in {zcc}/lib - this simplifies installation very considerable, and creates a unified package.
I include the script startzcc which will set up the various assigns and paths that are needed for everything to work out of the box - you will have to change the definition of ZCC: so that it matches your hard drive set up though.
As of v0.44 of the kit, the frontend has changed considerably, with a bit of luck it will be more stable than ever and is certainly more flexible, so read on and find out how it has changed!
I've supplied a pre-processor (based on the Decus one ), however it's not particularly capable, so I would recommend that you get hold of the GNU one since it doesn't baulk at non-standard # directives.
The frontend now behaves much like a normal frontend to a compiler e.g. much as gcc,cc,dcc, even (help me please!) bcc etc etc, the concept of project files has be thrown out of the window, they were an early kludge which served their role for a while.
In order for the source code to be as machine independent as possible, zcc reads the location and names of executables from a config file. The location of the config file is given by the environmental variable ZCCFILE.
To invoke the frontend use the command:
zcc [flags] [files to be compiled/linked]
The files can be either C files (.c) , preprocessed C files(.i), compiled C files (.asm), optimised compiled file (.opt) or assembled files (.obj), any combination of them can be mixed together and the relevant processed done on them.
Processing of a file list is done on each file in turn (i.e. preprocess, compile, optimise, assemble) at the end all files may be linked into a single executable if desired.
The various flags are:
+[file] Name of config file (including full path and suffix). This option overrides the file give by ZCCFILE.
You shouldn't need this option unless you are compiling
for other Z80 machines.
-a Produce .asm file only
-c Do not link object files
-E Preprocess files only, leave output in .i file
-o[file] Specify output file for binary (default is a.out)
NB There's not a space between -o and the filename.
-On Optimize compiler output (to .opt file)
-v Verbose - echo commands as they are executed
-vn Don't be verbose
-l[name] Link in a library - supply the full path, if you want
to link in Gunther's standard.lib (which is needed for
graphics, use -ls
-lm Link in the generic Z80 maths library
-lmz Link in and generate code for OZ's maths routines
-s Generate .sym files when assembling/linking
-m Generate .map files when assembling/linking
-make-lib Shortcut to generate .obj files from library .c files
-z80-verb Allow z80asm to be verbose, this tends to generate a lot
of output to the screen so may not be desired.
-cleanup Remove all the files that the compiler has produced (
except for the output one) -no-cleanup stops cleanup
-show-errs Show any linking errors at the end.
The flags, -D, -I, -U are passed through to the preprocessor (see the manual page for your choice of precompiler for the significence of these.
I have added a couple of shortcuts to the library linking, if the -math-z88 flag is specified then the z88 maths library is automatically linked in. Similarly vice-versa, if you specify the flag -lmz, then the -math-z88 flag is supplied to the compiler. Should you wish to use the generic math library simply supply the flag -lm
Naturally, zcc allows the compiler options to be passed through.
The Compiler is based upon the Small C/Plus compiler which has long history but can be basically be attributed to in chronological order: Cain, Van Zandt, Hendrix and Ronald Yorston. This last person in particular developed the compiler very considerably beyond the original specification for Small C set down by Ron Cain. James R. Van Zandt modified it to include floating point using floating routines originally written by Neil Colvin.
I too have further extended the compiler and hopefully made it into a true cross compiler which can produce z80 code from any machine.
The compiler accepts a subset of Standard C, for more details about C see the ubiquitous K&R - "The C Programming Language", if you want details about the compiler try and find the now out of print "The Small-C Handbook" by James E Hendrix. Information on the original incarnation of the compiler is available on a Dr Dobbs CDROM, though at $50 it could be considered overpriced.
Small C+ supports a wide variety of datatypes which are similar to those in ANSI C, however it should be noted that the float type does not exist, it is represented instead by double.
Characters, ints and longs may be either signed or unsigned. Their maximum and minimum values are detailed in the table below. Pointers contain the addresses of data elements and are treated as unsigned ints when compared. NB. The size of pointers is likely to be userdefinable in the future:
| Type | Size | Min | Max |
| char | 1 byte | -128 | 127 |
| unsigned char | 1 byte | 0 | 255 |
| int | 2 bytes | -32768 | 32767 |
| unsigned int | 1 bytes | 0 | 65535 |
| long | 4 bytes | -2147483648 | 2147483647 |
| unsigned long | 4 bytes | 0 | 4294967296 |
The supported datatypes are detailed below:
char c; character char *c; pointer to character char **c; pointer to pointer to character char c(); function returning character char *c(); function returning pointer to character char c[3]; character array char *c[3]; array of pointers to character int i; 16 bit integer int *i; pointer to integer int **i; pointer to pointer to integer int i(); function returning integer int *i(); function returning pointer to integer int i[4]; integer array int *i[4]; array of pointers to integer int (*i)(); pointer to function returning integer long i; 32 bit integer long *i; pointer to long long **i; pointer to pointer to long long i(); function returning long long *i(); function returning pointer to long long i[4]; long array long *i[4]; array of pointers to long double d; 48 bit floating point double *d; pointer to double double **d; pointer to pointer to double double d(); function returning double double *d(); function returning pointer to double double d[5]; array of doubles double *d[5]; array of pointers to double struct st s; structure with tag st struct st *s; pointer to structure with tag st struct st **s; pointer to pointer to structure struct st *s(); function returning pointer to structure struct st s[5]; array of structures with tag st struct st *s[5]; array of pointers to structure union un u; union with tag un union un *u; pointer to union with tag un union un **u; pointer to pointer to union with tag un union un *u(); function returning pointer to union union un u[5]; array of union with tag un union un *u[5]; array of pointers to union with tag un
Hopefully longs will now be correctly handled - including all arithmetic routines. One area which I haven't addressed is the conversion to and from doubles - they might work if they are smaller than 32767/65535 (signed/unsigned), however they will work if the -math-z88 flag is supplied.
Extern and global static (as well as auto of course!) storage classes are supported. Also unsupported are multidimensional arrays and more complex types - if you can name one and provide a good portable example that cannot be represented using other (supported) types I'll think about giving you prize!
Structures are declared in the usual way but may not contain instances of themselves, but may contain pointers to instances of themselves. Passing structures to a function is not permitted i.e. the only legal way a structure can occur in a function argument is as a pointer.
Far pointers are now supported, although there is no library code which will support them, so any attempted compilation will fail.
You should run the compiler via the frontend however, even so, it's essential to know which flags zcc passed through to the compiler:
-unsigned Implicitly define all ints/longs/chars as unsigned
-stop-error Pause on an error
-// Accept C++ style // (Though these are usually
taken out by the preprocessor)
-math-z88 Use Z88 Maths routines
The following flags are accepted though they are effectively ignored at the moment.
-make-app Make Z88 Application
-far-pointers Enable far pointers passing in functions
This last option does in actual fact work and generate working code, however if specified at the moment the program will break due to the libraries being unaware of far pointers!
In this section I'll just raise a few of the more obscure aspects of the compiler
Rather pardoxically if the code hasn't been run through the optimizer the following code snippet:
function()
{
int j;
j++;
}
Generates more code than:
function()
{
int j;
j=j+1;
}
However when run through the optimizer both pieces of C result in the same
machine code being produced. One thing to note is that in no circumstances
use:
function()
{
int j;
j=j++;
}
Not that you would anyway, because that generates an awful lot more assembler
code.
Other tips include define arrays and chars first of all in a function and ints later. Loop counting ints which are defined last of all will (when the code is optimized) generate much faster and smaller code.
If you do define a global (static) variable (as opposed to just declaring its presence) please note that the variable is embedded within the code and could not be changed if the program were to be blown onto an EPROM. Also important to note is that global doubles can not be initialised.
It is possible to mix C and assembly in a single source file, simple enclose the assembler language with #asm/#endasm, from v0.40 the use of #pragma asm and #pragma endasm is also supported. However it isn't advised that you mix assembler and C unless you know very well what you are doing.
Possibly the most useful use of the #asm/#endasm would be to initialize global variables (though this can be done automatically), this could be used to easily define udg graphics for example:
extern char udg[8];
main()
{
/* Put some code here! */
}
#asm
; A very boring graphic!
.smc_udg defb 185,170,185,170,185,170,185,170
#endasm
Note the use of the smc_ prefix - this has been added to avoid label clashes with the library.
There is another method to call machine code from within C - the "asm function" this is an incredibly useful way of getting macros into z80asm, an example of how to do it is in the example/ directory, it's easier to read the example than to read me try to describe it(!)
As well as providing macros, asm() can be used to call machine code routines embedded with C expressions (caution!!). An example of using it this way follows:
main()
{
int j;
j=asm("call\tfish");
}
#asm
.fish
ld hl,1
ret
#endasm
Not perhaps the best example ever, but it illustrates its use - you can also return longs (kept in dehl) and chars (same as for ints)
There is another feature of this version of Small C which could potentially be of much use if you were combining an existing assembler and a C program. This is the so called external pointer data type, and I shall illustrate it's use with a few C examples and the hypothetical assembler code produced, let us assume that hl is the integer j.
extern int data(address); address=16 bit Z80 address
function()
{
int j;
j=data ld hl,(address)
j=&data ld hl,address
}
You can similarly do the same trick with chars and doubles. I believe this to be slightly "non-standard" but could prove invaluable for finding out system variables etc.
The optimizer supplied with the package is of the peephole variety - i.e. if it spots a a group of lines which it knows can be better represented in another way it will - it isn't (and never will) be a true optimizer and keep track of register usage and avoid repeated loading of a register with the same value.
From v0.46 of the compiler there are now two levels of optimization, for the best (smallest and fastest) code it is recommended that you compile with the -O2 option from zcc. On large source files it will take its time, but it is worth it - savings of over 10% in program size are common place, and execution time can decrease quite considerable. One program that I'm working on at the moment was 11984 bytes before optimization and 10131 bytes afterwards which is just a smidgen over 15%. The execution time decreased dramatically as a result.
Hopefully the rules supplied will not cause any software to fail (I haven't come across anything yet - and I have used them a lot), however if they do, then just mail me with the C source and the assembler output (raw and optimized) and I'll see what I can do to remedy the situation.
Included with in the package are two Z80 disassemblers, both have their uses at various times, though at the moment only one comes with documentation!
This is dzasm disassembler from Interlogic which is perfect for disassembling Z88 ROM images due to its full support of the Z80 module assembler in producing disassembly files with CALL_OZ pseudo opcodes. It's not so much a command line disassembly as a sort of disassembler environment. It's very powerful, but sadly at the moment comes without instructions or documentation. I only include in the Amiga binary, for other platforms grab the source code from Z88 Forever and compile it.
DZ80 is a very good disassembler originally written for M$DOG, but I successfully ported it over to the Amiga last August. I've modified it slightly to produce disassembly files which can be recompiled using z80asm (with the small addition of a MODULE xxx line). Details of the changes can be found in the dz80.readme file in the doc/ directory
Should you require the generic sources for dz80 then go to the Inkland website.
As previously noted the assembler and linker are combined in the z80asm Module Assembler from Interlogic. Using this program has numerous advantages over the traditional method of using a separate linker and assembler, briefly these are:
I could go on, but I won't - even I, a happy user of a generic, blindingly fast cross-assembler for the Amiga (za80) was converted once I saw what it could do. In my mind, it should become the standard cross-assembler for z80 developers.
I have endeavoured to put together a fairly comprehensive library of functions which cover most of the things that smallish C programs would want to do. There are of course many omissions and undoubtedly many mistakes, so if you find one, could you either endeavour to fix it and send me the corrected version or alternatively let me know and I'll find the time to repair it.
The listing of all of the functions in the header files is getting quite long so I shall only list the interesting ones. Those functions without a type return an int.
graphics.h
extern window(struct *window); extern plot(struct *pixels); extern unplot(struct *pixels); extern draw(struct *pixels); extern undraw(struct *pixels); extern drawr(struct *draw); extern undrawr(struct *draw); extern drawb(struct *pixels); extern undrawb(struct *pixels); extern lscroll(struct *pixels); extern rscroll(struct *pixels); extern clg(void); extern clga(struct *pixels); extern closegfx(struct *window) extern circle(struct pixels); extern uncircle(struct pixels);These function provide "stubs" for Gunther's standard.lib, for the meanings of the structures look at the actual header file. stdlib.h
extern csleep(int time extern char *getcwd(char *buffer, int len))
csleep will pause time centiseconds, this uses the Z88 OZ function os_dly.
getcwd will load the current directory into buffer up to a maximum len bytes.
malloc.hextern void *calloc(int number, int size_of_datatype); extern void *malloc(int bytes); extern free(char *); extern getfree(void); extern getlarge(void); extern initmem(int size);I've kludged around and come up with an extremely rudimentary malloc/free scheme for Small C. All memory is allocated out of character array! The routines actually come from a file I spotted on an MSX website a year or so ago, here's an example of how to use these routines.
#include/* * Define the size of the pool, HEAPSIZE is a macro for unsigned char [ ] */ #define HPSIZE 8192 HEAPSIZE(HPSIZE) main() { char *s; int size; initmem(HPSIZE); /* Initialise the heap - has to be done! */ s=malloc(100); if (s == 0 ) exit(0); free(s); size=getfree(); /* Get total free memory */ size=getlarge(); /* Get size of largest block */ }
Don't forget to link with -l{zcc/lib}/malloc
strings.h, ctype.h
From v0.46 I've added an enormous amount of new routines to these header files, best solution is if you just have a peep at them! Most of the new string.h functions are in C so they can be easily recompiled for long pointers.
WARNING! I've only tested a few of these functions (and corrected a few bugs!) so I can't guarantee their correct function, you may well have to delve into the libsrc/ directory to see whats up. Please let me know which ones you've used and whether they work or not!
Included within the package are a few C example programs, they're quite simple, but prove that the package does actually work!
world.c
Go on, take a guess, yup, the Z88DK version of the infamous Hello World program!
filetest.c
Pretty much as it suggest really, it just test a few of the library functions and displays the first 5 lines of a file on screen - make sure you change the filename to one on your Z88 though! If you don't you can test out the error checking!
gfx.c
A little test of the new graphics library - compile using zcc with the -ls option.
It's quite boring, but demonstrates the speed of the circle routine very well in addition to being a demonstration as to how to use some of the other graphics functions.
opt_test.c
Hmmm, this doesn't actually do anything useful! I wrote it to test out the code optimizer, as you can see it does a pretty good job, though obviously it doesn't say "This code is complete nonsense why are you bothering with it"!
gfx.c
A short example of using the map area from Small C+ - now..who's going to do a 3D wireframe game!
enigma.c
The original "big" program! This is a simple emulation of the German Enigma Machine used during WWII, the BASIC equivalent (enigma.bas) was the first program I wrote for the Z88 back in April!
This version is not quite as polished, but it works a lot quicker! What you type is translated into Enigma Code (yes it does work, it's not faking it!) instead of being echoed in English/German/Danish/Swedish/French etc. It only takes the standard 26 letters of the Roman Alphabet and automatically converts them to capitals. To stop it simply enter a full stop i.e. a "."
I have to admit I was quite astonished when this worked the first time after being compiled - it calls on quite a few library functions which hadn't been tested previously, but it does, so enjoy!
cube.c
Thanks to Dennis for forwarding this one to me - after a few tweaks for speed and accurate compilation we have the first vector graphic on the Z88! There appear to be a few problems when compiling this on a PC - I can't quite track them down, so a precompiled binary is supplied.
Unfortunately(!) if you compile this example for the Z88 math routines it appears to produce strange results, if anyone can track down this problem I'd be immensely grateful - I think I'm generating fp constants correctly, however I could be wrong!
dstar.c
A new demonstration program from v0.43 onwards. DStar is a conversion of a small TI86 game I found with assembler source, I converted it over to C and we have a new demo program, it's a very nice game and good fun to play as well - for instructions look at the source - there's a couple of pages of comments in there about it!
define.c
Not so much a demonstration, but an example of how to declare static strings for the best possible performance - I used this to debug the initialisation routines.
sorter.c
A short insertion sort routine from Joe Mackay - he took some code home from his course made a couple of minor adjustments and came up with this! - Thanks Joe!
rpn.c
This one's for you Garry! A reverse polish notation calculator. This was originally written for the GBDK, but a couple of minor changes later (documented) and it works on the Z88.
???/*.c
It's going to come soon, and it will blow your socks off - you've been warned! - This has been delayed due to work on the compiler!
Hopefully this list will first grow(!) and then shrink as I sort them out.
22.3.99
--->And still it bugs me, the floating point constants, I've now had it working on 4 operating systems on 4 different processors with 3 different compilers: AmigaOS (m68k; DICE,gcc,vbcc) , WarpOS (PPC; vbcc), Solaris 2.5 ([Whatever Sparc chip is in an Ultra]; gcc), Linux (i486; gcc) and I will be damned if I can get it barf and spit rubbish out at me on any of them, well certainly not of the kind of rubbish that some people are getting out, I am completely flumoxxed to say the least...
8.3.99
--->The supplied preprocessor is ANSI C and doesn't strip // style comments - solution don't use them or use the -// flag.
So, I guess you're here cos your code doesn't work? Have you checked it - I mean have you really checked it? Does it make sense, there are no wild pointers are there? Ah, where does it go wrong then? In a library function? Good, delve into the libsrc directory pull out the relevant file and look at it. Spotted a bug? Tell me about it and I'll put it right.
Oh, not there then, hmmm, if all else fails it sounds like a compiler shortfall then. First step is to look back at the C code, have you got any lines looking like:
[long]=[int]+[constant]L- the L on the end is important - remove the L it should work - the integer expression is automagically converted to a long type.
Whats's this Compiler Error Bug thing? -> well, it's a nice way of stopping a SEGV for a start! I'm afraid it's a bit of shortcoming in the compiler itself, you've tried to be clever and produce a bit of code along the lines of:
struct mystruct {
[type] data[n];
};
void myfunc(struct mystruct *ptr);
{
[type] *ptr2;
ptr2=&ptr->data[m];
}
Which causes the compiler to report back with a null pointer - not good, so I trap it, put the warning up and you can get on with life, I'll fix it when I sort far code out. I haven't come across a case where the code generated is bad, it's just there to warn you. You can get round it by using
ptr2=(&ptr->data)+m>/P>
The development history of the Z88DK is now found in the separate file history.html.
v0.49 saw a large number of goals diminish - prototyping brought about ANSI function definitions and hence rigorous type checking. This then brought about the setting up of stdin/out/err in the startup code due to pointer mismatching(!)
Future developments can of course be found of the private Small C+ website where I will upload the latest distributions on a fairly regular basis.
Development of the compiler was started on an Amiga A1200 with a 28MHz '020 processor and 6 meg of RAM (not exactly a speed machine). The beast has now been upgraded to a dual processor (PPC603e 160MHz and '040/25MHz) box with 34meg. If there's any demand for it then I can produce executables which will run on the PPC under either PowerUP or WarpOS (depending on which compiler I find). All development was done using DICE (v3.15/v3.16). Obscure SEGV bugs were tracked down using gdb on a Linux box (sadly not Amiga) - someone please send me a CD containing APUS!
Back to top