Banked Memory Manager for Commander X16
Version 0.11
Author: Jimmy Dansbo
Note that this documentation can change at any time.
Table of Contents
- Overview
- Memory Bank Header
- Memory Area Header
- Using in your project
- Error codes
- Functions
- Zero Page
- Lowram Functions
The Banked Memory Manager library is designed to ease using banked memory in programs and libraries designed for the Commander X16.
The library is designed to handle banked memory even if it is loaded into a memory bank itself. It is able to allocate memory in any RAM bank, except bank 0 and it will keep track of remaining memory in a bank as well as pointers to allocated memory areas.
The library utilizes handles to let the user easily free an allocated memory area again.
NOTE: When a memory area is freed, it can either be done by marking the freed memory as dirty or by defragmenting the remaining memory. When defragmentation is chosen, it is important to get new pointers with the mm_get_ptr function as addresses may have changed.
The mm_get_ptr function will always return the correct address of a memory area identified by handle.
The library also makes several bank safe functions availabe to load, store and copy inside banked memory.
Any memory bank, except bank 0, can be initialized for use with the library.
When a bank is initialized, the first 36 bytes, starting at $A000 are claimed for the memory bank header.
The header has the following layout.
| Address | Size | Description |
|---|---|---|
| $A000 | 2 bytes | Next available memory address |
| $A002 | 2 bytes | First allocatable address |
| $A004 | 32 bytes | Handle bitmap |
This means that the library can use the remaining available space in an otherwise used memory bank, provided the first 36 bytes are free to initialize the bank.
When a memory area is allocated, it takes up 4 bytes more than the requested size.
These 4 bytes are used for the memory area header:
| Offset | Size | Description |
|---|---|---|
| $00 | 2 bytes | Address to next memory element |
| $02 | 1 byte | Handle ID in current bank |
| $03 | 1 byte | Checksum of header |
If bit 6 is set in high-byte of the address to next element, it means that the current area is marked as dirty.
The library is designed to be used in CC65 assembler projects. To include the library in your own project, you need memman.inc & memman.o
When linking your project with the banked memory manager library, you need a custom cc65 configuration file. The configuration file must contain definition of two segments MEMMAN & MMLOWRAM like this:
SEGMENTS {
...
MEMMAN: load = HIRAM, type = ro;
MMLOWRAM: load = HIRAM, type = ro, define = yes;
...
}
The MMLOWRAM segment must have the define=yes option to ensure the linker can check the size of the segment.
In order to use the functions and constants in the library, you should include the memman.inc file in your own source files.
When linking your project you simply link it against the memman.o file as well.
ld65 -C yourproject.cfg memman.o yourproject.asm -o yourproject.prg
| Code | Name | Description |
|---|---|---|
| $01 | MM_ERR_ZERO | Zero bytes allocation requested |
| $02 | MM_ERR_NOSPACE | Not enough available memory in bank |
| $03 | MM_ERR_NOHANDLE | No available handles in current bank |
| $04 | MM_ERR_HANDLE_NOTFOUND | Requested handle was not found |
| $05 | MM_ERR_CORRUPT_HDR | Memory area header has been found corrupted |
Purpose: Initialize Banked Memory Manager
Communication registers: A, Y & X
Description Initialize the banked memory manager library, copy functions to low-ram and initialize the first memory bank for usage by the library.
| Registers | Purpose |
|---|---|
| A | First ZeroPage address to use as pointer |
| Y | Second ZeroPage address to use as pointer |
| X | Memory bank to initialize |
The two ZeroPage pointers will be used by the library to allocate memory and copy memory within banks.
Before calling mm_init the first ZeroPage pointer (zp1) must containt the address to 256 bytes of lowram that will be used by the library.
The second ZeroPage pointer (zp2) must contain the first available address in the current RAM bank. If it is an empty RAM bank, the address should be set to $A000.
; Use golden memory $0400 as lowram
lda #<$0400
sta $30 ; Store low-byte in zp1-low
lda #>$0400
sta $31 ; Store high-byte in zp1-high
; Tell the library that it is an empty bank
lda #<$A000
sta $32 ; Store low-byte in zp2-low
lda #>$A000
sta $33 ; Store high-byte in zp2-high
lda #$30 ; Use $30,$31 as ZP1
ldy #$32 ; Use $32,$33 as ZP2
ldx #$02 ; Initialize RAM bank 2
jsr mm_init
Purpose: Initialize a memory bank for use by the library.
Communication registers: A, Y & X
Preserves: X
Description Initialize a memory bank by setting the 4 first bytes of the bank to the first available memory address, followed by 32 bytes for bank handle bitmap. See Memory Bank Header
| Registers | Purpose |
|---|---|
| A | Low-byte of next free address |
| Y | High-byte of next free address |
| X | RAM bank to initialize |
Purpose: Return the amount of memory available in a specified memory bank
Communication registers: A, Y, X
Preserves: X
Description Calculates and returns the amount of free memory in a specified bank. This does not include memory areas that are marked as dirty. In order to ensure an accurate amount is returned, the memory should be defragmented first.
| Input | Purpose |
|---|---|
| X | Bank to check free space for |
| Output | Description |
|---|---|
| A | low-byte of available memory |
| Y | high-byte of available memory |
Purpose: Allocate a memory area
Communication registers: A, Y, X & C
Preserves: X
Description Allocates the specified amount of memory in a bank.
If a dirty memory area is found to be large enough to accommodate the requested size, it will be re-allocated and the amount of available memory will stay unchanged.
| Input | Purpose |
|---|---|
| A | low-byte of bytes to allocate |
| Y | high-byte of bytes to allocate |
| X | RAM bank to allocate memory in |
| Output | Description |
|---|---|
| C | Carry set on error |
| A | low-byte of handle or error code on error |
| Y | high-byte of handle |
Purpose: Return address of memory area identified by handle
Communication registers: A, Y, X & C
Description Returns the actual address of a memory area identified by handle.
This function should be called to ensure correct address of a memory area if memory defragmentation has been performed.
Only the handle of an allocated memory area is guaranteed to stay the same.
| Input | Purpose |
|---|---|
| A | low-byte of handle |
| Y | high-byte of handle |
This function can be called in a different way to look for dirty memory areas instead of handles. This is meant to be used internally for defragmenting the memory.
| Input | Purpose |
|---|---|
| A | Set to 0 to look for dirty memory |
| X | RAM bank |
| Y | 0 if searching from beginning of RAM bank |
In any case, the output of the function is the same.
| Output | Description |
|---|---|
| C | Carry set on error |
| A | low-byte of address or error code on error |
| Y | high-byte of address |
| X | RAM bank |
Purpose: Free a memory area
Communication registers: A, Y & C
Description Free a previously allocated memory area.
The function can defragment the memory right away or simply mark it as dirty.
Dirty memory may be re-used if it is large enough to contain a new request.
A request to allocate memory will use the first dirty memory area large enough to hold it, even if it means using a memory area much larger than the requested size.
When memory is defragmented, it means that allocated memory get's new pointers and it is therefore very important to get the correct pointers through a call to mm_get_ptr.
| Input | Purpose |
|---|---|
| A | low-byte of handle |
| Y | high-byte of handle |
| C | clear=defrag memory, set=mark only |
| Output | Description |
|---|---|
| C | Carry set on error |
| A | Error code on error |
Purpose: Defragment a memory bank
Communication registers: A, X & C
Description Defragment a memory bank, freeing up previously allocated memory and in the process moving still used memory areas.
Note: After defragmentation, it is important to call mm_get_ptr to ensure your code has the correct memory addresses.
| Input | Purpose |
|---|---|
| X | RAM bank to perform defragmentation on |
| Output | Description |
|---|---|
| C | Set on error |
| A | Error code on error |
Purpose: Get the size of a memory area identified by handle
Communications registers: A, Y, X & C
Description Get the actual size of an allocated memory area, minus the 4 bytes for header.
This can be used to check if an allocated memory area takes up more space than was actually requested. This could happen if a dirty memory area has been re-allocated.
| Input | Purpose |
|---|---|
| A | Low-byte of handle |
| Y | High-byte of handle |
| Output | Description |
|---|---|
| C | Set on error |
| A | Low-byte of size or errorcode if C set |
| Y | High-byte of size |
Purpose: Install banked ISR
Communication registers: A, Y & X
Preserves: X
Description Installs an interrupt service routine located in banked RAM. When the banked ISR does a normal rts, execution will continue to the default interrupt handler
| Input | Purpose |
|---|---|
| A | low-byte of address for banked ISR |
| Y | high-byte of address for banked ISR |
| X | RAM bank of the banked ISR |
Purpose: Restore previous ISR
Communication registers: none
Uses: A
Description Remove the banked interrupt service routine and restore the original interrupt handler.
A few functions are exported that are only used to manipulate the Zero Page addresses used by the library.
Purpose: Update the ZeroPage pointers used by the library
Communication registers: A & Y
Description Change the ZeroPage address used by the library. This function is part of the library initialization, but can be called seperately if the user needs to change the ZeroPage addresses used by the library.
| Registers | Purpose |
|---|---|
| A | First ZeroPage address to use for pointer (zp1) |
| Y | Second ZeroPage address to use for pointer (zp2) |
Purpose: Read the value/address currently stored in ZP1
Communication registers: A & Y
Description Returns the values stored in the first Zero Page Pointer that was provided to the library.
| Output | Description |
|---|---|
| A | low-byte of ZP1 |
| Y | high-byte of ZP1 |
Purpose: Read the low-byte of ZP1
Communication registers: A
Description Returns the low-byte stored in the first Zero Page pointer that was provided to the library
| Output | Description |
|---|---|
| A | low-byte of ZP1 |
Purpose: Read the high-byte of ZP1
Communication registers: A
Description Returns the high-byte stored in the first Zero Page pointer that was provided to the library
| Output | Description |
|---|---|
| A | high-byte of ZP1 |
Purpose: Read the value/address currently stored in ZP2
Communication registers: A & Y
Description Returns the values stored in the second Zero Page Pointer that was provided to the library.
| Output | Description |
|---|---|
| A | low-byte of ZP2 |
| Y | high-byte of ZP2 |
Purpose: Read the low-byte of ZP2
Communication registers: A
Description Returns the low-byte stored in the second Zero Page pointer that was provided to the library
| Output | Description |
|---|---|
| A | low-byte of ZP2 |
Purpose: Read the high-byte of ZP2
Communication registers: A
Description Returns the high-byte stored in the second Zero Page pointer that was provided to the library
| Output | Description |
|---|---|
| A | high-byte of ZP2 |
Purpose: Store a new value/address in ZP1
Communication registers: A & Y
Description Update the first Zero Page pointer (zp1) with a new value/address.
| Input | Purpose |
|---|---|
| A | low-byte of new value/address |
| Y | high-byte of new value/address |
Purpose: Store new low-byte in ZP1
Communication registers: A
Description Update the low-byte of the first Zero Page pointer with a new value
| Input | Purpose |
|---|---|
| A | low-byte of new value |
Purpose: Store new high-byte in ZP1 Communication registers: A
Description Update the high-byte of the first Zero Page pointer with a new value
| Input | Purpose |
|---|---|
| A | high-byte of new value |
Purpose: Store a new value/address in ZP2
Communication registers: A & Y
Description Update the second Zero Page pointer (zp2) with a new value/address.
| Input | Purpose |
|---|---|
| A | low-byte of new value/address |
| Y | high-byte of new value/address |
Purpose: Store new low-byte in ZP2
Communication registers: A
Description Update the low-byte of the second Zero Page pointer with a new value
| Input | Purpose |
|---|---|
| A | low-byte of new value |
Purpose: Store new high-byte in ZP2 Communication registers: A
Description Update the high-byte of the second Zero Page pointer with a new value
| Input | Purpose |
|---|---|
| A | high-byte of new value |
There are several lowram functions made available by the library. They are located in the memory area that was made available to the library at initialization.
To call a lowram function, the function offset is simply added to the start address of the lowram area.
jsr lowram+MM_LDA_BANK_OFS
The functions are also exported by the library so if your code is running in the same bank as the library, you can simply call the functions directly.
Purpose: lda from banked address
Communication registers: A, X & Y
Depends: zp1 pointer
Preserves: X, Y and RAM bank
Offset: $12
Offset constant: MM_LDA_BANK_OFFS
Description Reads a single byte from banked memory specified by first ZeroPage pointer into register A
| Inputs | Purpose |
|---|---|
| zp1 | ZeroPage pointer to address to read from |
| X | RAM bank to read from |
| Y | Offset from pointer to read from |
| Output | Description |
|---|---|
| A | Value read from banked memory |
| Flags | Set according to A |
Purpose: lda & ldy from banked address
Communication registers: A, Y & X
Depends: zp1 pointer
Preserves: X & RAM bank before call
Offset: $21
Offset constant: MM_LDAY_BANK_OFFS
Description Reads two bytes from banked memory specified by first ZeroPage pointer into registers A & Y
| Inputs | Purpose |
|---|---|
| zp1 | ZeroPage pointer to address to read from |
| X | RAM bank to read from |
| Y | Offset from pointer to read from |
| Output | Description |
|---|---|
| A | low-byte of value read from banked memory |
| Y | high-byte of value read from banked memory |
Purpose: lda, ldy & ldx from banked address
Communication registers: A, Y & X
Depends: zp1 pointer
Preserves: RAM bank before call
Offset: $33
Offset constant: MM_LDAYX_BANK_OFFS
Description Reads three bytes from banked memory specified by first ZeroPage pointer into registers Y, X & A
| Inputs | Purpose |
|---|---|
| zp1 | ZeroPage pointer to address to read from |
| X | RAM bank to read from |
| Y | Offset from pointer to read from |
| Output | Description |
|---|---|
| A | low-byte of value read from banked memory |
| Y | mid-byte of value read from banked memory |
| X | high-byte of value read from banked memory |
Purpose: sta to banked address
Communication registers: A, X, Y
Depends: zp1 pointer
Preserves: A, X, Y and the RAM bank before call
Offset: $48
Offset constant: MM_STA_BANK_OFFS
Description Store a value from register A to banked memory pointed to by first ZeroPage pointer.
| Inputs | Purpose |
|---|---|
| zp1 | ZeroPage pointer to address to write to |
| X | RAM bank to write to |
| Y | Offset from pointer to write to |
| A | Value to write |
Purpose: sta & sty to banked address
Communication registers: A, X & Y
Depends: zp1 pointer
Preserves: A, X, Y & RAM bank before call
Offset: $56
Offset constant: MM_STAY_BANK_OFFS
Description Store two bytes to banked memory specified by first ZeroPage pointer from registers A & Y
| Inputs | Purpose |
|---|---|
| zp1 | ZeroPage pointer to address to write to |
| X | RAM bank to write to |
| A | low-byte to write to banked address |
| Y | high-byte to write to banked address |
Purpose: Copy memory within banks
Communication registers: A & X
Depends: zp1, zp2 pointers and 2 first bytes of lowram area
Preserves: X & RAM bank
Offset: $6E
Offset constant: MM_BANK_COPY_OFFS
Description Copies memory to- and from any RAM bank and conventional RAM. The funciton is NOT aware of IO ports such as VERA data ports.
The two first bytes of the lowram area provided to the library must be filled with the amount of bytes that needs to be copied prior to calling this function.
If the source- and destination RAM bank are equal, the RAM bank will be set once before copying data. This enable to function to copy within the same RAM bank or to copy to- and from conventional memory and banked memory.
| Inputs | Purpose |
|---|---|
| zp1 | Fist ZeroPage Pointer is source address |
| zp2 | Second ZeroPage Pointer is destination address |
| lowram | The two first bytes of lowram is the number of bytes to copy |
| A | Source RAM bank |
| X | Destination RAM bank |