4. Getting started

This chapter provides an overview of using the compiler and its related tools.

4.1. C language

C is a widely used programming language. It is well-suited for hardware-level programming and also functions as a powerful, generic high-level language. It offers powerful abstractions over target machines, enabling efficient application development and precise control.

This implementation uses the ISO/IEC 9899:1999 standard, commonly known as C99. In this guide, it is referred to as Standard C.

Cross compiler

The compiler is a cross-compiler, running on a modern workstation but producing applications for more constrained target machines.

Supported devices

This Calypsi C compiler tool chain supports the Western Design Center 65816.

4.2. File extensions

The following table shows the file extensions normally used with the Calypsi C compiler tool chain.

Table 4.1 File extensions

Extension

Purpose

.c

C source

.h

C header source

.s

Assembler source

.o

ELF/DWARF object file

.a

Library (collection of object files)

.lst

List file

.scm

Linker rules

.elf

ELF/DWARF output (executable file)

.hex

Intel-hex output

.srec

Motorola S-record output

.s19

Motorola S-record output, 16-bit address records

.s28

Motorola S-record output, 24-bit address records

.s37

Motorola S-record output, 32-bit address records

.raw

Raw output

.pgz

Foenix binary format

.prg

Commodore 64 binary format

4.3. Building applications

Applications can be built from source files and libraries. Source files, written in C or assembly language, are compiled into object files: C source files use cc65816, and assembly source files use as65816.

A library is a collection of object files produced by the nlib tool, combining them with an index into a single file. The C runtime library is provided as an example; third-party libraries are also supported.

The ln65816 takes object files, libraries, and placement rules as input to construct the executable application.

Compiler

The compiler command-line interface processes a single source file to produce an object file:

$ cc65816 source.c

The object file produced will have the same base name as the input but with a .o file extension.

Note

You typically need command-line options to select the CPU core, runtime models, and other settings.

Assembler

C projects do not require knowledge of assembly language. C simplifies programming and enables portability across architectures.

However, for highly specific target code, deep-level control, or critical routines, the assembler is indispensable.

The assembler command-line interface is similar to the compiler, with the main difference being the file extension:

$ as65816 source.s

The produced object file has the same .o extension as the compiler.

Note

You may need to provide a --core option for the assembler to recognize the target exact machine instructions.

Linker

The linker combines object files and libraries to create an executable application.

A rules file is required by the linker to describe the memory system, including placement rules for code and data. Stack and heap sizes can also be specified in this file.

You can run the linker from the command line as:

$ ln65816 myfile1.o myfile2.o rules.scm

This simplified command produces aout.elf, an ELF binary.

There are many ways to tailor the output:

  • Using hex output, in either Intel HEX or Motorola S-record file format

  • RAW output, which is just plain binary output of a single memory area

  • Foenix program files, which are segmented binary files with start address

  • The --debug option includes DWARF debugging information in the ELF executable.

The linker can produce a list file with cross-reference information, showing memory usage, placement, and why certain library contents were included.

4.4. Configuring

Tune compiler code generation using various command-line options.

The most basic settings to consider are:

  • The CPU core (--core) in use. This controls the exact instruction set.

  • The data model, which affects how data are placed and accessed in memory.

  • The size of the double floating point type.

  • Optimization settings.

Core

The Calypsi C compiler tool chain supports the WDC65816 core.

Code model

The code model governs default function placement and function pointer size.

The available code models are Small, Compact and Large. If not specified the compiler uses the Large code model which allows addressing functions in the entire memory range.

The Small and Compact code models restricts the code to a single 64K bank. In the Small code this is the first bank (low 64K) while the Compact code model allows the use any bank.

Table 4.2 Code models

Code model

Function pointer size

Size limit

Small

16 bits

64K bytes

Compact

16 bits

64K bytes

Large

24 bits

16M bytes

Data model

The data model dictates default global data placement and defines the default pointer type (a data pointer without specific address attributes).

The available data models are Small, Medium Large and Huge. If not specified the compiler uses the Small data model.

Generally, smaller data models result in smaller and faster applications. Larger data models are primarily used for very large applications where avoiding explicit memory attributes is desired.

Small

In the small data model all default data is located in the first 64K of memory. This allows for using efficient 16 bits pointers, also for functions provided in the C library runtime.

You can still have data outside the first 64K, but you have to use keywords on such objects and pointers to them. This makes it possible to use 24 bits addressing to reach such data.

Note

In the Small data model, the near attribute is disabled because only a single 64K data bank (bank 00) is used.

Medium

The Medium data model uses a designated 64K area (other than bank 00) for static data by default. While default pointers to this data are 24 bits, you can achieve efficient 16-bit addressing using a typed near pointer.

The Medium data model is useful for static data, such as tables or string literals, that do not fit within the limitations of the Small data model.

Large

In the Large data model all pointers and data have the far attribute by default. This means that you can have data that span the entire 16MB memory range without using any keywords. Data object size is limited to 64K minus one byte. For larger objects, you must use the huge attribute or switch to the Huge data model.

For fast access, you can still use the tiny or near attribute on such objects.

Huge

In the Huge data model all limitations are removed and you can address the entire memory range using 24 bits addressing with no limitation on object sizes. This also means that the code size will grow as the compiler is forced to generate code that is less efficient.

Table 4.3 Default pointer behavior in different data models

Data model

Default pointer size

Default attribute

Max object size

Total size limit

Small

16 bits

Bank 00

64K bytes

64K bytes

Medium

24 bits

near

64K bytes

64K bytes

Large

24 bits

far

16M bytes

16M bytes

Huge

24 bits

huge

16M bytes

16M bytes

Note

The total size limit applies to static or global objects without explicit memory attributes. You can go beyond the size limit imposed by the default pointer by using explicit memory attributes, e.g far.

Note

For shorter addressing you can always use the tiny attribute which uses the direct page.

Note

In the Small, Medium and Large data models, the huge attribute is disabled by default. This is because huge is seldom needed, and enabling it requires size_t to be 32 bits instead of the more efficient 16 bits on the 65816. If enabled with these data models, you will also need to rebuild the C runtime library. See Rebuilding the C library for how to build your own C runtime library.

Size of double

The double floating-point type uses IEEE 754 format and can be set to either 32 or 64 bits using --32bit-doubles or --64bit-doubles. It defaults to 32 bits if not specified.

The float type is always 32 bits, and the long double type is always 64 bits.

Optimization

Select the optimization level using the -O command-line option, which accepts a numeric argument of 1 or 2.

The compiler applies a number of techniques to reduce the width of expressions, select efficient code sequences and dead code elimination, regardless of optimization settings.

The -O option enables further optimization to reduce the code memory footprint and typically increase execution speed.

4.5. Low level control

This section provides a brief overview of controlling access to specific memory and built-in functions, also known as intrinsics.

Extended keywords

The 65816 has a direct page short address area which can be accessed more efficiently than other memory addresses. This area has a single-byte address size.

You can specify that something is allocated in the direct page using extension keywords, either as __tiny or __attribute__((tiny)).

Additional data space is available in the near address area which uses a 16 bits address (two bytes). In the Small data model that uses a default pointer of 16 bits, this data area must be bank 00. The hardware stack also resides in this bank. In the other larger data models, the near address area can be any fixed 64K bank and does not have to be in bank 00. However, it must be tied to a designated 64K bank and cannot change during execution. You can specify that something is allocated in the near area using extension keywords, either as __near or __attribute__((near)).

The next data attribute is the far address area which can cover the entire memory space. The only limitation for data in the far area is that a single object cannot exceed 65535 bytes (64K minus one). You can specify that something is allocated in the far area using extension keywords, either as __far or __attribute__((far)).

Finally the huge address area covers the entire memory space and has no limitation on object size. You can specify that something is allocated in the far area using extension keywords, either as __huge or __attribute__((huge)).

Apart from different capability on how much and large objects that can be stored in a given address area, the compiler code that the compiler generates to access the different data areas differs. Accessing objects in larger, more general address spaces requires more instructions and executes slower than in smaller ones. Thus, it makes sense to plan where data objects are located and use the smallest possible one. You can of course put different objects in different address spaces, taking advantage of the performance of the tiny area for small frequently access objects while large and seldom used objects can be allocated in the larger address spaces.

The function attribute __saveds can be used on API and interrupt functions that may be called from another run-time context. It will set up the direct page and data bank when the function is called and restore the previous direct page and data bank on return. This is typically useful for entry points to an operating system that may be called by applications that operate with another direct page or data bank.

Note

For saveds API calls, you should generally prevent arguments from being passed on the direct page. Use either a single register argument or the simple_call calling convention to force arguments onto the stack. Direct page differences when using saveds mean accessing caller-placed arguments there will not work.

Note

When using saveds for API functions you most likely want to use the saveds attribute on C written interrupt functions as well, as interrupts may be triggered at any time.

Intrinsics

Intrinsics functions appear as ordinary calls but are special compiler constructs, emitting specific instruction sequences. To enable them, include the calypsi/intrinsics65816.h header file.

Assembly code

You can write functions in assembly language by following the C calling convention. These functions can be called from C in the same way as any C function.