More Than 64k
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.
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.
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.
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.
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);
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 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.
In order to support calling routines located in banked memory, you will need to implement the l_farcall
support 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.
To be written See #641
- Overview
- Platform List
- Unsupported Platforms
- i8080/5 Support
- Homebrew hardware quickstart
- Retargetting
- Building the libraries
- Clang support
- Pragmas
- Adding to Classic
- Introduction
- Library Configuration
- CRT
- Header Files
- Assembly Language
- Library in Depth
- Embedded Platform
- Adding to NewLib
- Benchmarks
- Datatypes
- Debugging
- Decompression
- More than 64k
- Deficiencies
- Compiling Larger Applications
- Importing routines written in 8080 assembly mnemonics
- Using CP/M libraries in REL format with z88dk
- Writing optimal code
- Speeding up Compilation
- CMake usage