Personal Log »

CPC cartridges: bank switching

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:

Address Size Type Function
0x0000 16K ROM Code
0x4000 16K RAM R/W Data
0x8000 16K RAM R/W Data
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 0x4000 to 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.

Would you like to discuss the post? You can send me an email!