Skip to content
suborb edited this page May 8, 2024 · 15 revisions

Breaking the 64k barrier

The Z80 processor is an old processor, and it's based on an even older one - the Intel 8080. These two processors are 8 bit accumulator based and can access an address space of 65536 bytes (or 64k).

Quite early on it was realised that this simply isn't enough address space to implement a comprehensive operating system and leave sufficient user RAM for their own programs.

The solution to this is quite straightforward, have more ROM/RAM in the system and page it in when required. This trick isn't just used on the Z80 but also for the 6500 processors.

Named address spaces (classic/newlib sccz80 and zsdcc)

Both sccz80 and sdcc support a subset of the Embedded C standard named address spaces. Both of them use them for implementing bank switching. Some examples follow:

void setb0(void);            // The function that sets the currently active memory bank to b0
void setb1(void);            // The function that sets the currently active memory bank to b1
__addressmod setb0 spaceb0;  // Declare a named address space called spaceb0 that uses setb0
__addressmod setb1 spaceb1;  // Declare a named address space called spaceb1 that uses setb1

spaceb0 int x;          // An int in address space spaceb0
spaceb1 int ∗y;         // A pointer to an int in address space spaceb1
spaceb0 int ∗spaceb1 z; // A pointer in address space spaceb1 that points to an int in address space spaceb0

A functioning example for the +zx is in the classic examples directory - although the idea is valid for newlib as well.

__banked function calls (classic sccz80 and zsdcc)

The named address space feature allows the data address space of the z80 to be extended to >64k, where as __banked functions allow the creation of projects that contain more than 64k code.

The general principle behind the feature is that the memory space is divided into a common area containing library and other support routines and a pageable segment where code and const data is paged in. An example implementation for (classic) +msx +gb +zx +sms +cpc can be found in the classic examples directory.

Far Memory (classic - sccz80)

Abstracted Handling

z88dk supports the concept of a __far pointer. This a type qualifier that can be prefixed to any pointer type, and as far as the programmer is concerned, handling of them should be transparent.

The far pointers used by z88dk are 24 bits long i.e. 3 bytes, this enables the theoretical access of 16Mb of memory. The far pointer allows for a flat memory model - i.e. they are treated as "normal" 24 bit number by the compiler. Far pointers are always handled in ''ehl'' where e contains bits 16-23 and hl bits 0-15 of the pointer. If e holds 0 then hl is taken to be an absolute address in the current memory configuration.

Compiler support

Out of the two compilers with z88dk, only sccz80 supports far pointers, at present there are implementations for +msx, +z88, +zx as well as the +test target.

A far pointer can be declared using the left-associative __far decorator, for example:

char *__far string;
void (*__far function_pointer)(int param);

Library support

A collection of common str* and mem* functions are available. The functions are suffixed f to indicate that they use/return __far pointers.

Additionally, the printf family support the %S format specifier to print strings located in far memory. To just print the pointer value use %lp. The functions snprintff and snprintff (note the double f!) will do a formatted print to a __far pointer.

Memory can be allocated with malloc_far, calloc_far, realloc_far and freed with free_far. The maximum allocatable block is 65534 bytes.

To enable the farheap, supply the option -pragma-define:CLIB_FARHEAP_BANKS=nn where nn is the number of pages to allocate to the heap. For most targets this is measured in 16k pages. The following pragmas are available to configure the behaviour of the heap:

Implementing Far memory

Implementing of support for far memory is of course machine specific and depends on the target machines memory model. Z88dk makes certain assumptions that routines are available, in order to have a functioning far memory system for your target the functions detailed in this file will need to be implemented.

; Get the initial banking setting
; Exit: a = current bank (or entry value for __far_end)
; Uses; none
__far_start:

; Restore the initial banking setting
; Entry: a = bank to page in
; Uses: none
__far_end:

; Page in the physical memory associated with the ebc pointer. Set hl to point to address
; Entry: ebc = logical address
;         a' = local memory page
; Exit:   hl = physical address to access (bank paged in)
;        ebc = logical address
; Corrupts: d,a
; Preserves: a'
__far_page:

Implementations of these functions are available for several targets so can be used for reference when porting to new targets.

Far pointer calling

In order to support calling routines located in banked memory, you will need to implement the l_farcallsupport function. This takes ehl as the far address to call.

Note that for some targets (eg +zx), the heap and #pragma bank data occupy a different address spaces, so pointers are not convertible.

Trampoline function calls (sccz80 and zsdcc)

To be written See #641

Clone this wiki locally