After my thoughts about Amstrad CPC cartridges, I want to show how easy is bank switching in both CPC plus and Dandanator cartridges.
When the CPC boots from cartridge, our code will start executing at address
0x0000 and we have the first
16K bank mapped on that address (lower ROM, in case you want to check CPC documentation).
The strategy that I’m proposing is the following:
|0xC000||16K||RAM/ROM||Video memory/Mapped cart bank|
You may or may not use a back-buffer with hardware (e.g. in
0x8000), but considering that we can have all our read only data in ROM,
16K for just code is probably enough.
Thanks to the properties of the memory mapping on the CPC, when we map a bank of ROM into lower ROM (
0x0000) or higher ROM (
0xC000), reads will go to ROM and writes will go to RAM; and mapped ROM is only seen by the CPU. This is important because the gate array can only see RAM when drawing the screen.
This is great, because we can map a bank to higher ROM on
0xC000 and write to
0xC000, even having video memory active at that address, and we will be fine because those writes will go to RAM –and is RAM what will be displayed on screen, not the mapped ROM–.
So the idea is mapping on higher ROM any of the banks as we need them, and we can use that data with RAM from
0xffff, leaving on lower ROM the bank 0 with our code.
In the case of the CPC plus cartridges, there’s nothing to do, but in the Dandanator we need to configure the cartridge to support this behaviour. We can do it like this:
; SDCC syntax ; the byte pointed by iy will be overwritten di ld a, #0x8a .db 0xfd, 0xfd ld 0 (iy), a ei
We will run this once at the start of the game.
Then we need a bank set function that takes the bank number we want to map in higher ROM. Let’s look at the CPC plus cartridge:
; SDCC syntax ; defined as __z88dk_fastcall in C - parameter in l di ld a, l ; cart slots 0 - 31 start at 128 or #128 ld c, a ld b, #0xdf out (c),c ld bc, #0x7f80 out (c),c ei
And the equivalent code in the Dandanator:
; SDCC syntax ; defined as __z88dk_fastcall in C - parameter in l ; the byte pointed by iy will be overwritten di ld c, l .db 0xfd, 0xfd ld 0 (iy), c ei
Finally we need a function to unmap the higher ROM and have regular RAM on
0xC000. In CPC plus cartridges is done with:
; SDCC syntax di ld bc, #0x7f88 out (c),c ei
And in the Dandanator:
; SDCC syntax ; the byte pointed by iy will be overwritten di ld c, #32 .db 0xfd, 0xfd ld 0 (iy), c ei
It is as simple as that!
Of course that the Dandanator has more functionality than what I’ve shown here, but if we hide these differences behind some preprocessor directives (e.g.
ifdef), we can write the code once and generate both types of cartridges without any other changes –tip: look at my CPR tools to generate cartridges; using raw and padding flags gives you a Dandanator–.
And that’s all! This post is result of my own research and I don’t consider myself an expert, so if you find any issues with the text, please let me know.
Update: I added a comment in the code to remember that the opcode used by the Dandanator to process commands will overwrite a byte, so be sure that
iy is pointing to a safe memory address.