The targeted system uses block RAM contained on the XILINX FPGA for the system memory (both RAM and ROM). The block RAMs are available in various aspect ratios, and they will be used in this system as 2K*8 devices. There will be two RAMs used for data storage, starting at location $0000 and growing upwards. There will be one ROM (realized as initialized RAM) used code storage, starting at location $FFFF and growing downwards.
The cc65 toolset requires a memory configuration file to define the memory that is available to the cc65 run-time environment, which is defined as follows:
MEMORY {
ZP: start = $0, size = $100, type = rw, define = yes;
RAM: start = $200, size = $0E00, define = yes;
ROM: start = $F800, size = $0800, file = %O;
}
ZP defines the available zero page locations, which in this case starts at $0 and has a length of $100. Keep in mind that certain systems may require access to some zero page locations, so the starting address may need to be adjusted accordingly to prevent cc65 from attempting to reuse those locations. Also, at a minimum, the cc65 run-time environment uses 26 zero page locations, so the smallest zero page size that can be specified is $1A. The usable RAM memory area begins after the 6502 stack storage in page 1, so it is defined as starting at location $200 and filling the remaining 4K of space (4096 - 2 * 256 = 3584 = $0E00). The 2K of ROM space begins at address $F800 and goes to $FFFF (size = $0800).
Next, the memory segments within the memory devices need to be defined. A standard segment definition is used, with one notable exception. The interrupt and reset vector locations need to be defined at locations $FFFA through $FFFF. A special segment named VECTORS is defined that resides at these locations. Later, the interrupt vector map will be created and placed in the VECTORS segment, and the linker will put these vectors at the proper memory locations. The segment definition is:
SEGMENTS {
ZEROPAGE: load = ZP, type = zp, define = yes;
DATA: load = ROM, type = rw, define = yes, run = RAM;
BSS: load = RAM, type = bss, define = yes;
HEAP: load = RAM, type = bss, optional = yes;
STARTUP: load = ROM, type = ro;
INIT: load = ROM, type = ro, optional = yes;
CODE: load = ROM, type = ro;
RODATA: load = ROM, type = ro;
VECTORS: load = ROM, type = ro, start = $FFFA;
}
The meaning of each of these segments is as follows.
ZEROPAGE:
Data in page 0, defined by ZP as starting at $0 with length $100
DATA:
Initialized data that can be modified by the program, stored in RAM
BSS:
Uninitialized data stored in RAM (used for variable storage)
HEAP:
Uninitialized C-level heap storage in RAM, optional
STARTUP:
The program initialization code, stored in ROM
INIT:
The code needed to initialize the system, stored in ROM
CODE:
The program code, stored in ROM
RODATA:
Initialized data that cannot be modified by the program, stored in ROM
VECTORS:
The interrupt vector table, stored in ROM at location $FFFA
A note about initialized data: any variables that require an initial
value, such as external (global) variables, require that the initial
values be stored in the ROM code image. However, variables stored in
ROM cannot change; therefore the data must be moved into variables that
are located in RAM. Specifying run = RAM
as part of
the DATA segment definition will indicate that those variables will
require their initialization value to be copied via a call to the
copydata
routine in the startup assembly code. In addition,
there are system level variables that will need to be initialized as
well, especially if the heap segment is used via a C-level call to
malloc ()
.
The final section of the definition file contains the data constructors
and destructors used for system startup. In addition, if the heap is
used, the maximum C-level stack size needs to be defined in order for
the system to be able to reliably allocate blocks of memory. The stack
size selection must be greater than the maximum amount of storage
required to run the program, keeping in mind that the C-level subroutine
call stack and all local variables are stored in this stack. The
FEATURES
section defines the required constructor/destructor
attributes and the SYMBOLS
section defines the stack size. The
constructors will be run via a call to initlib
in the startup
assembly code and the destructors will be run via an assembly language
call to donelib
during program termination.
FEATURES {
CONDES: segment = STARTUP,
type = constructor,
label = __CONSTRUCTOR_TABLE__,
count = __CONSTRUCTOR_COUNT__;
CONDES: segment = STARTUP,
type = destructor,
label = __DESTRUCTOR_TABLE__,
count = __DESTRUCTOR_COUNT__;
}
SYMBOLS {
# Define the stack size for the application
__STACKSIZE__: value = $0200, weak = yes;
}
These definitions are placed in a file named "sbc.cfg" and are referred to during the ld65 linker stage.