Documentation/Examples on how to setup Banks on Spectrum 128
Documentation/Examples on how to setup Banks on Spectrum 128
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!
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!
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).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.
Yeah there isn't much information out there yet.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!
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.1. Is there a page(s) that explains how the system works, or example code? Maybe this will answer all my questions?
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.
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.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?
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.
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.3. How do I specify which files (c and asm) are assigned to which bank?
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
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.
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.
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.
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)
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)
Yes!KyotoG wrote:Thanks alvin! (AA?) most appreciated!
zsdcc has had those options for a long time but for sccz80 it is relatively recent.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).
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.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?
Other than that I can't see anything that would cause a crash.
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?
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.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.
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 there anything funky going on when the .tap file runs? Maybe basic is stomping that memory, and therefore the stack?
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.
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.
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.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.
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.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.
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.
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
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"
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"
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).
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).
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.
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.KyotoG wrote:Does the same information apply to the STDIO heap?
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.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.
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.
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?
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.
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.
Can you give an example of what you are talking about here?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?
That's great!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.
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?)
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.
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.
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.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.
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).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.
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.
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.
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.