The C128 Cartridge functionality is different than in the C64. Maximum size has been doubled to 32K and an EPROM socket (U36) has been provided inside the machine in addition to the cartridge port.

An external cartridge is referred as “External(Function ROM)” and the internal EPROM socket is referred as “Internal(Function ROM)”. This will focus on how to create an external cartridge.

A 8-bit machine can only “see” 64K at a time, and the same goes for the C128.

There are two areas in the C128 memory where cartridges can be located; the MID ($8000-$BFFF) and HIGH ($C000-FFFF). A cartridge can be started up in either these 16K areas or single 32K cartridge can be used. It is possible to have both an internal “cartridge” and an external cartridge installed, but the external has preference over the internal.

C128 memory is managed by a chip called the Memory Management Unit(MMU) and does not use the PLA(GAME/EXROM lines) like the C64.

MMU is controlled by its primary registers at $D500- and its secondary registers at $FF00-. The secondary registers are available to make it possible to switch out the I/O area at $D000-DFFF and still be able to access the MMU. This creates a memory “hole” at $FF00-$FF04 which can not be used for code.

Note that both cartridge areas (MID and HIGH) are always switched in together. If you set up a 16K cartridge in MID area and want to access Kernal routines, you will need to bank in Kernal at the HIGH space.

 

Cartridge Autostart
When a C128 is booted, a cartridge presence is checked. First it checks if GAME or EXROM lines are low. If this is the case, a C64 mode cartridge is connected and the machine starts in C64 mode. Otherwise the C128 memory is checked for an identifier(“CBM”) string at $8007 and $c007, this is done both for the external and internal slots. The banks are checked in the following order: External Mid, External High, Internal Mid and Internal High.

 

Code at $E27A- is used to verify if a cartridge is inserted:

 

Cartridge/ROM identifier byte
Position $8006 is the identifier byte for the cartridge which determines if the cartridge should be autostarted, it has three options:

$00 : Do not autostart ROM/cartridge.
$01 : Autostart immediately using the cartridge cold-start vector.
>$01 : Autostart through BASIC cold-start sequence ($FF commonly used for >$01).

BASIC will not be initialized when having $01 as identifier byte.

 
 

16K Cartridge code example at $8000(MID) with Kernal ROM banked in.

 

16K Cartridge example at $8000(MID) with BASIC code copied and executed.

This example will copy BASIC code from the cartridge ROM to BASIC_START ($1c01) and RUN it. Identifier byte is set to $FF, which initializes BASIC before running the cart/ROM.
Pay attention to the address basic_end = $1c17 in the example. It is the size of the BASIC program and will cause trouble with the BASIC interpreter if not set properly. It can be automated with a “better” copy-routine but I wanted to write it out in the example.

 

 
Test the code(s) above with WinVICE:

 


C128 16K Cartridge $8000-$BFFF (MID)
 

 
You can add another 27128 and connect RH to /OE on that chip to get 32K.

 


Update 2021-07: Clarified details about the cartridge/ROM identifier byte and added second example.