Bulkding banked code questions

If you like transforming your statements into code, this is the place for you

Moderator: Programming Moderators

Bagpuss
Posts: 27
Joined: Tue Jun 06, 2017 9:13 pm

Bulkding banked code questions

Post by Bagpuss » Sun May 13, 2018 1:19 pm

Hello,
Getting there now but I've run out of memory which is a bit of a sod.
Been looking at banked code and trying to decipher what to.

What I've done so far is move low frequency code (i.e. not often called) that takes a lot of memory into banks 06_L and 06_H, so I have a display messages in 6L and a sprite loader in 6H

I've been compiling as follows:

Code: Select all

zcc +zxn -c -clib=new --c-code-in-asm --list  --codesegBANK_3_L --constsegBANK_3_L @zproject_03_L.lst
zcc +zxn -c -clib=new --c-code-in-asm --list  --codesegBANK_3_H --constsegBANK_3_H @zproject_03_H.lst
zcc +zxn -c -clib=new --c-code-in-asm --list @zprojectMain.lst -Cz"--clean" -o nextRaiders
zcc +zxn -o nextRaiders -clib=new  -lzxnext_layer2 -lzxnext_sprite  messages.o sprites.o nextRaiders.o
appmake +zxn --sna --sna-clean --org 32768--sna-clean -b nextraiders
From what I have read using zcc to compile banked c code isn't the way to go.

Its building 128k images to getting nearer to where I need to be. As part of the development I've removed all mallocs and presized variables. All variables are created as globals rather than in functions to also reduce the amount of dynamic memory and stack.

The first issue I had was the lack of org address so I've put in 32768 on the basis that I read that z88dk puts code in from 32768 onwards. It has some success but aborts immediately after colouring the screen.

I've read some basics on bank usage but all seems to be based on assembler at present whereas I'm using C for development. Are there any basic examples around, e.g. print hello world using bank switching. Or is it as simple as banked code doesn't work yet with C?

User avatar
SevenFFF
Posts: 221
Joined: Mon Jun 05, 2017 5:30 pm
Location: USA

Re: Bulkding banked code questions

Post by SevenFFF » Sun May 13, 2018 5:33 pm

No it does work, you should be fine. Once you get to grips with banked code, your entire horizons will open up :)

[mention]Alcoholics Anonymous[/mention] will be along in a while to help out, I'm sure.
Robin Verhagen-Guest
SevenFFF / Threetwosevensixseven / colonel32
NXtel Spectron 2084blog

User avatar
SevenFFF
Posts: 221
Joined: Mon Jun 05, 2017 5:30 pm
Location: USA

Re: Bulkding banked code questions

Post by SevenFFF » Sun May 13, 2018 5:38 pm

Oops, can't mention people with tags here.

I have almost nothing in the regular 128K memory now, apart from bank switching, disk loading, printing, error handling and general glue code. All the game code is in 8K banks switched in with MMU paging. Doing this in assembler, but the principle is the same. Probably the first game you design like this will be a bit awkward because you baked in early design choices, but by the second one you will have a good idea of what works and how to lay everything out.
Robin Verhagen-Guest
SevenFFF / Threetwosevensixseven / colonel32
NXtel Spectron 2084blog

Bagpuss
Posts: 27
Joined: Tue Jun 06, 2017 9:13 pm

Re: Bulkding banked code questions

Post by Bagpuss » Sun May 13, 2018 8:42 pm

Cheers,
Most of the code should be bank friendly as I designed groups of c functions and corresponding globals to sit together.
I've now got the code to build by omitting calls to the banked code for now (I was expecting the compiler and linker to deal with far references and banking, do I need to write by own MMU code to bank in and out before calls?).
What I'm finding isn't working anymore is CRT seems that -startup=01 doesn't allow control codes now in printf. I've changed compilation to be (moving one of the banked bits of code into main memory for now). I'm not sure now if I'm using -startup correctly.

Code: Select all

zcc +zxn -c -clib=new --c-code-in-asm --list  --codesegBANK_3_H --constsegBANK_3_H @zproject_03_H.lst
zcc +zxn -c  -startup=01  -clib=new --c-code-in-asm --list @zprojectMain.lst -Cz"--clean" -o nextRaiders
zcc +zxn -o nextRaiders -clib=new  -lzxnext_layer2 -lzxnext_sprite  messages.o nextRaiders.o
appmake +zxn --sna --sna-clean --org 32768 --sna-clean -b nextraiders
I also find if I use -startup=01 in the linker (i.e. zcc _zxn -o ....) then I get duplicate references so have to have it in the 2nd zcc

Alcoholics Anonymous
Posts: 513
Joined: Mon May 29, 2017 7:00 pm

Re: Bulkding banked code questions

Post by Alcoholics Anonymous » Sun May 13, 2018 8:56 pm

Bagpuss wrote:
Sun May 13, 2018 1:19 pm
From what I have read using zcc to compile banked c code isn't the way to go.
Everything works :)

When you start banking, it's important to keep in mind:

1.
Where the stack is. Are you banking it out? See the REGISTER_SP pragma for setting the initial stack location of a program.

2.
When your banked code runs, can it see the variables or data it operates on in the current 64k configuration?

3.
If you need to be compatible with NextOS, stay away from NextOS's memory in 16k banks 0-8, except for regions of memory traditionally available on the spectrum. Even without NextOS compatibility, you can still use the esxdos api for disk functions.

In C, the assignment to sections (banks, pages, divmmc memory, resources, etc) happens on a per-file basis since you give the section assignments on the compile line, just as you are doing. In asm, there is a directive "SECTION" that allows you to switch sections within a single file.

The C compiler generates code and data into four sections with these as defaults:

code_compiler
The code section holds read-only code. All compiled code goes here.

rodata_compiler
Constant data. These are variables declared as const, eg "const int a = 5;", or the string part of 'unsigned char *msg = "hello world";'. For programs stored in rom, this stuff will also be placed in rom.

data_compiler
The data section. This holds initially non-zero ram variables, eg "int a = 2;" and self-modifying code. For programs stored in rom, the crt must keep a copy in rom that is copied into ram before the program starts.

bss_compiler
The bss section. This holds initially zero or uninitialized variables, eg "int a;". For programs stored in rom, the crt will zero this block of ram before starting the program, using something like an LDIR.

For the zx next, you are compiling a ram model program so the crt will not initialize the data or bss sections by default - instead the produced binary will contain the right initial data when it is loaded into ram. You may want to change this if your program would like to restart from scratch without re-loading. Using a rom model compile you can get the crt to re-initialize all variables in memory so that your program can run multiple times with a clean initial state.

Anyway I list these sections because in order to change what banks stuff is placed in you have to re-assign the section the compiler uses for one or more of the destination sections listed above. Those default sections are placed in the "main binary" by the memory map z88dk supplies. What I mean by "main binary" will be described below.

So to change where code goes, you have to change the section from "code_compiler" to something else with "--codesegNAME" on the zcc compile line, eg "--codesegBANK_6"

To change where the constant data goes, you have to change the section from "rodata_compiler" to something else with "--constsegNAME" on the zcc compile line, eg "--constsegBANK_6"

The data and bss sections can only be changed when using sccz80 (-clib=new). zsdcc cannot change those sections yet (-clib=sdcc_iy or -clib=sdcc_ix). For now, I'd recommend defining bss or data variables in assembler so that you can place them where you want.

Here's an example:

C program contains this:

foo.c

Code: Select all

extern unsigned int a;
extern int b;
The "extern" says this variable is defined somewhere else. That somewhere else will be assembler:

foo.asm

Code: Select all

SECTION BANK_06
PUBLIC _a

_a:  defw 10


SECTION BANK_50
PUBLIC _b

_b: defw -10
The extra leading underscore is necessary because the c compilers add it during translation to asm.

The program is compiled by listing both files on the compile line:
zcc +zxn .... foo.c foo.asm ...

The SECTION directive tells the assembler where something is going in the memory map, the PUBLIC directive makes the symbol visible to all files (if they import it with EXTERN). The "extern" in the c program locates this symbol from the global namespace.

****************

Now something about the memory spaces in the zx next.

z88dk defines four main memory spaces:

DIV
This maps to the 8k memory pages in divmmc memory.

RES
This holds resources, which is stuff you only want available from disk. We will be using this to add data to a single executable file which your program would read as needed from disk.

PAGE and BANK
These are different views of the same ram available on the zx next. BANK divides all the memory into 16k chunks and is consistent with the 128k spectrum's banking model. Eg, ports 7ffd/dffd allow you to place a 16k memory bank in the top 16k of memory from 0xc000 to 0xffff. PAGE divides the same memory into 8k pages. This is consistent with how the zx next's mmu paging works which allows you to put any page into any of the z80's eight physical 8k slots. Because they refer to the same memory PAGE_N, corresponding to 8k page N, refers to the same physical region in memory as 16k BANK_N/2 (N divided by 2, odd page number means upper half of 16k bank).

Although BANK and PAGE refer to the same memory, z88dk treats them differently. z88dk enforces 16k limits on BANKs. So if you put anything into BANK_6, it is not allowed to overflow the 16k boundary. This is consistent with 128k spectrum banking. Only one bank can be seen at a time in address range 0xc000-0xffff and it cannot exceed 16k. Because the zx next memory space is actually divided into 8k slots, z88dk also allows the 16k BANKs to be split into 8k halves called low (L) and high (H). So BANK_6 is a 16k bank with BANK_6_L and BANK_6_H corresponding to the bottom 8k of BANK_6 and the top 8k of BANK_6 respectively. Again, z88dk will enforce 8k sizes on BANK_6_L and BANK_6_H.

PAGEs have no restrictions applied to them at all. If you assign to PAGE_100 and you exceed 8k, any spillover will continue into PAGE_101.

So it is up to you to use BANK or PAGE depending on your needs. Choose BANK to enforce page boundaries and PAGE if you don't care about page boundaries.

z88dk will notify you if the stuff you place in memory overlaps.

Lastly, there is the question of ORG. By default, z88dk sets up the 128k memory model. That is all banks are ORGed to 0xc000 and pages, which divide the banks into 8k halves, are assigned ORG of 0xc000 for even pages and 0xe000 for odd pages.

However because of the zx next's mmu, there is a difference between placing stuff into a physical memory bank and where you might see that stuff in the z80's 64k memory space. So you may want to put items A & B into PAGE_50 but you may want to see item A in mmu0 (address 0-0x1fff) and you may want to see item B in mmu6 (48k-56k). The org for item A will be somewhere in 0-0x1fff but the org for item B will be somewhere in 48k-56k and yet both will be physically placed in the same 8k PAGE_50.

z88dk can handle this scenario but a more common concrete one is you want everything in PAGE_50 to org at 0 so that your program can place page 50 into mmu 0 before accessing the page. You would do this by changing the ORG address of PAGE_50 away from the default. This is done with a pragma "#pragma output CRT_ORG_PAGE_50 = 0". Then anything placed in that page will have org 0 and your program can put page 50 into mmu0 to run that code or read data from there.

Note that for data, the exact org address may not matter. There are functions in the library that will take an assemble address and relocate it into a different mmu slot. So if you have org-agnostic data assembled for mmu6, you can still access it from mmu0 if you modify the address to look at the same offset in mmu0 as originally orged in mmu6. This is what zxn_addr_in_mmu(mmu,addr) does. Place your 8k page containing data into any mmu and then change the address you have for it to point into that mmu with this function.

I think I've gone far enough here and probably further than you wanted to because I think you're still thinking in terms of 128k banking? In that case, simply assigning to BANK_6 or BANK_6_L/BANK_6_H for 8k splits of BANK_6 will org your code in the top 16k and is ready for banking using legacy port 7ffd/dffd or mmu6,mmu7.

There is much more flexibility if you use 8k pages and mmu to place pages in any part of the z80's address space.
Last edited by Alcoholics Anonymous on Mon May 14, 2018 3:40 am, edited 13 times in total.

Alcoholics Anonymous
Posts: 513
Joined: Mon May 29, 2017 7:00 pm

Re: Bulkding banked code questions

Post by Alcoholics Anonymous » Sun May 13, 2018 9:28 pm

Bagpuss wrote:
Sun May 13, 2018 8:42 pm
I've now got the code to build by omitting calls to the banked code for now (I was expecting the compiler and linker to deal with far references and banking, do I need to write by own MMU code to bank in and out before calls?).
No you need to do the actual banking manually. When you call something, you have to make sure it and the ram data it reads/writes are visible in the 64k memory space in the right mmu slot(s). There are special macros in arch/zxn.h that change page in each mmu slot. These map to "nextreg" instructions.

I forgot to explain the concept of "main binary" in the last post. The "main binary" is the 64k configuration you load your program into and usually consists of ROM3,BANK_5,BANK_2,BANK_0. This is where 48k programs load and where the memory visible to basic is in 128k spectrums. This is where the compiler generates code into by default and is where the z88dk memory map places all the library code your program uses. The idea is this "main binary" contains the fixed common code your program uses that is never banked out; code in other banks can count on code here always being visible so that they are free to use the same library routines or your common routines stored here. Anything you don't assign to BANKs, PAGEs, DIV or RES will be put into the main binary. Of course you have full control of where things go in memory if you supply your own memory map but this model is very appropriate for banked systems. In cases where you just can't put all common code in a fixed common bank, you can also duplicate code into orthogonal banks so that some banks use different copies of the same code.

As a neat tidbit, you can add a loading screen$ to an sna by placing the screen$ in bank 5 as long as nothing else is allocated there. Bank 5 is separate from the "main binary" even if the main binary occupies part of bank 5 (z88dk will merge everything together when the binary is made and will let you know if anything overlaps). With nothing else in bank 5, placing something there will ensure it appears at the front of bank 5 which is where the spectrum's display file is. Add this to your project and your sna will have a screen$:

screen.asm

Code: Select all

SECTION BANK_5
binary "screen.bin"   ;; file containing the screen$
What I'm finding isn't working anymore is CRT seems that -startup=01 doesn't allow control codes now in printf. I've changed compilation to be (moving one of the banked bits of code into main memory for now). I'm not sure now if I'm using -startup correctly.

Code: Select all

zcc +zxn -c -clib=new --c-code-in-asm --list  --codesegBANK_3_H --constsegBANK_3_H @zproject_03_H.lst
zcc +zxn -c  -startup=01  -clib=new --c-code-in-asm --list @zprojectMain.lst -Cz"--clean" -o nextRaiders
zcc +zxn -o nextRaiders -clib=new  -lzxnext_layer2 -lzxnext_sprite  messages.o nextRaiders.o
appmake +zxn --sna --sna-clean --org 32768 --sna-clean -b nextraiders
The crt is what implements per-compile options like startup and many pragmas. The crt is only present when the output binary is being formed. So "-startup=1" in your zcc invoke above has no effect because you're making an object file not a binary and the crt is not there. "-startup=1" is needed on your "-o" line where the binary is formed and where the crt will be pulled into the compile by zcc.

This is what you are looking for:

Code: Select all

zcc +zxn -vn -c -clib=new --codesegBANK_3_H --constsegBANK_3_H @zproject_03_H.lst -o bank3
zcc +zxn -vn -c -clib=new @zprojectMain.lst -o main
zcc +zxn -vn -startup=1 -clib=new -o nextRaiders bank3.o main.o -lzxnext_layer2 -lzxnext_sprite -pragma-include:zpragma.inc -subtype=sna -Cz"--clean" -create-app
(Change the -vn to -v if you want to see what zcc is doing)

The first line compiles all source files listed in "zproject_03_H.lst" and outputs into a single consolidated object file "bank3.o". You've changed which bank the compiler compiles its code and rodata into.

The second compile line compiles all source files listed in "zprojectMain.lst" and outputs into a single consolidated object file "main.o"

The last compile line forms the output binary. Here we're communicating with the crt with "startup" and pragmas in the file "zpragma.inc". The linker has to put it all together, including finding routines you may be using in the zxnext_layer2.lib and zxnext_sprite.lib libraries. You also have to specify what sort of output you want, with sna being selected here and additional option "--clean" which means appmake will delete all binaries it uses to make the sna file. (The output from the compiler/assembler/linker is a set of bin files. Appmake, called when -create-app is there, takes these bin files and makes the final output sna from them).

Or if you want to keep the appmake step separate:

Code: Select all

zcc +zxn -vn -c -clib=new --codesegBANK_3_H --constsegBANK_3_H @zproject_03_H.lst -o bank3
zcc +zxn -vn -c -clib=new @zprojectMain.lst -o main
zcc +zxn -vn -m -startup=1 -clib=new -o nextRaiders bank3.o main.o -lzxnext_layer2 -lzxnext_sprite -pragma-include:zpragma.inc
appmake +zxn --sna --clean -b nextRaiders -o nextRaiders.sna
The last zcc generates the output but does not call appmake to process it. So the output will be a set of binary files. A map file must be generated "-m" so that the following appmake invoke has enough information to make the output from the binaries.

Pragmas define many options, including ORG for all memory banks and the main binary. Your zpragma.inc file would look like this:

zpragma.inc

Code: Select all

// set org of main binary
#pragma output CRT_ORG_CODE = 32768
(although that is the default org too).

Details about other pragmas can be found here: https://www.z88dk.org/wiki/doku.php?id= ... figuration

The defaults for zxnext ram model compiles in particular:
https://github.com/z88dk/z88dk/blob/mas ... ig.inc#L22
These are what you'd be overriding with pragma directives.

You can override the default org values for each bank or page with pragmas too:
https://github.com/z88dk/z88dk/blob/mas ... ap.inc#L27
(eg "#pragma output CRT_ORG_PAGE_100 = 0x2000" - this is suitable if you want to run code stored in page 100 from mmu1)

Pragmas at the bottom of this file can change some text terminal characteristics like window size and font:
https://github.com/z88dk/z88dk/blob/mas ... s.inc#L104
Last edited by Alcoholics Anonymous on Mon May 14, 2018 3:45 pm, edited 10 times in total.

Alcoholics Anonymous
Posts: 513
Joined: Mon May 29, 2017 7:00 pm

Re: Bulkding banked code questions

Post by Alcoholics Anonymous » Sun May 13, 2018 10:15 pm

Its building 128k images to getting nearer to where I need to be. As part of the development I've removed all mallocs and presized variables. All variables are created as globals rather than in functions to also reduce the amount of dynamic memory and stack.
If not using malloc and not opening new files, you should set "#pragma output CLIB_MALLOC_HEAP_SIZE = 0" and "#pragma output CLIB_STDIO_HEAP_SIZE = 0" as well.

Making everything global is not always a good idea.

For example this:

Code: Select all

void main(void)
{
   static unsigned char i = 0;

   for (i = 0; i != 20; ++i)
      printf("hello");
}
places "i" in memory (it's a "global" but the variable can only be seen inside main so it helps to keep all instances of local "i" in different functions separate). This means the compiler cannot put it into a register. For zsdcc, this leads to worse code.

Code: Select all

void main(void)
{
   for (unsigned char i = 0; i != 20; ++i)
      printf("hello");
}
zsdcc will be able to put "i" into a register and much better code will be produced.

You never want big items on the stack but in terms of memory usage (for data not counting code size to access the data), stack variables are easy on memory because they are transient and automatically disappear after a function returns. For sccz80, making these variables static may get better code out in most circumstances but for zsdcc this is not always the case. For zsdcc, it will depend on the complexity of the function, size of vars (32-bit and above are usually better as statics), and number of variables simultaneously needed (you can indicate to the compiler which vars are in the working set by breaking large functions into smaller blocks inside {} and defining needed local vars inside those instead of at full function scope).

If you're worried about getting the fastest and smallest code out, you should be aiming to use zsdcc. sccz80 can still be very useful for quick compile turnaround as it is much faster than zsdcc.

Alcoholics Anonymous
Posts: 513
Joined: Mon May 29, 2017 7:00 pm

Re: Bulkding banked code questions

Post by Alcoholics Anonymous » Mon May 14, 2018 3:40 am

At the end of this I hope a few things are clear(er):

1.
You're making two decisions for banked code. One is where the code/data will be placed in physical memory. This is assigning the code or data to BANKs and PAGEs. The other is where you want to see that code or data in the z80's 64k address space. This is the ORG value of those same BANKs and PAGEs. z88dk's default set of ORG is consistent with the 128k spectrum and places those BANKs and PAGEs in the top 16k of the z80's 64k address space.

2.
Your program is responsible for banking. The tools cannot do far calls and far data yet and if it did, you may still prefer this "near" model because it's also faster.

3.
Banked code is often structured as a fixed common area that is never paged out and then other code or data that is paged in as demanded. The common area contains library code and user routines that must always be available. You can break this model if necessary (and if you know what you are doing as it is easy to screw up!).

4.
You need to be aware of some pitfalls. Be aware of where your stack is so that it doesn't get accidentally paged out. Be aware of where interrupts are serviced so that isrs are not paged out. Etc.

5.
With z88dk, compile time configuration is handled via pragmas (and this includes things like ORG and initial stack location) which are best kept in an independent file read when the binary is produced using "-pragma-include:filename" on zcc's compile line. The crt is responsible for implementing the options selected by the pragmas and it collects those pragmas from a file "zcc_opt.def" that is created by zcc as it processes the input files.

Bagpuss
Posts: 27
Joined: Tue Jun 06, 2017 9:13 pm

Re: Bulkding banked code questions

Post by Bagpuss » Mon May 14, 2018 5:32 pm

Thanks for the help. I think I'll need the next few days to digest all this & make up worked examples to get my head around it :)

I'm going for next development (as you would expect on here :) ) but thought I would start with 128K SNA files as a starter.
The globals I'm using are for general config and game run time. For functions that require lookup tables or own statics I put them in the same file as the code so they can be in the same bank.

In terms of variable management and the like, outside of my normal systems (which usually have a few TB of RAM so I have a bit to play with :) ) I'm thinking of the way I do Arduino programming (i.e. nearer to concepts of MISRA with pre allocated space).
I have a set of general purpose variables that are retained within each bank and uniquely named in relationship to that bank, a bit wasteful but it does de-conflict variables and helps isolate code.
I see what you mean thought about zdcc (I remember years back using register variables which act as a hint to the compiler to use registers where ever possible on some CPUs).

I'll have to look more into the startup. I kept getting compilation errors moving the startup around, when I put it into the zcc -o section, I got a metric tonne of errors on missing stdio & tshr_01_output_fzx_smooth_scroll.
I think I may just stick to default CRT and code my own assembler to replace printf as should really be coding something more optimised.

I'll see how far I get through this lot in the week :)

Alcoholics Anonymous
Posts: 513
Joined: Mon May 29, 2017 7:00 pm

Re: Bulkding banked code questions

Post by Alcoholics Anonymous » Mon May 14, 2018 11:23 pm

Bagpuss wrote:
Mon May 14, 2018 5:32 pm
I'm going for next development (as you would expect on here :) ) but thought I would start with 128K SNA files as a starter.
The globals I'm using are for general config and game run time. For functions that require lookup tables or own statics I put them in the same file as the code so they can be in the same bank.
ok but keep in mind that zsdcc is unable to change the bank it puts non-const data into (whereas sccz80 can). It's an annoying issue that couldn't be fixed immediately because sdcc itself has a bit of a contorted view of its data section.
I'm thinking of the way I do Arduino programming (i.e. nearer to concepts of MISRA with pre allocated space).
I have a set of general purpose variables that are retained within each bank and uniquely named in relationship to that bank, a bit wasteful but it does de-conflict variables and helps isolate code.
ok I see what you mean. We do the same in the library... even malloc (which is the opposite of what you're talking about here) works within a fixed size and pre-allocated container (and the api allows many independent heaps to be created in fixed regions in memory). There's an obstack implementation that creates an obstack in a fixed region of memory and the data types the library supplies also work by placing links into memory the user supplies. So the linked list implementation uses spots in the user's struct for links, the variable sized arrays have a fixed bit of static memory they are allowed to grow into, etc.
I'll have to look more into the startup. I kept getting compilation errors moving the startup around, when I put it into the zcc -o section, I got a metric tonne of errors on missing stdio & tshr_01_output_fzx_smooth_scroll.
Are you perhaps mixing up the startup codes? "tshr_01_output_fzx_smooth_scroll" is for an fzx terminal (that is proportional fonts) on the Timex 512x192 screen. This would be "-startup=24" I think. It doesn't do control codes; the version that does would be "-startup=25" but it hasn't been put in yet. "-startup=1" selects the fixed width 8x8 font 32x24 driver that we're all familiar with.

"tshr_01_output_fzx_smooth_scroll" sounds like it came from one of the examples in z88dk. The terminal drivers are object oriented so you can specialize their behaviour and one of the examples changes the behaviour for scrolling by supply a new scroll routine that scrolls a pixel at a time instead of multiple text rows at a time. But this one is only for the Timex hi-res terminal and you'd have to make some changes to get it to work with a 32x24 driver.

The startup values are just selecting canned crts by number (see https://github.com/z88dk/z88dk/tree/mas ... xn/startup ). The variety of crts are there mainly for convenience. They mainly differ by what drivers are placed on stdin,stdout with some intended for the 32x24 screen, some for the 64x24 Timex screen, some using proportional fonts, some using 4x8 or 8x8 fixed width fonts. You can actually create your own driver instantiation section and decide to have multiple text terminals in variable-width windows distributed around the screen.
I think I may just stick to default CRT and code my own assembler to replace printf as should really be coding something more optimised.
If using printf you should also make sure to use the "#pragma printf = ..." pragma to control which % converters end up in the compile. The default list is fairly long and if you confine yourself to a few you can save on size. Also, the canned crts all define stdin and stderr as well as stdout. So you can save a bit more memory by using a custom driver instantiation that eliminates stdin and stderr if you don't use them.

There's also a step change if you go to sprintf instead of printf and get rid of the stdin.stdout drivers entirely. This is because the drivers in z88dk are pretty feature rich and are intended to work within c's stdio model. Using sprintf in combination with a minimal custom print routine would be much smaller. And then you may want to reduce the features further and just have a minimal print routine. For arcade games especially this is usually what you want.

All of the z88dk library is written in assembler, including printf so it's all much smaller than you'll find in most c compilers already.

Post Reply