Memory on the C128 is managed by the MMU into 15 banks. The most common banks are:

Bank 0

Almost purely RAM from block 0. The exceptions are addresses $0000–$0001, which are the 8502 microprocessor's on-chip I/O port registers and addresses $FFOO–$FF04, where MMU (memory management unit) chip registers are always seen, regardless of the bank configuration. Your machine language program will usually reside in this bank, and BASIC program text will also be stored here. As long as you don't try to do I/O or call Kernal routines, it's also a convenient bank for ML programming.

Bank 1

RAM from block 0 in addresses $0002–$0400. Above that, the bank consists of RAM from block 1 (except for the MMU chip registers at $FF00–$FF04). Use this configuration to read or change BASIC variables, arrays, and strings

Bank 14

Very similar to bank 15, but this configuration contains the character generator ROM at locations $D000–$DFFF instead of the I/O chip registers.

Bank 15

Very convenient for ML programming. It has RAM from block 0, BASIC and Kernal ROMs, and the I/O chip registers

Accessing Data Between Banks

Your program may reside in one place, but may need access to information from an area that isn't visible in the current configuration. To do this, you may use one of the following Kernal routines:

INDFET (INDirect FETch) = $FF74
INDSTA (INDirect STore) = $FF77
INDCMP (INDirect CoMPare) = $FF7A

Note that these routines are in Kernal ROM. If you call them, the Kernal must be visible, and that usually means that you're in bank 15. Before calling the routine, you must set up an indirect address somewhere in the zero page of memory to be a pointer to the address you wish to access. Then you must tell the routine where this indirect address is located, and set the processor's Y register with the offset from the address in the pointer to the one you actually wish to access. (Load Y with $00 if you wish to access the exact address in the pointer.)

Here's an example. Suppose you wish to read the contents of address $2468 within bank 1 using the Kernal INDFET routine. The first job is to pick an indirect address somewhere in page zero to serve as a pointer. Locations $FB–$FC are free, so the desired address can go there (LDA #$68: STA $FB: LDA #$24: STA $FC). In this case we set Y to zero (LDY #$00). The bank number goes into the X register (LDX #$01 for bank 1). Finally, we must tell the INDFET routine where to find the indirect address pointer we have set up. This is done by loading the accumulator (A register) with the pointer address:LDA #$FB. Now we can call INDFET with JSR $FF74. Upon return from the ROM routine, the accumulator will hold the value read from address $2468 in bank 1.

The procedure for using INDSTA or INDCMP to store or compare a value in another bank configuration is similar, except that it takes a bit more work to indicate the direct address location. Suppose you want to store the value 7 into location $CDEF in bank 0. It could be done this way: Begin by storing the target address in $FB–$FC (LDA #$EF: STA $FB: LDA #$CD: STA $FC). Next, tell the system where the indirect address pointer is located by storing the pointer address directly in the INDSTA routine, at address $02B9 (LDA #$FB: STA $02B9). To use INDCMP comparison rather than INDSTA for a store, you should store the indirect pointer address in $02C8. Set up the Y index (LDY #$0) and put the bank number in X (LDA #$00 for bank 0). Now you can load the byte value to be stored into the accumulator (LDA #$07) and complete the store operation with JSR $FF77.

After having done the selected task, these ROM routines return you to the same configuration that was set up when the routine was called. By the way, if you're wondering if there is a proper bank for addresses such as $FA or $02B9, don't worry. Addresses below $0400 are always seen in block 0 RAM in normal operation.

If you're using the bank 15 configuration, a shortcut is available for storing data in bank 0. Remember that bank 0 and bank 15 see the same RAM (block 0) in all addresses below $4000. In the bank 15 configuration, reading the contents of a ROM address ($4000–$CFFF or $E000–$FFFF) always returns the value from the corresponding ROM location, but writing to the address actually causes the value to be stored in the corresponding location in the underlying block 0 RAM. Thus, when you are programming in bank 15 (or bank 14), it's not necessary to use INDSTA to place values in bank 0 unless you need access to a RAM address under the I/O block ($D000–$DFFF). For instance, the example above could have placed a value in location $CDEF of bank 0 simply using STA $CDEF. However, the INDFET and INDCMP routines are still required for reading or comparing bank 0 locations from the bank 15 configuration.