Documentation/Examples on how to setup Banks on Spectrum 128

ZX80, ZX 81, ZX Spectrum, TS2068 and other clones
KyotoG
Member
Posts: 16
Joined: Sat Aug 05, 2017 11:10 pm

Documentation/Examples on how to setup Banks on Spectrum 128

Post by KyotoG »

Hi,

I've reached the point where I now need to start thinking about banking memory. I see that after running zcc I end up with eight files named name_BANK_xx.bin which are all 0 bytes in length. I've searched these forums, googled, etc... but cannot find anything of note of how to setup my build to utilize this. I'm developing a music player and editor for the Spectrum Next, and have limited experience with the spectrum in general (I was a c64 guy) so plz bear with me!

1. Is there a page(s) that explains how the system works, or example code? Maybe this will answer all my questions?
2 I see that zx_rules.inc defines the eight banks, all org-ed at $c000. According to this page, bank2 is located at $8000 (but can also be mapped to $c000) so this is confusing to me as how can the code for this bank be compiled with an ORG of $8000? http://www.worldofspectrum.org/faq/refe ... erence.htm
3. How do I specify which files (c and asm) are assigned to which bank?

I have a lot more questions but I think this is enough to get rolling if I can understand these.

Cheers!
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

KyotoG wrote:I've reached the point where I now need to start thinking about banking memory. I see that after running zcc I end up with eight files named name_BANK_xx.bin which are all 0 bytes in length.
You may want to update the tools - empty files should no longer be generated and recent commits have added the zx next target (+zxn) and a method to generate 128k sna snapshots which you may be needing for fast development. I haven't actually checked that 128k snas work yet tbh but 48k ones do (add -subtype=sna to a compile line).
I've searched these forums, googled, etc... but cannot find anything of note of how to setup my build to utilize this. I'm developing a music player and editor for the Spectrum Next, and have limited experience with the spectrum in general (I was a c64 guy) so plz bear with me!
Yeah there isn't much information out there yet.
1. Is there a page(s) that explains how the system works, or example code? Maybe this will answer all my questions?
The original 128k spectrum banks memory through port 0x7ffd as described here in the memory subsection. Memory can only be paged into the top 16k bank at address 0xc000. The roms at the bottom of memory can also be changed but as that's all rom that doesn't help you. The bottom three bits of port 0x7ffd determine which of 16k banks 0-7 are currently paged in there. The other 16k ram pages at 0x4000 and at 0x8000 are fixed and are occupied by banks 5 and 2 respectively.

The other bits should be bit 3 = 0 to select the display file at address 0x4000 (the newlib can also draw to the second display file at 0xc000 if that becomes convenient), bit 4 = 1 to keep the original 48k rom at the bottom of memory, bit 5 = 0 so that further writes to this port are allowed and bits 6/7=0.

If you include arch/zx.h or arch/zxn.h, this port is defined so you an write to it like you are writing a char as in "IO_7FFD = value;". Of course when you suddenly page out the top 16k you need to make sure your stack is not up there, your interrupt routine is not up there including your isr table and your program is not currently executing up there. z88dk is not going to do any banking related things for you automatically - you have to manage when the bank is changed and how your program and data is distributed amongst banks so that you can do this banking. Usually people will segment their program so that there is a fixed portion always in memory that fits below 0xc000 and then there are independent portions that fit into the 16k banks at the top of memory that only call into the fixed portion and not between themselves.

The Next adds many more banks besides those 8. It also uses bits 6 and 7 to identify the bank so that in total you have 5 bits to select one of 32 16k banks but it should be unnecessary for now to access those, and I haven't defined them yet in the Next target nor can the 128k SNA snapshot format accommodate them.

The Next can also bank ram in the bottom 16k using divmmc (the sd interface thing) but the details are not clear and there are special considerations because layer 2 also maps down there in a write only mode.
2 I see that zx_rules.inc defines the eight banks, all org-ed at $c000. According to this page, bank2 is located at $8000 (but can also be mapped to $c000) so this is confusing to me as how can the code for this bank be compiled with an ORG of $8000?
Yeah banks 2 and 5 are always fixed at 0x8000 and 0x4000 respectively. So if you also put bank 2 into the top 16k, you have a second image of it up there and can also read or write it at address 0xc000.

The best way to think about it is you have a fixed portion of your program in banks 2 & 5 and then the variable banked portion in the other banks that must be mapped at 0xc000.
3. How do I specify which files (c and asm) are assigned to which bank?
z88dk divides a binary into sections and the linker will map the sections to memory according to a memory map. Part of the memory map you found is defining sections for banks 0-7 at address 0xc000. Don't bother using banks 2 & 5 as those will be in main memory in the main binary ("foo_CODE.bin"). When the SNA snapshot is put together, those sections will be ignored, but in the future these banks and the main binary will be merged so that there is only one bank 2 and one bank 5; the advantage of having these sections is you will be able to compile some code that can be executed at address 0xc000 rather than at 0x4000 or 0x8000 in those banks.

How do you assign code and data to a specific bank? From asm it's easy:

Code: Select all

SECTION BANK_07
PUBLIC _label_07

_label_07:
defm "I'm in bank 7",0

SECTION code_user
PUBLIC _label_main

_label_main:
defm "I'm in the main _CODE binary", 0
You can just change section whereever you want. The sections are open so you can add to them from any c or asm file, however if you do add from more than one file the order things are added to the bank cannot be guaranteed (nor should it matter).

For C, bank assignment happens on a file granularity. This means you have to separate your c code and data into files that belong to the same bank. Then compile your .c files to object files, then link the whole works together in a separate zcc invoke.

zsdcc is less flexible than sccz80 at the current time. It can only change the section it compiles code to (so executable code) and the section it compiles constant data to (rodata section - const variables). Regular variables in bss, initialized variables in data and self-modifying code in data will always go to the main binary. Change the sections for code and rodata by adding to the compile line: "--codesegBANK_07 --constsegBANK_07".

sccz80 can change any section and adds "--bssseg" as well as "--dataseg".

So as an example, suppose you have .c files going into bank 7. List them in a project file: zproject-07.lst
Everything else goes into the main binary so list them in: zproject-m.lst
Finally list all files in the project as .o in: zproject.lst

Then you would compile in three steps.

1. Compile stuff destined for bank 7:
zcc +zx -vn -c ... --codesegBANK_07 --constsegBANK_07 @zproject-07.lst

2. Compile everything else for the main bank:
zcc +zx -vn -c ... @zproject-m.lst

3. Link all the object files together to form the binaries and a 128k snapshot (hopefully it works!):
zcc +zx -vn -subtype=sna .... -o name @zproject.lst -create-app

Or you can get rid of the subtype and create-app and put together the binaries yourself perhaps with a basic loader that loads each bank from tape.
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

Unless you know for sure you're reasonably confident you'll be needing a lot of extra memory, you may want to look into using the memory below 32768 too. You can define sections to fill holes down there and placing bss variables down there is an easy way to use that mem.
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

I should add, programs for the sega master system routinely use extra memory banks. You can see an example with compile lines here:
https://github.com/z88dk/z88dk/tree/mas ... AstroForce

On the sms, the main program resides in a fixed 0-32k area and uses a fixed ram area from 48-56k. The middle 16k from 32k-48k is banked and in this example, the only thing put there is constant data like graphics and sound, not code at all.
KyotoG
Member
Posts: 16
Joined: Sat Aug 05, 2017 11:10 pm

Post by KyotoG »

Thanks alvin! (AA?) most appreciated! I'm reading and digesting now but in the interim, here's a quick pass of what I had in mind for my memory and bank maps. I'm using ULA+ and Timex modes, so memory below $8000 is sparse. I did try to place the stack pointer at various locatinos $8000/$7fff/$6000/$5fff but the program crashed when I ran it. Any ideas?

I tried using --constsegBANK_07 etc... earlier today but it wasn't recognized as a valid option (it just spat out the options list when I executed the line). Maybe I just need to update to the latest (which I need to anyway).

-= Memory Map =-

Memory 0x0000-0x3FFF
0x0000-0x3FFFF ROM

Memory 0x4000-0x7FFF
0x4000-0x57FF Screen pixels
0x5800-0x5FFF free 2k
0x6000-0x77FF Screen attributes
0x7800-0x7FFF free 2k (good place for stack?)

Memory 0x8000-0xBFFF
0x8000-0x8101 IM2 Jump table (im2 table at 0x8000 filled with 0x81 bytes with jp to isr at 0x8181)
0x8102-0x8180 free (126 bytes)
0x8181-0x8183 IM2 Jump instruction (jp to isr at 0x8181)
0x8184-0xBFFF Editor code

Memory 0xC000-0xFFFF
0xC000-0xFFFF Switching in the Banks as per below


-= Banks Map =-

Bank 0 Editor data
Bank 1
Bank 2 Editor code + local editor data
Bank 3 Music player + data
Bank 4
Bank 5 Screen (0x4000)
Bank 6 Graphics data (and rendering code?)
Bank 7 Shadow screen (but we can use)
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

KyotoG wrote:Thanks alvin! (AA?) most appreciated!
Yes!
I tried using --constsegBANK_07 etc... earlier today but it wasn't recognized as a valid option (it just spat out the options list when I executed the line). Maybe I just need to update to the latest (which I need to anyway).
zsdcc has had those options for a long time but for sccz80 it is relatively recent.

I'm reading and digesting now but in the interim, here's a quick pass of what I had in mind for my memory and bank maps. I'm using ULA+ and Timex modes, so memory below $8000 is sparse. I did try to place the stack pointer at various locatinos $8000/$7fff/$6000/$5fff but the program crashed when I ran it. Any ideas?
There should be no problem with moving the stack to 0x8000 or 0x6000 (as long as the basic isr is not running, which it should not be as your program will start with interrupts disabled and then you set your own interrupt routine via im2). Your music player is in bank 3 so your isr, which must be in the main binary below 0xc000, may have to enable bank 3, call your music isr, then restore whatever bank is supposed to be up there and ei/reti. Note that port 0x7ffd is write only so you have to keep track of the last port write to know what bank is currently paged in there. So every time you write to port 0x7ffd, also keep a copy in another ram variable. You can use the library variable "unsigned char GLOBAL_ZX_PORT_7FFD" (should be defined in zx.h/zxn.h). The library doesn't use it yet but when it does this will keep you and the library in sync.

Other than that I can't see anything that would cause a crash.
KyotoG
Member
Posts: 16
Joined: Sat Aug 05, 2017 11:10 pm

Post by KyotoG »

Yes, the ISR is located at $8000 set to jump to $8181, interrupts are disabled at startup, as per your help previously. Right now, everything isn't banked (obviously, as that is the topic of this thread!). I was just experimenting with setting the SP just to make sure everything is ok. But it isn't. I think I need to solve that first before getting ahead of myself with banking. Memory is really tight (the .def file shows the last function at $FD75) but I was able to move the SP slightly lower down about 50 bytes or so without any issues. I was thinking that the 2k regions in the video bank memory would be more than plenty. Is there anything funky going on when the .tap file runs? Maybe basic is stomping that memory, and therefore the stack?
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

KyotoG wrote:I was just experimenting with setting the SP just to make sure everything is ok. But it isn't. I think I need to solve that first before getting ahead of myself with banking....I was thinking that the 2k regions in the video bank memory would be more than plenty.
256 bytes is normally enough unless you are doing something unusual like you have deeply recursive subroutines or you are declaring large data on the stack in local variables.
Is there anything funky going on when the .tap file runs? Maybe basic is stomping that memory, and therefore the stack?
No, the basic loader sets ramtop below your to-be loaded binary so that nothing basic does will interfere with the binary being loaded. Then your program starts and only then does your program put things in lower memory below ramtop like the im2 table and the second display file. Basic's not running anymore so there's nothing that can happen.

Is it only crashing after you try to exit? Because you're not going to be able to exit to basic after you begin using the 2nd dfile or move the stack too much around there. This will clobber basic's variables, program, etc.
KyotoG
Member
Posts: 16
Joined: Sat Aug 05, 2017 11:10 pm

Post by KyotoG »

It crashes right at the start. I'm just finishing up modifying my build to create the .sna file, then I'll strip everything down to try and isolate the issue and report back. Thanks!
KyotoG
Member
Posts: 16
Joined: Sat Aug 05, 2017 11:10 pm

Post by KyotoG »

I've built a .sna (as per above but using +zxn instead of +zx) but have an odd situation when loading the .sna in ZEsarUx where it changes the machine from TBBlue to Spectrum 48k and crashes if I call my music driver from the ISR. Disabling that "runs" but the Next features don't work - namely the hardware sprites. The old .tap I was generating works fine - TBBlue is preserved.
KyotoG
Member
Posts: 16
Joined: Sat Aug 05, 2017 11:10 pm

Post by KyotoG »

I found this page (http://www.worldofspectrum.org/faq/refe ... ormats.htm) and it looks like the .sna build step is generating a 48k image (the header and filesize match what's on this page) instead of the 128k version.
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

KyotoG wrote:I found this page (http://www.worldofspectrum.org/faq/refe ... ormats.htm) and it looks like the .sna build step is generating a 48k image (the header and filesize match what's on this page) instead of the 128k version.
z88dk will produce a 48k sna if you don't use the 128k memory banks. If you use one of those banks it will build a 128k sna.
I've built a .sna (as per above but using +zxn instead of +zx) but have an odd situation when loading the .sna in ZEsarUx where it changes the machine from TBBlue to Spectrum 48k and crashes if I call my music driver from the ISR. Disabling that "runs" but the Next features don't work - namely the hardware sprites. The old .tap I was generating works fine - TBBlue is preserved.
There's no machine information in an sna file so ZEsarUX is assuming a specific spectrum model when loading the sna. That will have to be overridden somehow by convincing ZEsarUX to load the sna into a TBBLUE config. Actually, doesn't esxdos have a snapshot loader? Maybe you could load the sna through esxdos? I'll try some of these things a little later.
KyotoG
Member
Posts: 16
Joined: Sat Aug 05, 2017 11:10 pm

Post by KyotoG »

Ok, that makes sense. I created a dummy c file assigned to bank 03 and now it's generating a 128k .sna. But you hit the nail on the head - ZEsarUX switches to Spectrum 128k mode and my program doesn't work as a result of that. I appreciate your help!
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

We'll probably have to generate some sort of file specific to esxdos / zx next to take care of this properly.
KyotoG
Member
Posts: 16
Joined: Sat Aug 05, 2017 11:10 pm

Post by KyotoG »

As a sanity check, I've managed to build a tap using the same steps but without the subtype=sna and that load and executes correctly.

I figured out the crash at startup up issue when placing the stack at 0x8000, which I solved by added these two lines to my pragma file. I'll have to figure out how to use the heap if I end up needing to allocate memory.

Code: Select all

#pragma output CLIB_MALLOC_HEAP_SIZE = 0
#pragma output CLIB_STDIO_HEAP_SIZE = 0
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

Ah, yes I often forget to mention that. The default setting for CLIB_MALLOC_HEAP_SIZE (-1) will try to create a heap between the end of BSS and the bottom of the stack. But since you moved your stack below BSS, the crt computes a negative heap size and silently causes the program to exit, which will cause a crash if you move your SP down low enough.

Another setting is to specify a size > 14 bytes. If that's done the heap will be created in the BSS section.

And a third option is to specify the top address of the heap so that the heap is created between the end of BSS and that address. In this case, the top address is given as a negative value. Eg if you set CLIB_MALLOC_HEAP_SIZE = -0xffff, then the heap will extend from the end of BSS up to and including address 0xffff.

BTW, I posted on facebook that you can use esxdos to load snapshots using the snapload dot command. ".snapload prog.sna"
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

I should also mention it's possible to place the heap someplace in memory, for example in a 2k hole.

There are other options as well like a block memory allocator or an obstack, which is just a fancy name for allocating out of an array (give me 20 bytes, move pointer forward, give me 100 bytes, move pointer forward. Free this thing - move pointer backward to this thing).
KyotoG
Member
Posts: 16
Joined: Sat Aug 05, 2017 11:10 pm

Post by KyotoG »

Thanks! I saw the post and have that up and running. Now I'm going to try and setup a bank switching test. And thanks for the heap tips. I'll look into that later. Does the same information apply to the STDIO heap? I'm not sure if I'll need it if I use ESXDos though but I haven't even looked into the file handling side of things yet.
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

KyotoG wrote:Does the same information apply to the STDIO heap?
No, the stdio heap is always created in the bss section. You won't need it unless you open memory streams, which you're probably not doing, so setting to 0 will save you some bytes.
I'm not sure if I'll need it if I use ESXDos though but I haven't even looked into the file handling side of things yet.
It's fairly easy to use and similar to doing io with file descriptors. The newlib does not have an object model for disk yet so esxdos is implemented in parallel as an independent entity.

My first test program is a dot command:
https://drive.google.com/file/d/0B6XhJJ ... sp=sharing

ZEsarUX's esxdos handler doesn't quite emulate esxdos properly yet so the file open mode that happens when only one file is specified ("ESXDOS_MODE_R|ESXDOS_MODE_W|ESXDOS_MODE_OE") does not work.

And the file open for writing does not work as "ESXDOS_MODE_W|ESXDOS_MODE_CT" but will work as "ESXDOS_MODE_W|ESXDOS_MODE_OE" (almost, ZEsarUX treats this as an append mode which esxdos does not do).

If you compile this example, you have to change this last flag and avoid invokes with one filename. Nothing bad will happen but the program will stop with errors otherwise.
KyotoG
Member
Posts: 16
Joined: Sat Aug 05, 2017 11:10 pm

Post by KyotoG »

I noticed one oddity using the bank approach of building the code, and that it that the compile and link error line numbers are for "blobbed" up files that belong to the bank, apposed to the line and filename of the originating file(s). This makes it quite tedious to debug - is there a roadmap to produce output that is more human digestible?
KyotoG
Member
Posts: 16
Joined: Sat Aug 05, 2017 11:10 pm

Post by KyotoG »

Excellent! I now have banking working! The music player is in Bank 4, Sprite data (which I will purge after init and use for the editor data) in Bank 3, Stack at 0x8000, additional data int the 0x5800-0x6000 hole, and have around 2k spare in the code Bank 2 (after disabling the text output). Thanks for all your help alvin.

Is there a way of having the font data and code reside in a specific bank (other then Bank 02), as that is very tight memory wise. If not, I'll have to roll my own, which isn't the end of the world but thought I'd check first.
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

KyotoG wrote:I noticed one oddity using the bank approach of building the code, and that it that the compile and link error line numbers are for "blobbed" up files that belong to the bank, apposed to the line and filename of the originating file(s). This makes it quite tedious to debug - is there a roadmap to produce output that is more human digestible?
Can you give an example of what you are talking about here?
Excellent! I now have banking working! The music player is in Bank 4, Sprite data (which I will purge after init and use for the editor data) in Bank 3, Stack at 0x8000, additional data int the 0x5800-0x6000 hole, and have around 2k spare in the code Bank 2 (after disabling the text output). Thanks for all your help alvin.

Is there a way of having the font data and code reside in a specific bank (other then Bank 02), as that is very tight memory wise. If not, I'll have to roll my own, which isn't the end of the world but thought I'd check first.
That's great!

Yes you can place the font anywhere. Which font are you using and how are you using it? (Your own print code, a driver to print?)
KyotoG
Member
Posts: 16
Joined: Sat Aug 05, 2017 11:10 pm

Post by KyotoG »

I don't have an example at hand but it's during linking. I forget to add a new file to the link list list (but could easily enough be caused by a typo) and the error was for a garbled name .asm file with a line number that didn't not match anything.

I'm using start=5 with the default 64 column font. I currently change the startup mode to 2 (I think - I'm not at my computer right now) as it was taking up a large amount of code space - about 4-5k. I doubt I can afford that as I only have 2k left in my main code bank and have a lot more code to add. I'll most likely end up just writing a simple text renderer with a font located in a spare bank.
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

KyotoG wrote:I don't have an example at hand but it's during linking. I forget to add a new file to the link list list (but could easily enough be caused by a typo) and the error was for a garbled name .asm file with a line number that didn't not match anything.
Yeah I know what you are talking about. zcc creates temp files as it processes the input and it only passes the names of those temp files to the linker so that the linker can only use those names in errors.
I'm using start=5 with the default 64 column font. I currently change the startup mode to 2 (I think - I'm not at my computer right now) as it was taking up a large amount of code space - about 4-5k. I doubt I can afford that as I only have 2k left in my main code bank and have a lot more code to add. I'll most likely end up just writing a simple text renderer with a font located in a spare bank.
Yes those drivers are heavy because they are independent of the rom and are intended to implement everything a driver can do (io via stdio or file descriptors, ioctls, lots of options, etc).

The simplest step to reduce size is to specify exactly which printf converters you need (and scanf if you are using that) via pragma: #pragma printf = "%c %s %u"

The next easiest step is to eliminate stdin if you don't use it. startup=5 provides stdin, stdout, stderr and you can specify a custom instantiation of drivers that eliminates stdin and stderr.

However if memory is short and unless you really need printf, it'll be much smaller to implement your own functions that write directly to the display.


About using bankswitched memory for this. Yes you can do it. For the 64 column driver you would subclass it so that if printing is occurring, you first change banks, then call the existing print routine, then restore bank. Because it's object oriented the amount of code to write is very small.

You can place the font in another bank most easily (ie without coming up with a custom memory map) by add it as an asm file where you can specify the bank, something like this:


SECTION BANK_07
PUBLIC _zx7_font_4x8_default

_zx7_font_4x8_default:

BINARY "font_4x8_default.bin"


You will be hijacking the name "_zx7_font_4x8_default" from the library. The linker will find your name first and the driver will use your name to refer to the font.

The font data itself ("font_4x8_default.bin") can be copied from libsrc/_DEVELOPMENT/font/font_4x8 to your local directory.

If you want to try any of this let me know and I can show you how to subclass the driver.
KyotoG
Member
Posts: 16
Joined: Sat Aug 05, 2017 11:10 pm

Post by KyotoG »

Great info! I think, at least for developing, having the 4x8 font will be useful for debugging. All that I really need is the ability to render a font to the screen at a specified position, and to be able to generate strings with numbers (dec and/or hex) inside it, with at least the font residing in another bank (which looks easy). If the code is small enough I could probably get away with it in the main but a bit of a waste really as my program won't have heavy font usage. A small wrapper to change banks and call into the bank would work best.

Oh, I ended up moving the stack to the 126 byte hole (0x8101-0x8180) in between the ISR jump table (0x8000) and Jump instruction (0x8181). Like you said, it should be plenty as I'm not doing any recursion, and only call 2-3 function deep at max.
Post Reply