New CLIB Looking for Testers

Discussions about testing new clib
Post Reply
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

New CLIB Looking for Testers

Post by alvin »

There have been some substantial changes within z88dk over the past year.

Among them are:

* zcc has been modified to allow C compilation by either sccz80 or sdcc
* z80asm has been modified to introduce sections
* new clib has been committed that aims to implement a large subset of C11

The existing clib has also been growing but the aim of this sub-board is to test the new clib in combination with the new features in z80asm.

The new clib has been written from scratch, with only a small number of functions surviving unchanged from the existing clib. The number of functions is more than 400 consisting of somewhere around 25,000 lines of asm code. There is a lot of potential for errors in there and it is hoped that some user testing prior to release will help stamp out the most egregious ones. The forum was created because a few people did volunteer to help with some testing but anyone at large is welcome to help.

The main goals of the new clib:

* Implement as large a subset of the C11 standard as is reasonable on an 8-bit uP.

* Be the fastest and smallest z80 library among libraries with the most capability.

* Separate implementation from the C interface so that the library is easily used in assembler projects and with any z80 C compiler.

* Make use of the new sections feature of z80asm, the underlying linking assembler in z88dk. Sections permit the automatic generation of ROMable code and targetting of bankswitched memory systems.

* The entire library must be re-entrant to support the multithreading features demanded in C11.

* The library must be highly configurable at library build time and at compile time. This will allow the user to choose between fast or small implementations of library code and to choose among options for memory model and size of clib data structures from within C source.

* Supply an object oriented driver model that allows new targets to quickly gain access to tty-emulated input and output terminals as well as disk drivers.

* Support any number of attached devices and assign them to a global namespace.

* Portable to any z80 platform.

* Make the distinction between C for "big" machines and C for "small" machines disappear.


The new clib is still growing but the core is present and that is what we are looking to test. Here is an overview of current functionality:

* adt (Abstract Data Types) - a collection of container types modelled on C++ STL containers. Things like arrays, vectors, linked lists, priority queues, stacks and queues are present. The same api as in C++ is used.

* alloc (Dynamic Memory Allocation) - balloc (a block memory allocator), malloc (a heap allocator) and obstack (GNU's object stack allocator) are present. The malloc and obstack modules can manage any number of independent heaps/obstacks through a named heap/obstack api. This means, for example, you could have different heaps for different banking configurations. The malloc library is especially rich with aligned allocation present and realloc especially optimized to be fast as the C11 standard relies on it so much.

* compress - contains the tiny lz77 data decompressor written by Einar Saukas. This decompressor is also used by the crt to decompress the data segment into ram on startup when programs are compiled for the rom model.

* fcntl - posix subset and file descriptors. Drivers are object oriented and this subdirectory also contains abstract classes for easily implementing fully functional input and output terminals. An object oriented disk stack is also on the way.

* font - fzx by Andrew Owen and Einar Saukas is a proportional font printing system for 8-bit micros. Close to 100 fonts are available. I am currently rewriting this to be re-entrant so that it can be used in output terminals.

* math - the integer math library with options for choosing between small and fast versions. The fast version has further options for selecting things like loop unrolling and leading zero elimination. Floating point math has not been added yet! There is a z80 implementation in the existing z88dk clib that has not been migrated so if you need this, just ask and we can push up the priority.

* sound - currently contains 1-bit sound generators. Any system with a memory mapped or port-mapped speaker with on/off bit is supported. Sounds range from simple beeps to sound effects to polyphonic to a 3-voice synthesizer with drums. I did attempt to insert fast clock compensation for fast z80s but that will have to wait until z80asm gains a macro feature.

* stdio - just about everything you can think of. stdio can attach high level FILE i/o to any device that can be opened on a file descriptor. Memory streams are also supported, allowing FILE i/o to operate on a vector<char> in memory. It is planned to extend this scheme to bankswitched systems so that one way to access more than 64k will be treating the extra memory as a memstream.

* stdlib - most functions are present. qsort can be configured to use insertion sort, shellsort or quicksort. Quicksort can be configured to use middle item or random item as pivot and has options to use insertion sort for small partitions, randomize assignment to left or right intervals for equal items, etc.

* strings - all string functions are present plus many from bsd and gnu.

* z80 - processor related functions like precise delays measured in milliseconds or T states, a comprehensive im2 api, and access to i/o from C.


Testing functionality is a decidedly unsexy task. We're not expecting anyone to go so far as design unit tests but mainly to try things out from a user-level to root out the most egregious bugs. If your test programs can be used as examples, we can perhaps immortalize your efforts by including them in an examples directory.
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

A few notes about the file structure of the new clib:

The root of the source tree is located in z88dk/libsrc/_DEVELOPMENT
The header files are found in z88dk/include/_DEVELOPMENT
Specific target machines are described in z88dk/libsrc/_DEVELOPMENT/target

The only target machine currently is the ZX Spectrum.

If you are interested in testing on a different platform or with a general platform, start a new thread and we can try to walk you through adding another target.


1. CREATING A CRT

If you want to start compiling C programs now, skip ahead to "4. COMPILING PROGRAMS". Pre-made crts are available to start compiling programs right away.

For the zx target, the crts are made in z88dk/libsrc/_DEVELOPMENT/target/zx/startup. We're using m4 (a standard macro language) to generate the assembler crts from an m4 template. If you are using windows you will probably have to download a windows version (http://gnuwin32.sourceforge.net/packages/m4.htm); a convenient place to install the binary might be z88dk/bin.

The m4 template allows static instantiatiation of FILEs and devices on file descriptors. In other words, it allows us to decide what devices are connected to stdin, stdout, stderr and any other files we might want open when the program starts.

There is one m4 template currently being used for the zx target:

z88dk/libsrc/_DEVELOPMENT/target/zx/startup/zx_crt_1.m4

If you have a look at that file, you will see an area where the FILEs are statically instantiated and and an area where the crt start up code lies beginning at "_Start".

The startup code is written for programs started in RAM at a user-specified address which is the most common way programs are run on the zx spectrum. The zx spectrum also has a cartridge format where the program would begin at address 0 and run from ROM. Different startup code located at address 0 would have to be used in order to supply the various restarts and isr entry points defined by the z80 in the first page of memory. We'll be providing a different m4 macro for creating cartridges but for now we'll use the RAM model and we'll just concern ourselves with statically instantiating devices attached to stdin, stdout, stderr.

At the top of zx_crt_1.m4 you will see a list of drivers that can be statically instantiated:

Code: Select all

dnl## input terminals
dnl
dnl#include(../driver/terminal/zx_01_input_kbd_inkey.m4)dnl
dnl#include(../driver/terminal/zx_01_input_kbd_lastk.m4)dnl
dnl
dnl## output terminals
dnl
dnl#include(../driver/terminal/zx_01_output_char_32.m4)dnl
dnl#include(../driver/terminal/zx_01_output_char_32_tty_z88dk.m4)dnl
dnl#include(../driver/terminal/zx_01_output_char_64.m4)dnl
dnl#include(../driver/terminal/zx_01_output_char_64_tty_z88dk.m4)dnl
dnl#include(../driver/terminal/zx_01_output_fzx.m4)dnl
dnl#include(../driver/terminal/zx_01_output_fzx_tty_z88dk.m4)dnl
dnl
dnl## file dup
dnl
dnl#include(../../m4_file_dup.m4)dnl
dnl
dnl## empty fd slot
dnl
dnl#include(../../m4_file_absent.m4)dnl
These macros are located alongside the driver source code.

The input and output terminal macros can be found in:
z88dk/libsrc/_DEVELOPMENT/target/zx/driver/terminal

The file dup and empty slot macros are found in:
z88dk/libsrc/_DEVELOPMENT/target

Each macro definition documents the parameters it accepts while instantiating. As an example, the input terminal macro file "zx_01_input_kbd_inkey.m4" begins like this:

z88dk/libsrc/_DEVELOPMENT/target/zx/driver/terminal/zx_01_input_kbd_inkey.m4

Code: Select all

dnl############################################################
dnl##      ZX_01_INPUT_KBD_INKEY STATIC INSTANTIATOR         ##
dnl############################################################
dnl##                                                        ##
dnl## m4_zx_01_input_kbd_inkey(...)                          ##
dnl##                                                        ##
dnl## $1 = label attached to FILE or 0 if fd only            ##
dnl## $2 = label attached to output FDSTRUCT or 0 if none    ##
dnl## $3 = ioctl_flags (16 bits)                             ##
dnl## $4 = size of edit buffer attached to FDSTRUCT or 0     ##
dnl## $5 = key debounce in ms (0 default)                    ##
dnl## $6 = key start repeat delay in ms (500 default)        ##
dnl## $7 = key repeat period in ms (15 default)              ##
dnl##                                                        ##
dnl############################################################
This macro accepts seven parameters as described.


1a. Instantiating Drivers

An example will help explain how things are done.

The driver instantiations are done in the "INSTANTIATE DRIVERS" section of zx_crt_1.m4.

Code: Select all

dnl############################################################
dnl## INSTANTIATE DRIVERS #####################################
dnl############################################################

EXTERN _font_4x8_def

include(../../clib_instantiate_begin.m4)

include(../driver/terminal/zx_01_input_kbd_inkey.m4)dnl
m4_zx_01_input_kbd_inkey(_stdin, __i_fcntl_fdstruct_1, 0x03b0, 64, 1, 500, 15)dnl

include(../driver/terminal/zx_01_output_char_64.m4)dnl
m4_zx_01_output_char_64(_stdout, 0x2370, 0, 0, 0, 64, 0, 24, 0, _font_4x8_def - 256, 56, 0, 56)dnl

include(../../m4_file_dup.m4)dnl
m4_file_dup(_stderr, 0x80, __i_fcntl_fdstruct_1)dnl

include(../../clib_instantiate_end.m4)
The instantiation section must begin with "clib_instantiate_begin" and end with "clib_instantiate_end". The former macro initializes variables that count how many static files are created and the latter finalizes internal stdio data structures after instantiation is complete.

In between, drivers are instantiated in the order they appear in the file descriptor table. The first driver will be assigned to fd=0 (stdin by convention), the second to fd=1 (stdout by convention) and so on. While these instantiations occur, a local label is attached to the underlying FDSTRUCT representing the device. The label format is "__i_fcntl_fdstruct_n" where n = the file descriptor.

You can leave an unoccupied fd slot by instantiating "m4_file_absent". You can also create a duped fd by instantiating "m4_file_dup".

To instantiate into an fd slot you must first include the driver macro and then instantiate it by executing that macro (the macro names always begin with "m4_"). Many output terminals can be created using the same driver but you must always include the macro definition before every instantiation, even if it has been included already.

Let's have a look at each instantiation in the example above:

Code: Select all

include(../driver/terminal/zx_01_input_kbd_inkey.m4)dnl
m4_zx_01_input_kbd_inkey(_stdin, __i_fcntl_fdstruct_1, 0x03b0, 64, 1, 500, 15)dnl
The first instantiation will be assigned to fd=0. The driver is "zx_01_input_kbd_inkey" which is a keyboard scanner that implements console_01 features (a specific feature set that includes line editing) and things like key debounce and repeat. It uses the library function in_inkey() to scan the keyboard and is completely independent of the Sinclair ROM.

The parameters passed to the macro are:

Code: Select all

dnl## $1 = label attached to FILE or 0 if fd only            ##
dnl## $2 = label attached to output FDSTRUCT or 0 if none    ##
dnl## $3 = ioctl_flags (16 bits)                             ##
dnl## $4 = size of edit buffer attached to FDSTRUCT or 0     ##
dnl## $5 = key debounce in ms (0 default)                    ##
dnl## $6 = key start repeat delay in ms (500 default)        ##
dnl## $7 = key repeat period in ms (15 default)              ##

_stdin
create a FILE* attached to this fd and call it "stdin"
setting this parameter to zero would mean no FILE* would be created

__i_fcntl_fdstruct_1
attach this input terminal to the device on fd=1
while reading text from this terminal, output will be sent to fd=1
the device on fd=1 must understand console_01 messages to display properly

0x03b0
ioctl_flags documented elsewhere
enable cursor, enable crlf conversion, echo on, line mode, cook enable

64
size of the edit buffer.
if set to zero the terminal will degrade to char mode without editing capability
the edit buffer can be assigned or resized with an ioctl()

1
key debounce in milliseconds

500
delay before repeat starts in milliseconds

15
repeat period in milliseconds
The second instantiation is assigned fd=1:

Code: Select all

include(../driver/terminal/zx_01_output_char_64.m4)dnl
m4_zx_01_output_char_64(_stdout, 0x2370, 0, 0, 0, 64, 0, 24, 0, _font_4x8_def - 256, 56, 0, 56)dnl
zx_01_output_char_64 is a fixed-width font 64 column output terminal using 4x8 fonts. It understands console_01 messages and its underlying FDSTRUCT will be assigned the local label "__i_fcntl_fdstruct_1" -- you can see in the instantion above for fd=0 that the input terminal will send its text output to this driver.

The parameters:

Code: Select all

dnl## $1 = label attached to FILE or 0 if fd only            ##
dnl## $2 = ioctl_flags (16 bits)                             ##
dnl## $3 = cursor.x coordinate                               ##
dnl## $4 = cursor.y coordinate                               ##
dnl## $5 = window.x coordinate (must be even)                ##
dnl## $6 = window.width (must be even)                       ##
dnl## $7 = window.y coordinate                               ##
dnl## $8 = window.height                                     ##
dnl## $9 = scroll limit (number of scrolls until pause)      ##
dnl## $10 = font address                                     ##
dnl## $11 = foreground colour (text attribute)               ##
dnl## $12 = foreground mask (set bits = keep screen bits)    ##
dnl## $13 = background colour (cls attribute)                ##

_stdout
create a FILE* attached to this fd and call it "stdout"

0x2370
icotl_flags
enable signal bell, enable ascii bell, enable pause when window fills, cook mode on, enable crlf conversion

(0,0) = (x,y)
initial cursor coordinates with respect to top left corner of window

(0,64,0,24) = (window.x, window.width, window.y, window.height)
location and size of the terminal window

0
initial scroll limit; the number of scrolls performed before a pause

_font_4x8_def - 256
address of the 4x8 font to use.  This label has also been EXTERNed a few lines above so that it can be found in the library.  The address provided is the address of the bitmap corresponding to ascii code 0; most fonts only include bitmap definitions for character codes 32+ as is the case with this one so we provide an address 256 bytes prior.

56
foreground colour = text attribute (ink black on paper white)

0
text colour mask (set bits use the attribute bit on screen when writing text)

56
background colour = attribute to use when clearing window or scrolling.
The last instantiation is assigned fd=2:

Code: Select all

include(../../m4_file_dup.m4)dnl
m4_file_dup(_stderr, 0x80, __i_fcntl_fdstruct_1)dnl
file_dup creates a dup of another fd as if done by the dup() function. In this case we'll dup the device at label "__i_fcntl_fdstruct_1" (fd=1) to fd=2. This is how we'll create the stderr stream.

Code: Select all

dnl## $1 = label attached to FILE or 0 if fd only            ##
dnl## $2 = FILE.state_flags_0 (file type and mode)           ##
dnl## $3 = label assigned to FDSTRUCT being duped            ##

_stderr
create a FILE* attached to this fd and call it "stdout"

0x80
write only, stdio handles ungetc for eatc messages

__i_fcntl_fdstruct_1
dup the device on fd=1
Hopefully that is enough information to instantiate your own drivers. I will a more complicated example below just so you are aware of the kinds of things you can do:

Code: Select all

67         EXTERN _font_4x8_def
68         
69         include(../../clib_instantiate_begin.m4)
70         
71         include(../driver/terminal/zx_01_input_kbd_inkey.m4)dnl
72         m4_zx_01_input_kbd_inkey(_stdin, __i_fcntl_fdstruct_3, 0x02b0, 64, 0, 500, 15)dnl
73         
74         include(../driver/terminal/zx_01_output_char_64.m4)dnl
75         m4_zx_01_output_char_64(_stdout, 0x2370, 0, 0, 2, 44, 1, 18, 0, _font_4x8_def - 256, 0x0f, 0, 0x0f)dnl
76         
77         include(../../m4_file_dup.m4)dnl
78         m4_file_dup(_stderr, 0x80, __i_fcntl_fdstruct_1)dnl
79         
80         include(../driver/terminal/zx_01_output_char_32.m4)dnl
81         m4_zx_01_output_char_32(_edit, 0x2230, 0, 0, 1, 30, 20, 3, 0, 15360, 56, 0, 56)dnl
82         
83         include(../driver/terminal/zx_01_output_char_64.m4)dnl
84         m4_zx_01_output_char_64(_sidebar, 0x20b0, 0, 0, 48, 14, 1, 18, 0, _font_4x8_def - 256, 0x27, 0, 0x27)dnl
85         
86         include(../../clib_instantiate_end.m4)
Five drivers are instantiated to create three different output terminals and one input terminal. One thing to notice is that FILE* names "edit" and "sidebar" are assigned to the last two output terminals. In the C source you can refer to those FILE* by declaring them extern:

Code: Select all

extern FILE *edit;
extern FILE *sidebar;
Printing to those terminals can then be done using fprintf().

stdin, stdout, stderr are made visible when including stdio.h so the program does not have to explicitly do that.


1b. Creating the CRT

With the m4 file finished the next step is to create the actual crt.asm file.

Do this by invoking m4 on it:

m4 zx_crt_1.m4 > zx_crt_25.asm

There should be no errors. Make sure you choose a crt name that does not conflict with anything else.

If you take a look at the crt file you will see the drivers statically instantiated and you can read the comments to see if you provided the parameters you meant to.


1c. Activating the CRT

To make the crt available for compiling you will need to add it to:
z88dk/libsrc/_DEVELOPMENT/target/zx/zx_crt.asm

Simply add it to the end of the file with something like:

Code: Select all

IF startup = 25

   ; comments here
   
   INCLUDE "startup/zx_crt_25.asm"

ENDIF
You will be able to use this crt by specifying "-startup=25" on the compile line. If you would like to make it the default selection, edit the default selector code at the top of the file.


The next post will be about library customization.
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

2. LIBRARY CUSTOMIZATION

Each target can have its own library configuration. If the library is reconfigured, it must be re-built for the changes to take effect.


2a. CLIB Configuration

The library settings are stored in z88dk/libsrc/_DEVELOPMENT/target/zx/clib_cfg.asm

Code: Select all

; -------------------------------------------------------------
; -- multi-threading ------------------------------------------
; -------------------------------------------------------------

; Enables multi-threading features of the library.

defc __CLIB_OPT_MULTITHREAD = $00

A task switcher is not implemented yet but the library does implement the locks necessary for MT-safe code.  Set to zero to reduce the amount of locking code attached to the binary.

; -------------------------------------------------------------
; -- integer math options -------------------------------------
; -------------------------------------------------------------

defc __CLIB_OPT_IMATH = 0

; < 50 = select small integer math library
; > 50 = select fast integer math library

There are two versions of the integer math multiply and divide routines.  Both are written in assembler but one is written to be small and the other to be fast.

If you choose the fast version of the library there are further options:

; FAST INTEGER MATH LIBRARY OPTIONS

defc __CLIB_OPT_IMATH_FAST = $0a

You can enable loop unrolling and leading zero elimination for division and you can enable the same for multiplication.  Loop unrolling is expensive in terms of memory so unless you need a really fast integer multiply, steer clear of that option.

; BIT SHIFTING

defc __CLIB_OPT_IMATH_SELECT = $00

; bit 0 = $01 = choose fast arithmetic shift right operator
; bit 1 = $02 = choose fast logical shift right operator
; bit 2 = $04 = choose fast shift left operator

The fast bit shifters attempt to shift by entire bytes at a time before shifting by individual bits.

; -------------------------------------------------------------
; -- text to number conversion --------------------------------
; -------------------------------------------------------------

defc __CLIB_OPT_TXT2NUM = $04

; bit 0 = $01 = enable specialized binary conversion for integers
; bit 1 = $02 = enable specialized octal conversion for integers
; bit 2 = $04 = enable specialized decimal conversion for integers
; bit 3 = $08 = enable specialized hex conversion for integers
;
; bit 4 = $10 = enable specialized binary conversion for longs
; bit 5 = $20 = enable specialized octal conversion for longs
; bit 6 = $40 = enable specialized decimal conversion for longs
; bit 7 = $80 = enable specialized hex conversion for longs

This option allows you to instruct the library to use fast text to number converters.  If options are disabled, the library will perform the conversions using multiplications.

If you enable one of the specialized text to number converters you can also indicate whether the library should use a fast converter or a small converter:

defc __CLIB_OPT_TXT2NUM_SELECT = $00

; bit 0 = $01 = choose fast binary conversion
; bit 1 = $02 = choose fast octal conversion
; bit 2 = $04 = choose fast decimal conversion
; bit 3 = $08 = choose fast hex conversion

; -------------------------------------------------------------
; -- number to text conversion --------------------------------
; -------------------------------------------------------------

defc __CLIB_OPT_NUM2TXT = $00

; bit 0 = $01 = enable specialized binary conversion for integers
; bit 1 = $02 = enable specialized octal conversion for integers
; bit 2 = $04 = enable specialized decimal conversion for integers
; bit 3 = $08 = enable specialized hex conversion for integers
;
; bit 4 = $10 = enable specialized binary conversion for longs
; bit 5 = $20 = enable specialized octal conversion for longs
; bit 6 = $40 = enable specialized decimal conversion for longs
; bit 7 = $80 = enable specialized hex conversion for longs

This option allows you to instruct the library to use specialized number to text converters.  If options are disabled, the library will perform the conversions using divisions.

If you enable one of the specialized number to text converters you can also indicate whether the library should use a fast converter or a small converter:

defc __CLIB_OPT_NUM2TXT_SELECT = $00

; bit 0 = $01 = choose fast binary conversion
; bit 1 = $02 = choose fast octal conversion
; bit 2 = $04 = choose fast decimal conversion
; bit 3 = $08 = choose fast hex conversion

; -------------------------------------------------------------
; -- stdio options --------------------------------------------
; -------------------------------------------------------------

defc __CLIB_OPT_STDIO = $00

Set this to 1 if you want stdio to check the validity of FILEs before using them.  If "file" is invalid in code like this: 'fprintf(file, "Hello World")' a crash could occur.  With checking enabled, the library will instead return an error indication.

; -------------------------------------------------------------
; -- printf converter selection -------------------------------
; -------------------------------------------------------------

defc __CLIB_OPT_PRINTF = $ffffffff

; bit 0 =  $    01 = enable %d
; bit 1 =  $    02 = enable %u
; bit 2 =  $    04 = enable %x
; bit 3 =  $    08 = enable %X
; bit 4 =  $    10 = enable %o
; bit 5 =  $    20 = enable %n
; bit 6 =  $    40 = enable %i
; bit 7 =  $    80 = enable %p
; bit 8 =  $   100 = enable %B
; bit 9 =  $   200 = enable %s
; bit 10 = $   400 = enable %c
; bit 11 = $   800 = enable %I
; bit 12 = $  1000 = enable %ld
; bit 13 = $  2000 = enable %lu
; bit 14 = $  4000 = enable %lx
; bit 15 = $  8000 = enable %lX
; bit 16 = $ 10000 = enable %lo
; bit 17 = $ 20000 = enable %ln
; bit 18 = $ 40000 = enable %li
; bit 19 = $ 80000 = enable %lp
; bit 20 = $100000 = enable %lB

You can individually choose which printf converters are supported.  By removing unused converters you can reduce the size of the binary.  The non-standard converter "B" is for binary and "I" is for IPv4 dotted decimal IP address.

; -------------------------------------------------------------
; -- scanf converter selection --------------------------------
; -------------------------------------------------------------

defc __CLIB_OPT_SCANF = $ffffffff

; bit 0 =  $    01 = enable %d
; bit 1 =  $    02 = enable %u
; bit 2 =  $    04 = enable %x
; bit 3 =  $    08 = enable %x (duplicate)
; bit 4 =  $    10 = enable %o
; bit 5 =  $    20 = enable %n
; bit 6 =  $    40 = enable %i
; bit 7 =  $    80 = enable %p
; bit 8 =  $   100 = enable %B
; bit 9 =  $   200 = enable %s
; bit 10 = $   400 = enable %c
; bit 11 = $   800 = enable %I
; bit 12 = $  1000 = enable %ld
; bit 13 = $  2000 = enable %lu
; bit 14 = $  4000 = enable %lx
; bit 15 = $  8000 = enable %lx (duplicate)
; bit 16 = $ 10000 = enable %lo
; bit 17 = $ 20000 = enable %ln
; bit 18 = $ 40000 = enable %li
; bit 19 = $ 80000 = enable %lp
; bit 20 = $100000 = enable %lB
; bit 21 = $200000 = enable %[

Similarly you can individually choose which scanf converters are supported.  Both duplicate entries must be eliminated to remove the hex converter.  The non-standard converter "B" is for binary and "I" is for IPv4 dotted decimal IP address.

; -------------------------------------------------------------
; -- stdlib options -------------------------------------------
; -------------------------------------------------------------

You can choose the sorting algorithm used by qsort()

defc __CLIB_OPT_SORT = 1

; 0 = insertion sort
; 1 = shellsort
; 2 = quicksort

Quicksort has more options:

defc __CLIB_OPT_SORT_QSORT = $0c

; bit 10 = pivot selection
;          $00 = pivot is middle item
;          $01 = pivot is random item
; bit  2 = $04 = enable insertion sort for small partitions
; bit  3 = $08 = enable equal items distribution

; -------------------------------------------------------------
; -- error strings --------------------------------------------
; -------------------------------------------------------------

Error strings retrieved by strerr() or perror() can consume a lot of space in the binary.  You can choose how concise those strings are.

defc __CLIB_OPT_ERROR = $01

; bit 0 = $01 = enable error strings
; bit 1 = $02 = select verbose error strings

$01 are brief error strings that only identify the errno ("EINVAL", "EDOM", etc).  Verbose strings include a short sentence.  If you set this to zero the library will only distinguish between "OK" and "ERR".
Whatever options you choose, all the library functions are always available. If you choose the small math library, for example, labels are internally defined to point at the small math library routines. However the fully qualified names for both the fast and small math routines are always available.

Your (assembler) programs can always call the math routines directly by name rather than through the option labels.


2b. Target Specific Configuration

Some options specific to the target can be selected.

The configuration file for the zx target is found in z88dk/libsrc/_DEVELOPMENT/target/zx/clib_target_cfg.asm

Code: Select all

; Z80 CPU SPEED

defc __clock_freq   = 3500000          ; Hz

Defines the clock rate in MHz.  This is used by the sound generation code and the precise delay loop timers.

; Z80 CPU TYPE

defc __z80_cpu_info = $03

; bit 0 = $01 = if set indicates an nmos z80 (if unsure set it)
; bit 1 = $02 = allow undocumented instruction "sll r"

The nmos z80 contains a bug that does not allow reliable determination of the z80 interrupt state with the "ld a,i" instruction.  If this bit is set, the library will use a lengthier method to determine this information.

;--------------------------------------------------------------
;-- GAMES/SP1 -------------------------------------------------
;--------------------------------------------------------------

This section contains configuration information for the sp1 sprite library.

;--------------------------------------------------------------
;-- SOUND/BIT -------------------------------------------------
;--------------------------------------------------------------

Contains information indicating how 1-bit sound is generated.
2c. Building the Library

With your library configurations selected, the library must be re-built.

For testing purposes we are using a simple windows batch file to build the zx library.

Change directory to:

z88dk/libsrc/_DEVELOPMENT

and run:

makelib(.bat)

The library should build and install itself. All done!


next topic is COMPILING PROGRAMS
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

3. COMPILE TIME OPTIONS

You can specify some aspects of the C environment from within your C source with pragmas.

Here is a list:


#pragma output CRT_ORG_CODE = (ORG of code segment)
#pragma output CRT_ORG_DATA = (ORG of data segment)
#pragma output CRT_ORG_BSS = (ORG of bss segment)

#pragma output CRT_MODEL = (0 for ram model, 1 for rom model, 2 for compressed rom model)
#pragma output CRT_INITIALIZE_BSS = (force bss initialization on startup, 0 for false, 1 for true)

#pragma output REGISTER_SP = (initial address of stack)

#pragma output CRT_ENABLE_COMMANDLINE = (0/1 to parse command line)
#pragma output CRT_ENABLE_RESTART = (0/1 to cause the program to restart on exit)
#pragma output CRT_ENABLE_CLOSE = (0/1 to cause open files to be closed on exit)

#pragma output CLIB_MALLOC_HEAP_SIZE = (initial malloc heap size in bytes, 0 for none)
#pragma output CLIB_STDIO_HEAP_SIZE = (initial stdio heap size in bytes, used for fdstructs and buffering)
#pragma output CLIB_BALLOC_TABLE_SIZE = (number of memory queues, 0 for none)

#pragma output CLIB_FOPEN_MAX = (max number of open FILE)
#pragma output CLIB_OPEN_MAX = (size of fd table)

#pragma output CLIB_EXIT_STACK_SIZE = (number of functions that can be registered with atexit)
#pragma output CLIB_QUICKEXIT_STACK_SIZE = (number of functions that can be registered with quickexit)


Before getting into what these options mean, I'll describe how the final settings are determined when a program is compiled.

1. There is a clib-level default setting for all these options.

You will find those settings in:
z88dk/libsrc/_DEVELOPMENT/target/crt_defaults.inc

2. At the target level settings can optionally override some or all the clib defaults,

You will find the target settings in the target's directory:
z88dk/libsrc/_DEVELOPMENT/target/zx/crt_target_defaults.inc

3. Finally any setting can be overridden by the pragmas embedded in the C source.

For the zx spectrum target, the combination of 1 and 2 create these settings for each compile:

Code: Select all

#pragma output CRT_ORG_CODE = 32768
#pragma output CRT_ORG_DATA = 0 (attach to the end of CODE)
#pragma output CRT_ORG_BSS  = 0 (attach to the end of DATA)

#pragma output CRT_MODEL = 0 (ram model)
#pragma output CRT_INITIALIZE_BSS = 0 (only initialize bss for rom models)

#pragma output REGISTER_SP = 0 (do not change SP at startup)

#pragma output CRT_ENABLE_COMMANDLINE = 0 (no command line parsing)
#pragma output CRT_ENABLE_RESTART = 0 (return to basic after exit)
#pragma output CRT_ENABLE_CLOSE = 1 (close files on exit)

#pragma output CLIB_MALLOC_HEAP_SIZE = 1024
#pragma output CLIB_STDIO_HEAP_SIZE = 256
#pragma output CLIB_BALLOC_TABLE_SIZE = 0 (do not create a balloc table)

#pragma output CLIB_FOPEN_MAX = 0 (only create as many FILEs as demanded by static instantiation)
#pragma output CLIB_OPEN_MAX = 0 (only create an fd table large enough to hold statically instantiated)

#pragma output CLIB_EXIT_STACK_SIZE = 2
#pragma output CLIB_QUICKEXIT_STACK_SIZE = 0
If you would like to change those settings you can list the relevant pragmas in your main.c

The memory model being used (z88dk/libsrc/_DEVELOPMENT/target/crt_memory_model.inc) and included by the default crts separates the compiled program into three sections :- CODE, DATA and BSS. CODE is where executable code and read-only data is assigned. DATA is where variables with non-zero initial values are placed (and self-modifying code). BSS is where initally zeroed variables are stored. This separation matters if your target is a ROM because the ROM can only head read-only data.

With CRT_MODEL = 0, the ram model is selected. In the RAM model, the CODE, DATA, and BSS sections are placed together in that order one after the other to form a single binary blob. The startup code does not initialize the data section nor zero the bss section by default, meaning this blob can be run only once since after exit there is no way to restore the DATA and BSS sections to their initial values. This is actually the most common use case for 8-bit microcomputers. If you want to reduce the binary image size by chopping off the BSS secton you can do that by specifying "CRT_ORG_BSS = -1". The "-1" will cause the assembler to emit the BSS section to a separate binary file on output. If you do this you should alse set "CRT_INITIALIZE_BSS = 1" so that the startup will zero the BSS section before calling main.

With CRT_MODEL = 1, the rom model is selected. In the ROM model, you must specify a DATA address "CRT_ORG_DATA = xxxx". This will represent the first ram address available for data. If you don't specify a BSS address, BSS will append itself after the DATA section. Three output files will be generated: name_CODE.bin, name_DATA.bin and name_BSS.bin. To form the ROM image you must concatenate the DATA file to the end of the CODE file. Under windows it would be done with "copy /b name_CODE.bin+name_DATA.bin > rom_image.bin" On startup the crt will copy the stored DATA section in the ROM to its address in RAM and then it will zero the BSS section in RAM before calling main.

With CRT_MODEL = 2, the compressed rom model is selected. This works similarly to CRT_MODEL = 1 except a compressed version of the DATA section is placed in the rom image. After compilation you will have three output files once again: name_CODE.bin, name_DATA.bin, name_BSS.bin. The next step is to compress the DATA section using the zx7 compression tool: "zx7 name_DATA.bin name_DATA.zx7". Then form the rom image by appending the compressed DATA section to the CODE section: "copy /b name_CODE.bin+name_DATA.zx7 > rom_image.bin" On startup the crt will decompress the stored DATA section in the ROM to its address in RAM and then it will zero the BSS section in RAM before calling main.

The other pragmas should be self-explanatory.

Here is a simple main.c with pragmas included:

Code: Select all

// zcc +zx -vn -clib=new test.c -o test
// zcc +zx -vn -startup=4 -clib=new test.c -o test

#include <stdio.h>
#include <sys/ioctl.h>
#include <arch/spectrum.h>
#include <string.h>
#include <stdlib.h>

#pragma output CRT_CODE_ORG = 40000

#pragma output CLIB_MALLOC_HEAP_SIZE = 0
#pragma output CLIB_STDIO_HEAP_SIZE = 0

char buffer[100];  // max edit buffer size is 64

main()
{
   unsigned int i;
   
   zx_border(INK_WHITE);
   ioctl(1, IOCTL_OTERM_CLS);
   
   for (i=0; ; ++i)
   {
      printf("%5u %0#5x %I\n", i, i, i + ((unsigned long)(~i) << 16));
      
      if (rand() > 32100)
      {
         printf("\nEnter a message:\n");
         
         fflush(stdin);
         scanf("%[^\n]", buffer);
         
         printf("\nMessage received and reversed:\n%s\n\n", strrev(buffer));
      }
   }
}
next section is COMPILING PROGRAMS
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

4. COMPILING PROGRAMS

A sample compile line looks like this:

zcc +zx -vn -clib=new main.c -o main

You do not have to link any other libraries as all functions have been brought together into one library.

You can compile a project consisting of several source files:

zcc +zx -vn -clib=new part1.c part2.c part3.c -o main

You can compile a project containing c as well as asm files:

zcc +zx -vn -clib=new main.c asm_code.asm -o main

You can compile a pure asm project:

zcc +zx -vn -clib=new main.asm asm_code.asm -o main

In this last case you would have to provide an asm function "_main" that is the entry point for the crt. It would also be preferable to perhaps use z80asm directly and the asm-only version of the library to make purely asm projects.


The above compiles will use the default startup crt which is:

-startup=0
32 column output terminal covering full screen


You can choose among other available startups by adding "-startup=n" to the compile line:

-startup=1
32 column output terminal with tty_z88dk emulation covers full screen


-startup=4
64 column fixed width font output terminal covering full screen


-startup=5
64 column fixed width font output terminal with tty_z88dk emulation covers full screen


Part 1 showed how you could make your own crt and have it selected with a startup option you supply.


The tty_z88dk terminal emulation understands certain control sequences in the output byte stream. Things like cursor movement, scroll, tab, etc. The ascii codes they are assigned to will be described elsewhere but you can see them documented in the source file:

z88dk/libsrc/_DEVELOPMENT/target/zx/driver/terminal/zx_tty/zx_tty_z88dk_state_table.asm


Any assembly code you write must be assigned to a section in the memory model we are providing. If not, z80asm will think that the code belongs in its own separate output binary file (and maybe that's what you want!).

Several sections have been defined to hold user asm code:

section code_user
section rodata_user
section smc_user
section data_user
section bss_user


code_user
Where executable code can be placed

rodata_user
Where read-only data can be placed. This is data that can sit in a ROM.

smc_user
Where self-modifying code can be placed.

data_user
Where non-zero data can be placed.

bss_user
Where data that is initially zero can be placed,

By placing your code and data into the appropriate sections, the compiler will be able to generate ROM images as described in the last section.


I will leave a simple example here that shows how a C program and an asm program could interact through global variables.

Code: Select all

zcc +zx -vn -clib=new main.c myasm.asm -o main

== file: "main.c" ===

#include <stdio.h>

extern int Add_X_Y(void);

int X;
int Y;

main()
{
   X = 100;
   Y = 200;
   
   printf("The sum of %u and %u is %u\n", X, Y, Add_X_Y());
}

== file: "myasm.asm" ===

SECTION code_user

PUBLIC _Add_X_Y

EXTERN _X, _Y

_Add_X_Y:

   ld hl,(_X)
   ld de,(_Y)
   
   add hl,de
   ret
The next section will provide a link to library documentation
(once it is has been started! this may take a few weeks yet)
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

Test Compile:

Code: Select all

// zcc +zx -vn -clib=new test.c -o test
// zcc +zx -vn -startup=4 -clib=new test.c -o test

#include <stdio.h>
#include <sys/ioctl.h>
#include <arch/spectrum.h>
#include <string.h>
#include <stdlib.h>

char buffer[100];  // max edit buffer size is 64

main()
{
   unsigned int i;
   
   zx_border(INK_WHITE);
   ioctl(1, IOCTL_OTERM_CLS);
   
   for (i=0; ; ++i)
   {
      printf("%5u %0#5x %I\n", i, i, i + ((unsigned long)(~i) << 16));
      
      if (rand() > 32100)
      {
         printf("\nEnter a message:\n");
         
         fflush(stdin);
         scanf("%[^\n]", buffer);
         
         printf("\nMessage received and reversed:\n%s\n\n", strrev(buffer));
      }
   }
}
Save as "test.c"

zcc +zx -vn -clib=new test.c -o test

The following files will be generated:

Code: Select all

2014-12-22  08:35 PM                 0 test
2014-12-22  09:17 AM               726 test.c
2014-12-22  08:35 PM             9,545 test_CODE.bin
The zero size "test" file is a side-effect of how z80asm handles sections. If it is ever non-zero it means some code or data was not assigned to a section.

The "test_CODE.bin" file is the executable. Load it as a binary into your emulator at address 32768 and "RAND USR 32768" to run it.


You can add "-m" to the compile line to generate a symbol list. The symbol list is split into two halves. The first half lists labels in alphabetical order while the second half lists the same labels in address order.

Each section z80asm encounters also gets three labels: ASMHEAD_section_name, ASMTAIL_section_name and
ASMSIZE_section_name.


One more thing you can do, maybe just for curiosity sake, is to add "-Cl--split-bin" to the compile line. This will cause z80asm to output the contents of each section into its own binary file. At a glance you can see what code / data is using most of the memory in the final binary image.
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

EMBEDDED TARGET ADDED

An embedded target has been added. Typical compile line:

zcc +embedded -vn -clib=new test.c -o test

The embedded target is a no frills target without any drivers (and therefore no FILEs or fds instantiated) and intended for ROM at address 0.

Two new pragmas were introduced specifically to handle the z80 restarts and nmi handler routine:

Code: Select all

#pragma output CRT_ENABLE_RST = 0
#pragma output CRT_ENABLE_NMI = 0
CRT_ENABLE_RST consists of eight bit flags corresponding to the z80's rst entry points:

bit 1 = $02 = rst $08
bit 2 = $04 = rst $10
bit 3 = $08 = rst $18
bit 4 = $10 = rst $20
bit 5 = $20 = rst $28
bit 6 = $40 = rst $30
bit 7 = $80 = rst $38

A set bit indicates the program will supply a function named "_z80_rst_xxh" to handle the restart where xx="08", "10", etc (ie the rst location in hex).

Where there is a set bit, the crt inserts a jump to the handler function, otherwise a simple ret is inserted.

"_z80_rst_38h" is a special case as this restart is also the im 1 interrupt handling routine. If the bit is set, the crt inserts a "call _z80_rst_38h; ei; reti" and if reset it inserts "ei; reti".


CRT_ENABLE_NMI is a true / false flag

If true, the crt inserts "call _z80_nmi; retn" at address 0x66, otherwise it insert "retn".


The default memory model is compressed rom model and by default the DATA/BSS section is placed at address $8000.



ZX SPECTRUM IF 2 CARTRIDGE TARGET ADDED

-startup = 63 is an IF2 cartridge without any FILEs instantiated
-startup = 32 is the 32 column driver with inkey input (~ startup=0)
-startup = 33 is the 32 column driver with tty_z88dk and inkey input (~startup=1)
-startup = 36 is the 64 column driver with inkey input (~startup=4)
-startup = 37 is the 64 column driver with tty_z88dk and inkey input (~startup=5)

The same additional pragmas apply as for the embedded target. Since the target pragma defaults are geared for the zx spectrum ram model, you will have to add pragmas to your source to select rom model and locate the address of the data/bss section in order to generate an if 2 rom image.
slenkar
New member
Posts: 7
Joined: Sun Feb 03, 2013 7:01 pm

Post by slenkar »

I compiled z88dk ok
when I try to compile the spectrum examples I get this:
cp: cannot stat ?/usr/local/lib/z88dk//lib/spec_crt0.opt?: No such file or directory
Cannot copy crt0 file

the file path has 2 forward slashes

Also if the forward slash was removed the opt file is not actually in the folder
/usr/local/lib/z88dk//lib/
Last edited by slenkar on Thu Feb 12, 2015 3:24 pm, edited 1 time in total.
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

slenkar wrote:I compiled z88dk ok
when I try to compile the spectrum examples I get this:
cp: cannot stat ?/usr/local/lib/z88dk//lib/spec_crt0.opt?: No such file or directory
Cannot copy crt0 file

the file path has 2 forward slashes

Also if the forward slash was removed the opt file is not actually in the folder
/usr/local/lib/z88dk//lib/
Resolved in another forum that has since crashed unfortunately. There were some problems with the linux install from the nightly build.
Post Reply