5. Data storage¶
This chapter discusses controlling data placement and its impact on application efficiency.
The 65816 has an address range of 16MB code and data space. Even though the address space 16MB, it can be seen as a sequence of 64K address ranges. While address calculations can cross 64K bank boundaries, this is not the most efficient way to utilize the 65816. Additionally, a single function must fit within a single 64K bank.
5.1. 64K banks¶
There are 256 such 64K banks on the 65816 numbered 00 to FF
hexadecimal. The first bank 00 is special in that the system
stack and the direct page must be located in it. Certain vectors, such
as the reset and interrupt vectors also reside in bank 00.
Note
Do not confuse the term bank here with a banked memory system where it often is used to refer to an address range which acts as a window to a larger memory area. A bank in the 65816 refers to a 64K memory area.
Direct page¶
The direct page is 256 byte large and can be located anywhere in the
first 64K of memory. The compiler reserves 32 bytes from the direct page for
pseudo registers, forming the tiny area which uses the tiny
attribute. The 65816 has several efficient addressing modes related
to this area, providing shorter instructions compared
to other areas.
The DP (direct page) register is 16 bits and points to the start
of the direct page. This register is set up before the main()
function is called and is expected to remain fixed while the
application is running.
Near bank¶
The near bank is the bank pointed to by the data bank register. This is a single 64K bank that is in the middle of addressing efficiency.
The DB or data bank register is an 8 bit register that points to
the active near bank. This register is set up before the main()
function is called and is expected to remain fixed for the duration
of the execution.
In the Small data model the near bank must be the same bank as the
CPU stack, which is always in bank 00.
This is because the default pointer is 16 bits wide in the Small
data model and it must be possible to point to a data object either on
the stack or the default static storage area.
5.2. Ways to store data¶
Data can be allocated as auto, static, or on the heap. Variable scope and the need for allocation during runtime determine placement.
As a general rule, use auto-allocated variables whenever possible. This offers the compiler maximum flexibility for resource allocation closest to the core, which typically results in the most efficient data access.
Auto variables¶
Auto variables include function parameters and local variables not
defined with static. The compiler attempts to allocate these in
processor registers or direct page pseudo registers; otherwise, the stack is used.
Auto variables are allocated only when used. Their registers can be reused for other auto variables or temporary data once no longer needed. This reuse also applies to stack locations.
Auto variables can have multiple live ranges; a variable with the same name might be used in distinct parts of a function. Internally, these are treated as different variables and may be allocated to various locations, potentially not existing between live ranges.
All auto variables associated with a function are deallocated upon function exit.
Note
If you take the address of an auto variable, you can pass its pointer to other functions, which is useful for temporary storage. However, avoid using such pointers outside their scope, as auto variables are deallocated on function exit. Be aware that taking an address allocates the variable on the stack, potentially increasing access cost compared to other auto variables.
Static variables¶
Global, module, or function static variables are allocated in global memory, occupying space for the application’s duration.
Their visibility varies. A global variable is universally visible but
requires extern for use. A module static variable, declared with
static at file scope, is visible within one compilation unit.
A static local variable within a function’s scope is only visible
there, retaining its value across function calls. Use static to
differentiate it from an auto variable.
Dynamically allocated¶
A dynamically allocated variable is retrieved from a heap using the malloc function. This is useful when the required data size is unknown at program startup.
Note
Dynamically allocated variables are a potential problem in memory constrained systems if the program is left running for a long time due to heap fragmentation.
5.3. Address spaces¶
The compiler provides multiple address spaces, which are addressable memory areas with specific properties:
Address width for pointers
Width of the associated integral index type
Different instruction sequences for accessing various address spaces
An extension keyword or type attribute name
Section names tied to the address space for linking control
Address space attributes are always active in the compiler.
The Calypsi C compiler tool chain provides five address spaces, tiny (direct page), near (64K), far, far24 and huge. The latter three can address the full 16MB range where far and far24 limit object size to 64K minus one.
All unqualified pointers are either 16 or 24 bits wide, depending on the selected data model.
Tiny address space¶
The tiny address space is 256 bytes of memory located somewhere
in the first 64K of memory. It has an address range 0x00-0xff.
This corresponds to the direct page on the 65816.
A tiny pointer is 16 bits wide and occupies two bytes when stored in memory. While a single byte would suffice, the compiler uses two bytes for efficiency.
Due to its 256-byte size and sharing with pseudo registers, the tiny area has limited storage. However, careful use can lead to a more efficient application.
Near address space¶
The near address space is a single 64K bank. This address space is not available in the Small data model. The bank can be located to any 64K bank and is fixed to that bank.
A near memory pointer is 16 bits and occupies two bytes when stored in memory.
Far address space¶
The far address space covers the entire 16MB address range. The only limitation on far objects is that they must be at most 64K (minus one byte) large.
A far memory pointer is 24 bits and occupies four bytes when stored in memory.
Far24 address space¶
The far24 address space cover the entire 16MB address range. The only limitation on far24 objects are that they must be at most 64K (minus one byte) large.
A far24 memory pointer is 24 bits and occupies three bytes when stored in memory.
Note
The only difference between far24 and far is that far24 is stored in memory without 32 bits padding. This is useful when hardware or API definitions require unpadded pointer storage. The generated code to store a 24 bits pointer is somewhat larger compared to a 32 bits pointer on the 65816.
Huge address space¶
The huge address space covers the entire 16MB address range. The maximum size of an object is 16MB (minus one byte).
A huge memory pointer is 24 bits and occupies four bytes when stored in memory.
Summary¶
The following table summarizes the available address spaces.
Memory type |
Keyword |
Address range |
Pointer size |
Index type |
|---|---|---|---|---|
Direct page |
|
|
16 bits |
|
Near |
|
|
16 bits |
|
Far |
|
|
32 bits |
|
Far24 |
|
|
24 bits |
|
Huge |
|
|
32 bits |
|
Syntax¶
An address space attribute keyword such as __tiny is
a type qualifier. Syntactically it works the same as other C language
defined type qualifier, e.g. const and volatile.
The following declaration defines four variables in the Direct page address space:
__tiny int a, b;
int __tiny c, d;
The __tiny type qualifier applies to the closest type,
int in this example. In C, the order between type qualifiers and
types does not matter; they convey the same meaning.
Pointers¶
A pointer in C points to something in memory. Both the pointer itself
and what it points to have types. As an example, the type char *
is a pointer to a char.
Pointer types are easier to understand if you read them from right to
left. The * is a pointer, so char * reads from right to left
as “pointer to char”. This order of reading is especially useful when
you mix in type qualifiers in pointer types, as it makes it a lot
easier to read and understand what the type means.
int __attribute__((tiny)) * p1;
long * __attribute__((tiny)) p2;
Here, p1 is a pointer stored in default memory that points to an
int in Direct page memory. p2 is a pointer stored
in Direct page memory that points to a long in default
memory.
Structures¶
You can place a structure in a specified address space. This means that all its members are in that address space. You cannot override individual structure members using an address space keyword. It is however possible to have members of the structure that point to a different address space.
struct tag {
int __tiny * p;
int value;
};
struct tag __tiny myTag;
This is however not allowed:
struct tag {
int * __tiny p; /* incorrect */
int __tiny value; /* incorrect */
};