As I mentioned before, I am rearranging my notes on Commodore 64 cartridges, especially on bank switching circuitry used by exotic cartridges (i.e. neither Normal nor Ultimax ones).
I plan to provide some schematic diagram to include in my book, but, as I had done in my article about Vorpal later, I think it’s important to understand what the problem is, rather than simply providing the schematics of the hardware that solves the problem. Furthermore, as I did in my article about sync signals available in Commodore disk drives, I plan to provide hardware information and relevant registers, side by side.
Let’s get into it.
We know that when either 8KiB or 16KiB of external EPROM/EEPROM are provided by a Normal or Ultimax cartridge, the Commodore 64 can map each 8KiB segment to one of the memory ranges $8000-$9FFF, $A000-$BFFF, or $E000-$FFFF.
Not just any combination is allowed, but what’s really important is that the Commodore 64 can only map one 8KiB segment of external memory to $8000, $A000, or $E000, and it can only map a 16KiB segment of external memory to base address $8000.
You might want to refer to this post on World of Jani for the values of /GAME and /EXROM used by Normal and Ultimax cartridges. The Commodore 64 Programmer’s Reference guide also provides a table with relevant information.
Here’s a summary:
- /EXROM = 1 and /GAME = 0 (Ultimax mode)
/ROML optionally enables the mapping of 8KiB to $8000 and
/ROMH enables the mapping of 8KiB to $E000
- /EXROM = 0 and /GAME = 1
/ROML enables the mapping of 8KiB to $8000
- /EXROM = 0 and /GAME = 0
For a double-chip cartridge:
/ROML enables the mapping of 8KiB to $8000 and
/ROMH enables the mapping of 8KiB to $A000
For a single-chip cartridge:
/ROML AND /ROMH enable the mapping of 16KiB to $8000
A summary of the above mentioned table is also provided below:
LHGE LHGE LHGE LHGE LHGE
1111 1110 0100 1100 XX01
A000 ROMH ROMH -
8000 ROML ROML ROML
L = /LORAM
H = /HIRAM
G = /GAME
E = /EXROM
As for registers, bits 0 and 1 at location $1 on the Commodore 64 are mapped to the /LORAM and /HIRAM signal respectively and eventually routed to /ROML and /ROMH: I will cover this in more detail in a future post on the subject.
For an 8KiB block of memory, thirteen bits of addressing are required in order to select each byte in the block itself: this is the reason only address lines A0-A12 of the address bus are connected to the memory chip in a cartridge. In fact, we can’t connect the lines A14-A15 of the Commodore 64 address bus directly to the corresponding address lines of an external memory chip, because they would select addresses in the external memory chip that are not mapped to locations that the Commodore 64 can access, being these beyond the 16KiB limit. In reality even A13 is never connected because /ROML or /ROMH are used instead to drive the corresponding line of the external memory. So I hope it’s now clear why address lines A13-A15 are not used by cartridges.
The problem is: how do we allow a Commodore 64 to access external memory chips when these are larger than 16KiB? A good quality game with a lot of graphic elements and tunes would hardly fit in just 16KiB of memory. It’s more likely that a good quality multi-level game would require 200KiB or more. We could also be wanting to use two 128KiB memory chips, or one 256KiB chip, or even a 512KiB chip.
Fortunately, although we can only map one or two 8KiB sections of external memory chips at any given time, we do have options when it comes to driving lines A13 and above of the external chip (or chips) and selecting exactly the 8KiB block (or blocks) we want to map.
The fact that we have options means that different vendors came up with different schemes for exotic cartridges.
Let’s take Ocean, for example. Their strategy was simple: use the data bus to decide the state of the lines A13 and above for the memory chip (or chips) they mounted in their cartridges. Obviously the data bus is used for reading data out of the cartridge memory, so how can we use it to set the state of lines A13 and above?
Well, as well as connecting data lines D0 and above to the memory chip for reading out its contents at a given location, the cartridge hardware also connected some of these lines to an 8-bit register (a.k.a. latch). Such register would sample and hold the value of data bus lines, presenting their state on its outputs directly connected to A13 and above, if and only if the Commodore 64 wanted to switch to a different memory bank. The Commodore 64, in the case of Ocean cartridges, uses D0 to set A13, D1 to set A14, and so on.
The next question to understand is then: how does a Commodore 64 communicate to an Ocean cartridge that it wants to perform a bank switching? Loading the data bus with the desired state of lines A13 and above is not enough; it would have to cause the latch to sample and hold. Well, looking at any Ocean cartridge circuitry, it’s pretty simple to establish that:
- Phi2 has to be high, meaning that it’s the CPU that’s controlling the bus
- /IO1 has to be low, meaning that the Commodore 64 is addressing a location in $DE00-$DEFF
So, in short, all the Commodore 64 has to do when it wants to switch bank is to set the value for A13 and above in bits 0 and above at e.g. $DE00; the hardware circuitry of the Commodore 64 will make sure that /IO1 is briefly pulled low at some point when Phi2 is also high. That’s exactly what the cartridge’s own circuitry is waiting for to sample and hold the value on the data bus and use it to set lines A13 and above.
This means that, as an example, for a single-chip 256KiB Ocean cartridge, we can map the bottom 8KiB to $8000 by setting $DE00 to 0. The 8KiB after these would be mapped by setting $DE00 to 1, and so on.
That’s, essentially, what the Lua script for single-chip Ocean cartridges used my Commodore 64 Cartridge Dumper does:
-- Ocean type B (single chip) dumping definition file
-- for the Commodore 64 Cartridge Dumper client
-- (C) 2019-2021 Luigi Di Fraia
-- Supported titles:
-- All Ocean titles excluding "Robocop 2", "Shadow of the Beast", and "Space Gun"
-- Bank selection circuitry uses:
-- 128 KiB cartridges (all known titles): bits 0-3 at $DE00 and ROML (single 128 KiB chip with A16 on pin 22, rather than the /OE signal)
-- 256 KiB cartridges (just "Chase H.Q. II"): bits 0-4 at $DE00 and ROML (single 256 KiB chip)
-- 512 KiB cartridges (just "Terminator 2"): bits 0-5 at $DE00 and ROML (single 512 KiB chip)
-- Calculate the number of 8 KiB banks to dump
local banks = size_kb / 8
local b = 0
-- Mapping is at $8000-$9FFF for all banks
while b < banks do
b = b + 1
Stay tuned for more!