3 LANGUAGE IMPLEMENTATION

This chapter contains the following sections:

Introduction
Accessing Memory
Storage Types
Non-Volatile RAM
Storage and Section Relations
Memory Models
Default Data Segment ES. _far_es
Code Segment Register ROM Data Access. _rom_cs
Program Counter Based ROM Data Access, _rom_pc0, _rom_pc1
SmartXA EEPROM, _edata
_MODEL
The _at() Attribute
The _atbit() Attribute
Data Types
ANSI C Type Conversions
Character Arithmetic
The _bit Type
The _bitbyte Type
Special Function Registers
Function Parameters
Parameter Passing
Automatic Variables
Initialized Variables
Type Qualifier volatile
Strings
Pointers
Generic Pointers
The _generic Pointer Type
Generic Pointer Size and Format
Generic Pointer Arithmetic
Generic Pointer Conversion
Generic Pointer Restrictions
Inline C Functions
Inline Assembly
Calling Assembly Functions
Intrinsic Functions
Interrupt and Using
Function Call with Software Trap Instruction
Safer C
Structure Tags
Typedef
Switch Statement
Portable C Code
How to Program Smart
Some Examples of Complex Declarators

3.1 Introduction

The TASKING C cross-compiler (cxa) offers a new approach to high-level language programming for the XA family. It conforms to the ANSI standard, but allows you to control the special functions of the XA in C.

This chapter describes the C language implementation in relation to the XA architecture.

The extensions to the C language in cxa are:

_bit

You can use data type _bit for the type definition of scalars in the XA bit-addressable area, and for the return type of functions.

_bitbyte

You can declare byte variables in the bit-addressable area as _bitbyte, 0x20-0x3F within each XA data section. This size restricts the maximum object size to 32 bytes. Individual bits within a _bitbyte can be declared with _atbit. For compatibility with the 8051 you can also access the bits using the macros _getbit() and _putbit().

_sfrbit

Data type for the declaration of specific, absolute bits in special function registers or special absolute bits in the SFR address space. The compiler does not allocate memory for an _sfrbit.

_sfrbyte

Data type for the declaration of Special Function Registers. The compiler does not allocate memory for an _sfrbyte.

_at

You can specify a variable to be at an absolute address.

_atbit

You can specify a variable to be at a bit offset within any bit-addressable variable.

storage types

Apart from a memory category (extern, static, ...) you can specify a storage type in each declaration. This way you obtain a memory model-independent addressing of variables in several address ranges (_bdat, _near, _far, _far_es, _huge, _rom, _rom_cs, _rom_pc0, _rom_pc1, _edata (SmartXA only), _data, _idat, _pdat, _xdat).

memory-specific pointers

cxa allows you to define pointers which point to a specific target memory. These types of pointers are very efficient and require only 2 or 4 bytes memory space.

generic pointers

cxa allows you to define generic or universal pointers with the _generic keyword. When you define a generic pointer it does not point to a specific address space. Instead the address space information is contained in the generic pointer and evaluated at run-time.

parameter passing via the stack

You can specify to pass all function parameters via the stack by using the _stackparm function qualifier.

inline C functions

You can specify to inline a function body instead of calling the function by using the _inline keyword.

assembly functions

Assembly functions can be called from C when they are prototyped with the _asmfunc keyword.

register bank

Each function can contain a specification regarding the register bank to be used (_using keyword).

interrupt functions

You can specify interrupt functions directly through interrupt vectors in the C language (_interrupt keyword). You must also specify the register bank to be used. You can use the _pagezero function qualifier for _interrupt functions to define that the interrupt function itself is located in segment zero. With the _frame function qualifier you can specify which registers must be saved for a particular interrupt function.

software trap

You can specify that a function can be called by a software trap instruction (_trap keyword). You must also specify the register bank to be used.

intrinsic functions

A number of pre-declared functions can be used to generate inline assembly code at the location of the intrinsic (built-in) function call. This avoids the overhead which is normally used to do parameter passing and context saving before executing the called function.

3.2 Accessing Memory

The XA has an address range of 16 Megabytes accessible via 24 bit addresses in two separate address spaces.

You can find a detailed description of the XA architecture, memory organization and CPU organization, in the 16-bit 80C51XA Microcontrollers Data Handbook.

cxa offers two ways of dealing with the separate address spaces implemented in the XA, which can be combined. You can:

- specify a storage type (and perhaps also a target memory of a pointer) with the declaration of a C variable

and you are able to:

- select a memory model, specifying which memory space must be used (as default) for all C variables which do not have an explicit storage specifier. This is very useful for compiling existing C source, which does not need to be adapted for the XA.

In practice the majority of the C code of a complete application is standard C (without using any language extension). You can compile this part of the application without any modification, using the memory model which fits best to the requirements of the system (code density, amount of external RAM etc.).

Only a small part of the application uses language extensions. These parts often deal with items such as:

- I/O, using the special function registers

- high execution speed needed

- high code density needed

- access to non-default memory required (ROM, internal RAM)

- bit type needed

- C interrupt functions

3.2.1 Storage Types

An object other than a function or an automatic (stack) variable cannot be referred to solely by its starting address, because this might be valid for several address spaces. You can explicitly assign each variable to one of the logical address spaces (_bdat, _near, _far, _far_es, _huge, _rom, _rom_cs, _rom_pc0, _rom_pc1, _edata (SmartXA only), _data, _idat, _pdat, _xdat) by using a type specifier. This specifier determines the 'storage type' of static objects.

cxa recognizes the following storage type specifiers:

Storage Type Description
_bdat All memory models:
Bit-addressable on-chip RAM (0x20-0x3f in each 64K segment).
Data object size is limited to 32 bytes.
_near Page Zero or Large Memory mode (all memory models):
Data is direct addressable, located in the default data segment DS.
_far Page Zero mode (tiny or small memory model),
Large Memory mode (medium or compact memory model):

Specifies that a data object is only indirect addressable and is located in the default data segment DS. The data object allocates less than 64K bytes of memory. Pointers declared with _far are 16-bit values. Pointer arithmetic is carried out in 16-bit. Large Memory mode (large or huge memory model):
Specifies that a data object can reside anywhere in memory and is not assumed to reside in any default data segment. The data object allocates less than 64K bytes of memory. Pointers declared with _far are 32-bit values. Pointer arithmetic is carried out in 16-bit, the segment register is not affected, i.e., the objects pointed to must reside within one segment.
_far_es Large Memory mode (compact memory model):
Specifies that a data object is only indirect addressable and is located in the default data segment ES. The data object allocates less than 64K bytes of memory. Pointers declared with _far_es are 16-bit values. Pointer arithmetic is carried out in 16-bit.
_huge Large Memory mode (medium, compact, large or huge memory model):
Specifies that a data object can reside anywhere in memory and is not assumed to reside in any default data segment. The data object may allocate more than 64K bytes of memory. Pointers declared with _huge are 32-bit values. Pointer arithmetic is carried out in 24-bit, the segment register is updated when required. The objects pointed to may reside in multiple segments.
_rom Page Zero mode (tiny or small memory model):
Specifies that a ROM data object can reside anywhere in code memory. The ROM data object allocates less than 64K bytes memory. Pointers declared with _rom are 16-bit values. Pointer arithmetic is carried out in 16-bit. Large Memory mode (medium, compact, large or huge memory model):
Specifies that a ROM data object can reside anywhere in code memory and is not assumed to reside in any default ROM data segment. The ROM data object may allocate more than 64K bytes of memory. Pointers declared with _rom are 32-bit values. Pointer arithmetic is carried out in 24-bit, the segment register CS is updated when required. The object pointed to may reside in multiple segments. The compiler implies the type qualifier const.
_rom_cs Large Memory mode (compact memory model):
Specifies an object that is located in the default ROM segment CS, which is only indirect accessible. The objects allocates less than 64K bytes of memory. Pointers declared with _rom_cs are 16-bit values. Pointer arithmetic is carried out in 16-bit, the segment register is not affected, i.e. the objects pointed to must reside within one segment. The compiler implies the type qualifier const.
_rom_pc0
_rom_pc1
Large Memory mode (compact and medium memory model):
Specifies an object or function that is located in a default ROM data group PC0 or PC1, with indirect ROM data access in the segment specified by the program counter PC. The objects and functions allocate less than 2*64K bytes of memory. Pointers declared with _rom_pc0 and _rom_pc1 are 16-bit values. Pointer arithmetic is carried out in 16-bit, the objects pointed to must reside within one segment. The compiler implies the type qualifier const.
_edata Page Zero or Large Memory mode (all memory models and SmartXA system mode only):
Specifies an object that is located in the SmartXA EEPROM data memory, which is only accessible via EEPROM SFRs. The objects allocates less than 32K bytes of EEPROM data memory. Pointers declared with _edata are 16-bit values. Pointer arithmetic is carried out in 16-bit.

Table 3-1: Storage type specifiers

A detailed table explaining the relations between all storage types, code space, data space, object size, pointer size, CPU mode, SSEL preset value for each memory model, is present in two separate PDF files delivered with the product (in the pdf subdirectory): xamodels.pdf and smartxamodels.pdf.

cxa recognizes the following storage type specifiers for TASKING C-51 compatibility. Use the following storage types only for porting existing TASKING C-51 code:

Storage Type Description
_data direct addressable RAM (0x00-0x3FF in each 64K segment).
Data object size is limited to 1KByte.
_data is identical to _near.
_idat indirect addressable RAM.
_xdat external RAM.
_pdat Data object is located in default memory space, i.e., the compiler ignores the _pdat qualifier.

Table 3-2: Storage type specifiers (C-51 compatible)

Keywords like _internal and _external, to specify whether code or data should be located in on-chip memory or external memory are NOT provided. However, code or data stored in on-chip memory can be faster accessed, and access consumes less power. On-chip and external memory access is exactly the same from the perspective of the code generator. Locating code and data in on-chip memory or external memory is solely a problem to be solved by the locator, not by the compiler.

The pragma renamesec is used to rename a code or data section. The DELFEE locator language is used to specify how to locate the renamed section. Solving locate problems within the locator language instead of in the C source, gives you the ability to tune an existing application for another XA derivative without updating the source code.

Functions are by default allocated in Program Memory; the storage specifier can be omitted in that case. Except functions may be qualified with storage space type specifiers _rom_pc0 or _rom_pc1 to access ROM data qualified with the same space type specifiers indirectly in the segment specified by the program counter PC. Function return values cannot be assigned to a storage area.

In addition to static storage specifiers, a static object can be assigned to a fixed memory address using the _at() keyword.

cxa treats the storage specifier _rom type in a special way: it always implies the type qualifier const.

Const Qualifier

The ANSI standard states that the type qualifier const can be used to specify 'read-only' objects (or: are not 'lvalues'). An ANSI C compiler may allocate static const objects in ROM memory. Allocating constants in ROM is therefore possible. Default are constants allocated in the XA code space. However, since the code and data spaces of the XA are completely separate, and are accessed using different instructions, constants are copied from code to data space by the startup code. Therefore, constants will be placed in sections with the INIT attribute by default. So, cxa treats const just as a type qualifier, which allows the compiler to check on illegal lvalue use. This is exactly the way it is meant to be used in the ANSI definition.

It is also possible to place ROM in the XA data space and allocate all constants in it. When you use the -Sd option cxa will place constants in this ROM data area. So, constants will then be placed in sections with the attribute (I/H)DATA ROMDATA instead of (I/H)DATA INIT.

But it is also possible to allocate strings and literals in the ROM code space only. When you use the -Sc option cxa will place strings in this ROM code area. So, strings will then be placed in sections with the attribute (H)CODE ROMDATA instead of (I/H)DATA INIT.

Section 3.9 , Strings.

Example:

void func( const int i )
{
     i++; /* results in error message from cxa */
}

3.2.1.1 Non-Volatile RAM

The value of variables located in non-volatile ram are retained when the microcontroller system is reset or is turned off. The first time the microcontroller system is started the non-volatile ram should be initialized, afterwards the values stored in the non-volatile memory should not be reinitialized after a reset. However, if the non-volatile memory is 'damaged' due to whatever (battery exchange, application program error, ...), the non-volatile memory should be reinitialized after a reset.

Handling non-volatile memory is a problem that should be solved by the locator and the startup code. The pragma renamesec is used to associate a symbolic name with the section that contains the objects located in non-volatile RAM. The DELFEE locator language is used to add additional locator attributes to the section (like noclear), and to map this section at the right physical memory addresses. For this reason the TASKING XA C compiler does not support a keyword like _persistent to indicate that a data object should be located in non-volatile memory.

3.2.1.2 Storage and Section Relations

The following tables show the resulting assembler section types and attributes for each C storage type (the second table is for TASKING C-51 compatibility only):

Storage Type XA Section Type / Attribute Purpose
_bit BIT
_bdat BITADDR
_near DATA
_far IDATA HDATA INSEGMENT In Page Zero mode In Large Memory mode
_far_es IDATA
_huge HDATA
_rom CODE ROMDATA HCODE ROMDATA In Page Zero mode In Large Memory mode
_rom_cs HCODE ROMDATA INSEGMENT
_rom_pc0/1 HCODE INSEGMENT ROMDATA JOIN HCODE INSEGMENT JOIN CODE JOIN ROM data
program code program code segment 0
_edata EDATA NOCLEAR EEPROM data, SmartXA system mode only

Table 3-3: Section types

Storage Type XA Section Type / Attribute Purpose
_bitbyte BITADDR TASKING C-51 compatibility
_data DATA TASKING C-51 compatibility
_idat IDATA TASKING C-51 compatibility
_xdat XDATA TASKING C-51 compatibility
_pdat Defined by default storage type of selected model TASKING C-51 compatibility

Table 3-4: Section types (C-51 compatible)

Examples using explicit storage types:

allocating:

1 byte in direct addressable RAM for c

11 bytes in direct addressable ROM for the initialized character array text[]

800 bytes in RAM for array

4 bytes in directly addressable RAM for l

100000 bytes in RAM, where object bigarray may (and will) cross 64k boundaries

The storage type specifiers are treated like any other type specifier (e.g. unsigned). This means the examples above can also be declared (exactly the same):

An object must be fully contained in a single storage section. See section 3.18 Structure Tags for details.

3.2.2 Memory Models

cxa supports several memory models: tiny, small, medium, compact and large. You can select one of these models with the -M option. Each model uses a different default storage type for (non-register) automatic variables, (non-register) parameter passing areas and declarations without explicit storage type.

You can find a detailed description of the XA architecture, memory organization and CPU organization, in the 16-bit 80C51XA Microcontrollers Data Handbook.

The following tables give an overview of the different memory models for the XA and the SmartXA. If no memory model is specified on the command line, cxa uses the tiny model because this model generates the most efficient code. The first table illustrates the differences in CPU mode and code objects and the second table illustrates the differences for data objects.

Memory Model CPU Mode Code Space &
Object Size
<=
Code Pointer Size
tiny
(default)
Page Zero 64K / 1 Segm
_rom: 64K
16-bit
16-bit
small Page Zero 64K / 1 Segm.
_rom: 64K
16-bit
16-bit
medium Large Memory 16M / 256 Segm.
_rom: 16M
_rom_pc0: 64K
_rom_pc1: 64K
24-bit
24-bit
16-bit
16-bit
compact Large Memory 16M / 256 Segm.
_rom: 16M
_rom_cs: 64K
_rom_pc0: 64K
_rom_pc1: 64K
24-bit
24-bit
16-bit
16-bit
16-bit
large Large Memory 16M
_rom: 16M
24-bit
24-bit
huge Large Memory 16M
_rom: 16M
24-bit
24-bit

Table 3-5: XA CPU mode and code space

Memory Model Data Space
<=
Object Size
<=
Data Pointer Size Default Storage Type
tiny
(default)
_bit: 32
_bdat: 32
_xdat: 64K
_near: 1K
_far: 64K
_bit: 1-bit
_bdat: 32
_xdat: 64K
_near: 1K
_far: 64K
_bit: N.A.
_bdat: 16-bit
_xdat: 16-bit
_near: 16-bit
_far: 16-bit
_near
small _bit: 32
_bdat: 32
_xdat: 64K
_near: 1K
_far: 64K
_bit: 1-bit
_bdat: 32
_xdat: 64K
_near: 1K
_far: 64K
_bit: N.A.
_bdat: 16-bit
_xdat: 16-bit
_near: 16-bit
_far: 16-bit
_far
medium _bit: 32
_bdat: 32
_xdat: 64K
_near: 1K
_far: 64K
_huge: 16M
_bit: 1-bit
_bdat: 32
_xdat: 64K
_near: 1K
_far: 64K
_huge: 16M
_bit: N.A.
_bdat: 16-bit
_xdat: 16-bit
_near: 16-bit
_far: 16-bit
_huge: 24-bit
_far
compact _bit: 32
_bdat: 32
_xdat: 64K
_near: 1K
_far: 64K
_far_es: 64K
_huge: 16M
_bit: 1-bit
_bdat: 32
_xdat: 64K
_near: 1K
_far: 64K
_far_es: 64K
_huge: 16M
_bit: N.A.
_bdat: 16-bit
_xdat: 16-bit
_near: 16-bit
_far: 16-bit
_far_es: 16-bit
_huge: 24-bit
_far
large _bit: 32
_bdat: 32
_xdat: 64K
_near: 1K
_far: 16M
_huge: 16M
_bit: 1-bit
_bdat: 32
_xdat: 64K
_near: 1K
_far: 64K
_huge: 16M
_bit: N.A.
_bdat: 16-bit
_xdat: 16-bit
_near: 16-bit
_far: 24-bit
_huge: 24-bit
_far
huge _bit: 32
_bdat: 32
_xdat: 64K
_near: 1K
_far: 16M
_huge: 16M
_bit: 1-bit
_bdat: 32
_xdat: 64K
_near: 1K
_far: 64K
_huge: 16M
_bit: N.A.
_bdat: 16-bit
_xdat: 16-bit
_near: 16-bit
_far: 24-bit
_huge: 24-bit
_huge
N.A.: Not Applicable

Table 3-6: XA data objects

Memory Model CPU Mode Code Space &
Object Size
<=
Code Pointer Size
tiny
(default)
Page Zero 64K / 1 Segm
_rom: 64K
16-bit
16-bit
small Page Zero 64K / 1 Segm.
_rom: 64K
16-bit
16-bit
medium Large Memory SM 512K / 8 Segm.
_rom: 512K
_rom_pc0: 64K
_rom_pc1: 64K
24-bit
24-bit
16-bit
16-bit
medium Large Memory UM N * 64K / N * WinMax
_rom: N * 64K
_rom_pc0: 64K
_rom_pc1: 64K
24-bit
24-bit
16-bit
16-bit
compact Large Memory SM 512K / 8 Segm.
_rom: 512K
_rom_cs: 64K
_rom_pc0: 64K
_rom_pc1: 64K
24-bit
24-bit
16-bit
16-bit
16-bit
compact Large Memory UM N * 64K / N * WinMax
_rom: N * 64K
_rom_cs: 64K
_rom_pc0: 64K
_rom_pc1: 64K
24-bit
24-bit
16-bit
16-bit
16-bit
large Large Memory SM 512K / 8 Segm.
_rom: 512K
24-bit
24-bit
large Large Memory UM Smart XA UM is not supported.
huge Large Memory SM 512K / 8 Segm.
_rom: 512K
24-bit
24-bit
huge Large Memory UM Smart XA UM is not supported.
UM: User Mode
SM: System Mode
N: for PSX10W01 N=2; for whole SmartXA family Nmax=32
WinMax: the maximum window size

Table 3-7: SmartXA CPU mode and code space

Memory Model / CPU Mode Data Space
<=
Object Size
<=
Data Pointer Size Default Storage Type
tiny
(default) /
Page Zero
_bit: 32
_bdat: 32
_near: 1K
_far: 64K
_edata: 32K
_bit: 1-bit
_bdat: 32
_near: 1K
_far: 64K
_edata: 32K
_bit: N.A.
_bdat: 16-bit
_near: 16-bit
_far: 16-bit
_edata: 16-bit
_near
small /
Page Zero
_bit: 32
_bdat: 32
_near: 1K
_far: 64K
_edata: 32K
_bit: 1-bit
_bdat: 32
_near: 1K
_far: 64K
_edata: 32K
_bit: N.A.
_bdat: 16-bit
_near: 16-bit
_far: 16-bit
_edata: 16-bit
_far
medium /
Large Memory SM
_bit: 32
_bdat: 32
_near: 1K
_far: 64K
_huge: 16M
_edata: 32K
_bit: 1-bit
_bdat: 32
_near: 1K
_far: 64K
_huge: 16M
_edata: 32K
_bit: N.A.
_bdat: 16-bit
_near: 16-bit
_far: 16-bit
_huge: 24-bit
_edata: 16-bit
_far
medium /
Large Memory UM
_bit: 32
_bdat: 32
_near: 1K
_far: 64K
_bit: 1-bit
_bdat: 32
_near: 1K
_far: 64K
_bit: N.A.
_bdat: 16-bit
_near: 16-bit
_far: 16-bit
_far
compact /
Large Memory SM
_bit: 32
_bdat: 32
_near: 1K
_far: 64K
_far_es: 64K
_huge: 16M
_edata: 32K
_bit: 1-bit
_bdat: 32
_near: 1K
_far: 64K
_far_es: 64K
_huge: 16M
_edata: 32K
_bit: N.A.
_bdat: 16-bit
_near: 16-bit
_far: 16-bit
_far_es: 16-bit
_huge: 24-bit
_edata: 16-bit
_far
compact /
Large Memory UM
_bit: 32
_bdat: 32
_near: 1K
_far: 64K
_far_es: 64K
_bit: 1-bit
_bdat: 32
_near: 1K
_far: 64K
_far_es: 64K
_bit: N.A.
_bdat: 16-bit
_near: 16-bit
_far: 16-bit
_far_es: 16-bit
_far
large /
Large Memory SM
_bit: 32
_bdat: 32
_near: 1K
_far: 16M
_huge: 16M
_edata: 32K
_bit: 1-bit
_bdat: 32
_near: 1K
_far: 64K
_huge: 16M
_edata: 32K
_bit: N.A.
_bdat: 16-bit
_near: 16-bit
_far: 24-bit
_huge: 24-bit
_edata: 16-bit
_far
large /
Large Memory UM

Smart XA UM is not supported.
huge /
Large Memory SM0
_bit: 32
_bdat: 32
_near: 1K
_far: 16M
_huge: 16M
_edata: 32K
_bit: 1-bit
_bdat: 32
_near: 1K
_far: 64K
_huge: 16M
_edata: 32K
_bit: N.A.
_bdat: 16-bit
_near: 16-bit
_far: 24-bit
_huge: 24-bit
_edata: 16-bit
_huge
huge /
Large Memory UM

Smart XA UM is not supported.
_xdat is disabled for all models because SmartXA has no external RAM
_edata and _huge are disabled for Large Memory User Mode
SM: System Mode UM: User Mode N.A.: Not Applicable

Table 3-8: SmartXA data objects

All memory models supGeneric Pointersport generic pointers. Unqualified pointers (default pointers) are generic for the huge memory model. A default pointer in the huge memory model can point to any object located in the XA code or data space. The pointer size of a generic pointer is 32-bit. See section 3.10.1 Generic Pointers for details.

The compiler ignores the keyword _huge in the tiny and small memory models and issues a warning when you use it. The keywords _far_es and _rom_cs are also ignored by the compiler for non compact memory models and a warning is issued when you use it. The _edata keyword is only allowed for SmartXA system mode. An error is generated when using the _edata keyword for non SmartXA system mode code generation.

The stack must always fit in one segment of 64K, no matter what memory model you use. This is because the User stack or System stack is always addressed relative to the current data segment (DS or 0) value, which is fixed to one segment. The System stack must always be located in segment 0. The User stack is always located in default data segment DS.

Separate versions of the C and runtime libraries are supplied for all supported models, avoiding the need for the programmer to recompile or rebuild these when using a particular model.

It is unlikely that many _huge objects, i.e. objects that allocate more than 64K bytes of memory, are used within an embedded application. Accessing a _huge object is less efficient than accessing _far objects, regarding both code space and runtime overhead. Using the _huge qualifier on individual objects is recommended.

A static memory model approach for C function parameters and automatics is not implemented for the following reasons:

3.2.2.1 Default Data Segment ES, _far_es

This section describes how the Compact Memory model supports the extra default data segment ES.

The storage type specifier _far_es specifies an object that is located in the extra default data segment ES.

This extra default data segment ES allows for an additional space of 64K bytes of 16-bit accessible RAM data in a 16M-data space. Pointers are defined to be 16-bit wide with 16-bit arithmetic.

The advantage of the compact memory model is the extra 64K of 16-bit indirect addressable memory in default data segment ES. The disadvantage of the compact memory model is the extra overhead when using _huge, because the compiler needs to set ES back to its original value after each 24-bit data access.

For the implementation of the extra default data segment ES the data segment register ES is used. The segment number of this extra default RAM data segment is stored in the segment register ES and its value must always remain the same. Controlled by an operating system or at startup the ES register is initialized with a default segment number.

A register protocol is used for accessing the extra default data segment ES. The registers R4 and R5 are used for _far_es data access. Therefore, the Segment Selection register (SSEL) must be initialized at startup or by an operating system with 030H and should remain this value.

The Medium, Large and Huge memory models cannot be extended for generating efficient code for extra default data segment access, because they are not designed for it. The Large and Huge memory models are designed for efficient far 24-bit access; supporting _far_es requires restoring ES for each far 24-bit RAM data access. The Medium model is not designed for having ES based RAM data access, SSEL is 0, supporting _far_es would require setup and restore of SSEL after each access.

Also, a minor drawback in efficiency of register usage can be noticed with the compact memory model. The XA general purpose registers are divided for indirect RAM data access with default segment DS/ES and indirect ROM data access with default segment PC/CS via the segment selection register SSEL (SSEL==0x30). In the medium model all XA general purpose registers are always available for indirect RAM data access with default segment DS and indirect ROM data access with default segment PC data access (SSEL==0x00).

3.2.2.2 Code Segment Register ROM Data Access, _rom_cs

This section describes how the Compact Memory model supports Code Segment register based ROM data access, called default ROM data access.

The storage type specifier _rom_cs specifies an object that is located in the default ROM data segment.

This default ROM data segment will allow for an additional space of 64K bytes of 16-bit accessible ROM data in a 16M-code space. Pointers are defined to be 16-bit wide with 16-bit arithmetic.

The advantage of the compact memory model is the extra 64K of 16-bit indirect addressable memory in default ROM data segment CS. The disadvantage of the compact memory model is the extra overhead when using _rom, because the compiler needs to set CS back to its original value after each 24-bit ROM data access.

For the implementation of default ROM data segment the Code segment register CS is used. The segment number of this extra default ROM data segment is stored in the segment register CS and its value must always remain the same Controlled by an operating system or at startup the CS register is initialized with a default segment number.

A register protocol is used for accessing the default ROM data segment. The register protocol conforms to the protocol already available in the Compact memory model for default RAM data access via Extra segment register ES. The registers R4 and R5 are used for _rom_cs data access. Therefore, the Segment Selection register (SSEL) must be initialized at startup or by an operating system with 030H and should remain this value.

The Medium, Large and Huge memory models cannot be extended for generating efficient code for default ROM data segment access, because they are not designed for it. The Large and Huge memory models are designed for efficient far 24-bit access; supporting _rom_cs requires restoring CS for each far 24-bit _rom data access. The Medium model is not designed for having CS based ROM data access (SSEL==0) and supporting _rom_cs would require setup and restore of SSEL after each access.

Also, a minor drawback in efficiency of register usage can be noticed with the compact memory model. The XA general purpose registers are divided for indirect RAM data access with default segment DS/ES and indirect ROM data access with default segment PC/CS via the segment selection register SSEL (SSEL==0x30). In the medium model all XA general purpose registers are always available for indirect RAM data access with default segment DS and indirect ROM data access with default segment PC data access (SSEL==0x00).

3.2.2.3 Program Counter Based ROM Data Access, _rom_pc0, _rom_pc1

This section describes how the Compact and Medium Memory model support indirect ROM data access in the segment identified by PC, called Program counter Based ROM data access.

The storage type specifiers _rom_pc0 and _rom_pc1 specify an object that is located in one of the two PC ROM data segments.

These PC ROM data segments will allow for an additional space of 2*64K bytes of 16-bit accessible ROM data in a 16M-code space. Pointers are defined to be 16-bit wide with 16-bit arithmetic.

ROM data access does not require any loading of segment registers when a ROM data object and a function accessing it are both qualified with the storage type specifier _rom_pc0 or _rom_pc1. Two ROM data groups are available for indirect ROM data access via the segment identified by PC. These two ROM data groups, called PC0 and PC1, can reside in any code segment of the XA.

To support Program Counter based ROM data access, a register protocol is used which supports default ROM data access via PC. This protocol can be supported in the Medium memory model, SSEL is 0, which allows default indirect ROM data access via R0 until R6 extended with the 8-bit segment identifier of PC. Also the Compact memory model can efficiently be extended. SSEL is 0x30 by convention that allows default indirect ROM data access via R0 until R3 and R6 extended with the 8-bit segment identifier of PC.

The storage type specifiers _rom_pc0 and _rom_pc1 are not implemented for the Large and Huge memory models, because they are not efficient when compared with the 24-bit _rom storage type specifier of these models. SSEL is 0x7f by convention for the Large memory model. The Large and Huge memory models are designed to be efficient for 24-bit data access. For efficient 24-bit ROM data access the segment selection register is set to extend all indirect register access with the contents of segment register CS (SSEL=0x7f), which makes indirect access via PC less efficient. Supporting PC relative ROM data access would require setup and restore of SSEL.

3.2.2.4 SmartXA EEPROM, _edata

The SmartXA is equipped with a 32 Kbytes EEPROM. This non-volatile memory is available through data and code space. The SmartXA only allows EEPROM data access in System Mode.

The storage type specifier _edata specifies an object that is located in the SmartXA EEPROM data memory. This qualifier allows C programming without using the EEPROM SFRs; the C compiler takes care of generating the EEPROM SFRs for accessing the EEPROM data memory. For every single EEPROM data object update, code is generated which does an immediate write cycle, even if EEPROM data objects are allocated on successive address locations.

The EEPROM is non-volatile RAM. Variables located in non-volatile ram are retained when the CPU is reset or turned off. Therefore, the _edata storage qualifier implies for global data objects not being cleared or initialized at startup. The ANSI-C standard prescribes that 'normal' not initialized non-automatic variables are cleared at startup and initialized non-automatic variables are initialized at startup, which is unwanted for non-volatile ram like the EEPROM.

No additional storage qualifiers are needed for EEPROM code generation and ROM data access (MOVC) from the C compiler. The EEPROM ROM data access is supported by the C compiler via the _rom storage qualifier.

3.2.2.5 _MODEL

cxa introduces the predefined preprocessor symbol _MODEL. The value of _MODEL represents the memory model selected (-M option). This can be very helpful in making conditional C code in one source module, used for different applications in different memory models. See also section 3.21 Portable C Code, explaining the include file cxa.h .

The value of _MODEL is:

Example:

3.2.3 The _at() Attribute

In C-XA it is possible to place certain variables at absolute addresses. Instead of writing a piece of assembly code, a variable can be placed on an absolute address using the _at() attribute.

Example:

The example above creates a variable with the name Display at address 0x2000. In the generated assembly code an absolute section will appear like 'ISEG AT 2000H', on this position space is reserved for the variable Display.

A number of restrictions are in effect when placing variables on an absolute address:

3.2.4 The _atbit() Attribute

In C-XA it is possible to define bit variables within a _bitbyte or _sfrbyte variable. This can be done with the _atbit() attribute. The syntax is:

where, bytename is the name of a _bitbyte or _sfrbyte variable and offset is the bit-offset with the variable.

Examples:

The first example defines an _sfrbit within an _sfrbyte. The second example defines a bit address within a bit-addressable byte.

For more information on SFR variables see section 3.3.5 Special Function Registers. For more information on _bitbyte variables see section 3.3.3 The _bitbyte Type.

The macros _getbit() and _putbit() (available in c51.h) are only available for compatibility with the 8051. The usage is conform the corresponding intrinsic functions of the 8051. The preferred way to access bits is with _atbit().

3.3 Data Types

All ANSI C data types are supported. In addition to these types, the _sfrbit, _sfrbyte, _bit and _bitbyte types are added. Three types of pointers are recognized. Object size and ranges:

Data Type Size
(in bytes)
Range
_bit 1 bit 0 or 1
_sfrbit 1 bit 0 or 1
signed char 1 -128 to +127
unsigned char 1 0 to 255U
_sfrbyte 1 0 to 255U
_bitbyte 1 0 to 255U (byte in bit-addressable RAM)
signed short 2 -32768 to +32767
unsigned short 2 0 to 65535U
signed int 2 -32768 to +32767
unsigned int 2 0 to 65535U
signed long 4 -2147483648 to +2147483647
unsigned long 4 0 to 4294967295UL
float 4 +/- 1.176E-38 to +/- 3.402E+38
double 8 +/- 2.225E-308 to +/- 1.798E+308
enum 2 -32768 to +32767
_near pointer 2 0 to 65535
_far pointer 2 / 4 0 to 65535 / 16777216 (depending on selected memory model)
_far_es pointer 2 0 to 65535
_huge pointer 4 0 to 16777216
_rom pointer 2 / 4 0 to 65535 / 16777216 (depending on selected memory model)
_rom_cs pointer 2 0 to 65535
_rom_pc0 pointer 2 0 to 65535
_rom_pc1 pointer 2 0 to 65535
_generic pointer 4 0 to 16777215
(data :medium,compact,large,huge)

2147483648 to 2149161373
(code :medium,compact,large,huge)

0 to 65535
(data :tiny,small)

2147483648 to 2147549183
(code :tiny,small)
_edata pointer 2 0 to 65535 (SmartXA System Mode only)

Table 3-9: Data types

- _bit, _sfrbit, char, _sfrbyte, _bitbyte, short, int and long are all integral types, supporting all implicit (automatic) conversions.

- cxa generates instructions using (8 bit) character arithmetic, when it is correct to evaluate a character expression this way. This results in a higher code density compared with integer arithmetic. A special section 3.3.2 Character Arithmetic provides details.

- the XA convention is used, storing variables with the least significant part at the lower memory address (Little Endian).

- float is implemented in little endian IEEE 32-bit single precision format.

- double is implemented in little endian IEEE 64-bit double precision format.

3.3.1 ANSI C Type Conversions

According to the ANSI C X3.159-1989 standard, a character, a short integer, an integer bit field (either signed or unsigned), or an object of enumeration type, may be used in an expression wherever an integer may be used. If a signed int can represent all the values of the original type, then the value is converted to signed int; otherwise the value will be converted to unsigned int. This process is called integral promotion.

Integral promotion is also performed on function pointers and function parameters of integral types using the old-style declaration. To avoid problems with implicit type conversions, you are advised to use function prototypes.

Many operators cause conversions and yield result types in a similar way. The effect is to bring operands into a common type, which is also the type of the result. This pattern is called the usual arithmetic conversions.

Section 3.3.2 Character Arithmetic.

Sometimes surprising results may occur, for example when unsigned char is promoted to int. You can always use explicit casting to obtain the type required. The following example makes this clear:

To overcome this 'unwanted' behavior use an explicit cast:

Keep in mind that the arithmetic conversions apply to multiplications also:

3.3.2 Character Arithmetic

cxa generates code using 8 bit (character) arithmetic as long as the result of the expression is exactly the same as if it was evaluated in integer arithmetic. This must be done, because ANSI does not define character arithmetic and character constants. Although the XA performs 16-bit operation as fast as 8-bit operations, the overhead caused by the integral promotions is suppressed.

So it is recommended to use character variables in expressions, because it saves data space for allocation, and often results in a higher code density. You can always force to use character arithmetic with a character cast.

The following examples clarify when integer arithmetic is used and when character arithmetic:

char  a, b, c, d;
int   i;

void
main( void )
{
   c = a + b;               /* character arithmetic   */
   i = a + b;               /* integer arithmetic     */
   i = (char)(a + b);       /* character arithmetic   */

   c = a / d;               /* character arithmetic   */
   c = (a + b) / d;         /* integer arithmetic     */
   c = ((char)(a + b)) / d; /* character arithmetic   */

   c = a >> d;              /* character arithmetic   */
   c = (a + b) >> d;          /* integer arithmetic   */

   if ( a > b )               /* character arithmetic */
      c = d;
   if ( (a + b) > c )         /* integer arithmetic   */
      c = d;
}

You can disable character arithmetic with the -AC ccommand line option.

3.3.3 The _bit Type

The _bit type is used to define scalars in the XA bit-addressable area and for the return type of functions. A struct containing bit fields cannot be used for this purpose, for example because the struct is alligned at a word boundary.

Variables of type _bit are located in the default data section.

The following rules apply to _bit type variables:

1. A _bit type variable is always unsigned.

2. A _bit type variable can be exchanged with all other integral type variables. The compiler generates the correct conversion.

A _bit type variable is like a boolean. Therefore, converting an int type variable to a _bit type variable does not mean the _bit type variable is the least significant bit of the int type variable. It is 1 (true) if the int type variable is not equal to 0, and 0 (false) if the int type variable is 0. In C:

can be seen as:

3. Pointer to _bit and array of _bit are not allowed, because the XA has no instructions to indirectly access bit-addrressable memory.

4. Structure of _bit is supported, with the restriction that no other type than _bit is member of this structure. Structure of _bit is not allowed on parameter or return value of a function.

5. A union of a _bit structure and another type is not allowed. The _bitbyte type can be used for this purpose.

6. A _bit type variable is not allowed as a function parameter of a function.

7. A _bit type variable is allowed as an automatic variable of a function. XA registers are bit addressable. A maximum of 16 automatic bit variables may be used within a function.

8. A function may have return type _bit. However, the next two rules may not be violated.

9. A function cannot have mulitple storage qualifiers. For example:
_bit _rom_pc0 f( void ); is not allowed.

10. Evaluation of a complex _bit expression (using non _bit types or _bit return type of a function) is not recursive nor reentrant, because the compiler might need temporary static bit space.

11. A _bit typed expression is not allowed as switch expression.

12. The sizeof of a _bit type is 1.

Normal C bit fields within a structure are not treated like _bit variables, and are therefore not allocated in BIT memory, but in one of the other memory spaces as specified during declaration. Using a structure of _bit type variables results in much better code density, less storage allocation and higher execution speed compared to a structure with a number of 1-bit bit field declarations.

For example:

3.3.4 The _bitbyte Type

The _bitbyte type is supported to ease porting 80C51 C applications to the XA. New designed applications should use the _bdat storage qualifier to declare variables in bit-addressable memory.

You can declare byte variables in the XA bit-addressable area (0x02-0x3F) as _bitbyte. You can access individual bits of this _bitbyte variable using _atbit. For compatibility with the 8051 you can also use the macros _getbit() and _putbit().

For example:

Section 3.2.4 The _atbit() Attribute.

The _bitbyte type is subject to the following rules.

1. A _bitbyte type variable is always unsigned.

2. A _bitbyte type variable can be exchanged with all other integral type variables. The compiler generates the correct conversion.

3. Pointer to a _bitbyte variable and array of _bitbyte is allowed.

4. Structure of _bitbyte is supported, with the restriction that no other type than _bitbyte is member of this structure. Structure of _bitbyte is not allowed on parameter or return value of a function.

5. A _bitbyte type variable is not allowed as a function parameter of a function.

6. A _bitbyte type variable is not allowed as an automatic variable of a function. However, a local static _bitbyte variable (within a function) is always allowed.

7. A function can not have return type _bitbyte.

8. The sizeof of a _bitbyte type is 1.

9. A _bitbyte typed expression is allowed as switch expression.

3.3.5 Special Function Registers

The _sfrbit and _sfrbyte keywords allow direct access to all special function registers (both bits and bytes), as if they were C variables. These special function registers can be used the same way as any other integral data type, including all automatic conversions.

A _sfrbit is handled the same way as a volatile _bit variable. A _sfrbyte is handled as a volatile unsigned char variable.

In order to 'include' a special function register definition file, you must use the -C option. You are able to specify which cpu must be used.

For example, the command:

causes the compiler to look for a file named regxa.sfr, and use this file as a special function register definition file. We deliver a number of these definition files with cxa, but you can easily make your own copy for the XA derivative you are using. cxa uses the same searching method for these register definition files as with include files. For details, see section 4.3 Include Files.

You can also declare sfr-registers within your C source by using the data types _sfrbit or _sfrbyte. The notation is as follows:

where, name must be replaced with the name of the SFR you want to specify. address is the bit or byte address of the SFR. offset is the bit-offset in the _sfrbyte. Because these registers are placed in the sfr-area of the processor, the compiler will not allocate any storage space.

Note, that the words 'sfrbyte' and 'sfrbit' are not reserved words for cxa. So these words can be used as identifiers. cxa does not generate symbolic debugging information for special function registers because they are already known by the debugger.

Because the special function registers are dealing with I/O, it is not correct to optimize away the access to them. Therefore, cxa deals with the special function registers as if they were declared with the volatile qualifier.

For example:

int           i;
volatile int  v;

void
main( void )
{
   i;       /* optimized away                             */
   S0BUF;   /* access S0BUF register (implicit volatile)  */
   v;       /* volatile: access variable                  */
}

3.4 Function Parameters

cxa supports (ANSI) prototyping of function parameters. Therefore, cxa allows passing parameters of type char, without converting these parameters to int type. This results into higher code density, higher execution speed and less RAM data space needed for parameter passing.

For example, in the following C code:

the code generator uses the prototype of func() and:

- passes c as a byte

- promotes i to long before passing it as a long

However, the code generator does not know anything of the printf() arguments, because this function is declared with a variable argument list. If there is no prototype (as with the old style K & R functions), the compiler promotes both char type parameters to int type, the same way an automatic conversion is done in an assignment of a char type variable to an int type variable. So, with the printf() call the code generator:

- promotes c to int before passing it as int

- passes i as int

3.5 Parameter Passing and Function Return

A lot of execution time of an application is spent transferring parameters between functions. The fastest parameter transport is via registers. If not enough registers are available, some parameters are passed via registers, the other parameters are passed over the stack. Therefore, the compiler passes the initial arguments to a function via the registers R0-R3 and R6. 16-bit arguments will be aligned to the next word register, 32-bit arguments will be aligned to the next double word register and 64-bit arguments will be aligned to the next long double word register. See the table below. The resulting gaps can be used by subsequent 1-byte and 2-byte arguments, according to a "first fit" algorithm. Thus, the order of the arguments may change.

Parameter type
Parameter order char int,
2-byte pointer,
structure <= 2-byte
long, float,
4-byte pointer,
structure <= 4-byte
double
1 R0L R0 R1:R0 R3:R2:R1:R0
2 R0H R1 R3:R2
3 R1L R2
4 R1H R3
5 R2L R6
6 R2H
7 R3L
8 R3H
9 R6L
10 R6H

Table 3-10: Register usage for parameter passing

Example with four register arguments:

- a (first parameter) is passed in register R0L.
- b (second parameter) is passed in register R3:R2.
- c (third parameter) is passed in register R1.
- d (fourth parameter) is passed in register R0H.

All parameters of a variable argument list function are always passed over the stack. Parameters are pushed in reverse order, so all ANSI C macros defined in stdarg.h can be applied.

Example with variable argument function:

- all parameters (including format) are passed via the stack.

The function qualifier _stackparm changes the standard calling convention of a function into a convention where all function arguments are passed via the stack. This keyword is very convenient for interfacing with (existing) assembly functions or when register usage must be minimized:

Function return is performed via register R0-R3. See the table below.

Return type Register(s)
bit R0.0
char R0L
short/int R0
long R1:R0
float R1:R0
double R3:R2:R1:R0
structure <= 2 bytes R0
structure <= 4 bytes R1:R0
structure > 4 bytes R0 (2-byte stack pointer)
2-byte pointer R0
4-byte pointer R1:R0

Table 3-11: Register usage for function return types

3.6 Automatic Variables

In C the register type qualifier is a means for the programmer to tell the compiler that the variable will be used very often. So the code generator must try to reserve a register for this variable and use this register instead of the stack location of this automatic variable. The compiler uses an efficient allocation scheme to decide which of the automatic objects and parameter objects that are used the most, are to be allocated within registers. Because of this allocation scheme cxa ignores the register keyword.

Automatic variables are allocated on the user stack and are addressed using the indexed addressing mode.

The code generator of cxa uses a 'saved by callee' strategy. The registers R4, R5 and R6 are saved by the called function instead of being saved by the caller. 'Saved by callee' means that a function which needs one or more registers for register variables, must save the contents of these 'registers' and restore them before returning to the caller. The major advantage of this approach is, that only registers which are really used by the function are saved. If the function does not have any register variables, the registers of the caller function remain valid without being saved.

3.7 Initialized Variables

Non automatic initialized variables use the same amount of space in both ROM and RAM (for all possible RAM memory spaces). This is because the initializers are stored in ROM and copied to RAM at start-up. This is completely transparent to the user. The only exception is an initialized variable residing in ROM, by means of the _rom storage type specifier.

See section 3.9 Strings for details on how to keep strings in ROM only.

Examples (small memory model) :

int        i = 100;       /* 2 bytes in rom and
                             2 bytes in DATA */
_rom  int  j = 3;         /* 2 bytes in CODE ROMDATA */
_rom  char a[] = "HELP";  /* 5 bytes in CODE ROMDATA */
_near char c = 'a';       /* 1 byte in IDATA ROMDATA and
                             1 byte in DATA */

3.8 Type Qualifier volatile

You can use the volatile type qualifier when modifications on the object have undesired side effects when they are performed in the regular way. Memory locations may not be updated because of compiler optimizations, which attempt to save a memory write by keeping the value in a register. When a variable is declared with the volatile qualifier, the compiler disables such optimizations.

The ANSI report describes that the updates of volatile objects follow the rules of the abstract machine (the target processor) and thus access to a volatile object becomes implementation defined.

Example:

3.9 Strings

In this section the word 'strings' means the separate occurrence of a string in a C program. So, array variables initialized with strings are just initialized character arrays, which can be allocated in any memory type, and are not considered as 'strings'. See section Initialized Variables for more information on this topic.

Strings and literals in a C source program, which are not used to initialize an array, have static storage duration. The ANSI standard does not require that these strings be modifiable. Allocating the strings in ROM is therefore possible. By default strings are allocated in the XA code space. However, since the code and data spaces of the XA are completely separate, and are accessed using different instructions, strings and literals are copied from code to data space by the startup code. Therefore, strings will be placed in sections with the INIT attribute by default.

It is possible to place ROM in the XA data space and allocate all strings and literals in it. When you use the -Sd option cxa will place strings in this ROM data area. So, strings will then be placed in sections with the attribute (I/H)DATA ROMDATA instead of (I/H)DATA INIT.

But it is also possible to allocate strings and literals in the ROM code space only. When you use the -Sc option cxa will place strings in this ROM code area. So, strings will then be placed in sections with the attribute (H)CODE ROMDATA instead of (I/H)DATA INIT. This is the default for the Huge memory model. Unqualified default pointers are generic in the Huge memory model. A generic string pointer may point to a string located in the XA ROM code or RAM data space. At run-time the space is determined when dereferencing a generic pointer, therefor it is not needed to copy strings and literals in the Huge memory model.

Where strings in data space are placed depends on the specified memory model. If the -Sc option is used, the compiler places all strings in ROM code space only. Library routines containing pointer arguments always expect the target memory of these pointers to be the default data space of the memory model used to make this library.

For example:

In the large memory model, this means printf() expects the address of the format string (the first argument) to have memory type _far. By default, the C startup code of the large memory model copies all strings from ROM code space to a section with attribute HDATA INIT in the RAM data space and if the -Sd option is used all strings are only allocated in a section with attributes HDATA ROMDATA in the ROM data space. So, the statement:

is executed correctly, because cxa passes the address of the allocated HDATA area to printf().

However, when using a microcontroller in a single chip application, you must be able to allocate strings in ROM code space only, and adapt your C source code to access these strings. Only some _rom qualifications are needed to initiate MOVC code generation for accessing data allocated in the ROM code space. The next example shows how strings can be placed in ROM code space only:

With the -Sc option on the command line:

The definition of pointer 'world' should change because it is pointing to ROM code space now instead of ROM or RAM data space.

These ROM code space typed strings can be accessed via a pointer to _rom or _generic, so C functions can be made to manipulate (copy, print) these strings in a user friendly way.

See section 3.10.1 Generic Pointers for more details on using generic pointers for accessing data from different spaces.

The standard ANSI-C library does not contain copy/move functions from and to non-default memory, which all are defined in the header file string.h. Because standard library functions expect addresses in the data space only, these strings cannot be passed to these functions. All source code is delivered to be used for making your own _rom or _generic qualified versions. For example, printf() can be used for a _rom_printf() . For example, a _rom_printf() function should have the following prototype:

and a _generic_printf() function should have the following prototype:

The compact memory model supports an extra ROM storage qualification called _rom_cs. This qualifier is the same for strings as the already mentioned ROM qualifier _rom. Only _rom_cs qualified strings reside in the default ROM data segment identified by the code segment register CS. The strings are restricted to reside in one 64Kb ROM segment, with the advantage that they can be accessed indirectly with 16-bit referring to the default segment CS. _rom qualified objects always require 24-bit indirect access for the large memory models medium, compact and large, because they can reside anywhere in the 24-bit address space of the XA. If the -Scs option is used, which is only supported for the compact memory model, the compiler places all strings in the default 64Kb ROM code space identified by the contents of CS.

The compact and medium memory models support an extra ROM storage qualification called _rom_pc0 or _rom_pc1. This qualifier is the same for strings as the already mentioned ROM qualifier _rom. Only _rom_pc0 or _rom_pc1 qualified strings reside in the default ROM data segment identified by the segment of the program counter PC. The strings are restricted to reside in one of the two 64Kb ROM segments which belong to the default data groups PC0 and PC1, with the advantage that they can be accessed indirectly with 16-bit referring to the default segment of the program counter PC. _rom qualified objects always require 24-bit indirect access for the large memory models medium, compact and large, because they can reside anywhere in the 24-bit address space of the XA. If the -Sc0 or -Sc1 option is used, which is only supported for the compact and medium memory models, the compiler places all strings in the default 64Kb ROM code space identified by the contents of PC.

ANSI string concatenation is supported: adjacent strings are concatenated - only when they appear as primary expressions - to a single new one. The result may not be longer than the maximum string length (509 characters).

The Standard states that identical string literals need not be distinct, that is, may share the same memory. Because memory can be very scarce with microcontroller applications, the cxa compiler overlays identical strings within the same module.

In section 3.1.4 the Standard states that behavior is undefined if a program attempts to modify a string literal. Because it is a common extension to ANSI (A.6.5.5) that string literals are modifiable, there may be existing C source modifying strings at run-time. This can be done either with pointers, or even worse:

cxa does not allow the modification of a string literal when option -Sd or -Sc is used.

3.10 Pointers

Some objects have two types: a 'logical' type and a storage type. For example, a function is residing in ROM (storage type), but the logical type is the return type of this function. The most obvious C type having different storage and logical type is a pointer. For example:

means p has storage type _near (allocated in direct addressable RAM), but has logical type 'character in target memory space CODE ROMDATA'. The memory type specifier used left to the '*', specifies the target memory of the pointer, the memory specifier used right to the '*', specifies the storage memory of the pointer.

The memory type specifiers are treated like any other type specifier (like unsigned). This means the pointer above can also be declared (exactly the same) using:

If the target memory and storage memory of a pointer are not explicitly declared, cxa uses the default of the memory model selected. For example, in the tiny model, the declaration:

is exactly the same as:

cxa recognizes different types of pointers _generic, _near, _far, _far_es, _huge, _edata, _rom, _rom_cs, _rom_pc0, _rom_pc1:

Pointer Type Description
_near Page Zero or Large Memory mode (all memory models):
Pointers declared with _near are 16-bit values. Pointer arithmetic is carried out in 16-bit.
_far Page Zero mode (tiny or small memory model):
Pointers declared with _far are 16-bit values. Pointer arithmetic is carried out in 16-bit. Large Memory mode (medium or compact memory model):
Pointers declared with _far are 16-bit value offsets in default data segment DS. Pointer arithmetic is carried out in 16-bit. Large Memory mode (large or huge memory model):
Pointers declared with _far are 32-bit values. Pointer arithmetic is carried out in 16-bit, the segment register is not affected, i.e., the objects pointed to must reside within one segment.
_far_es Large Memory mode (compact memory model only):
Pointers declared with _far_es are 16-bit value offsets in default data segment ES. Pointer arithmetic is carried out in 16-bit.
_huge Large Memory mode (medium, compact, large or huge memory model):
Pointers declared with _huge are 32-bit values. Pointer arithmetic is carried out in 24-bit, the segment register is updated when required. The objects pointed to may reside in multiple segments.
_rom Page Zero mode (tiny or small memory model):
Pointers declared with _rom are 16-bit values. Pointer arithmetic is carried out in 16-bit. Large Memory mode (medium, compact, large or huge memory model):
Pointers declared with _rom are 32-bit values. Pointer arithmetic is carried out in 24-bit, the segment register is updated when required. The objects pointed to may reside in multiple segments.
_rom_cs Large Memory mode (compact memory model):
Pointers declared with _rom_cs are 16-bit value offsets in default code segment CS. Pointer arithmetic is carried out in 16-bit.
_rom_pc0
_rom_pc1
Large Memory mode (compact or medium memory model):
Pointers declared with _rom_pc0 or _rom_pc1 are 16-bit value offsets in the segment indentified by PC which belong to the default ROM data group PC0 or PC1. Pointer arithmetic is carried out in 16-bit.
_generic Page Zero mode (tiny or small memory model):
Pointers declared with _generic are 32-bit values. Pointer arithmetic is carried out in 16-bit. The objects pointed to may reside in the code or the data space.

Large Memory mode (medium, compact, large or huge memory model):
Pointers declared with _generic are 32-bit values. Pointer arithmetic is carried out in 24-bit. The objects pointed to may reside in multiple segments of the code or the data space
_edata Page Zero or Large Memory mode (all SmartXA memory models and SmartXA system mode only):
Pointers declared with _edata are 16-bit values. Pointer arithmetic is carried out in 16-bit.

Table 3-12: Pointer types

Another example:

The structure 's' resides in DATA and the compiler allocates 4 or 6 bytes for this structure: 2 bytes for 'length' and 2 or 4 bytes for 'p' depending on the memory model (4 for the large model, 2 for all other models).

In pointer arithmetic cxa checks, besides the type of each pointer, also the target memory of the pointers, which should be the same. You can always convert from 2-byte to 4-byte pointer. When converting to a smaller pointer size, the compiler will warn you for potential loss of information.

3.10.1 Generic Pointers

Generic pointers or universal pointers are storage space independent pointers. The type of a pointer is generic when it is does not explicitly identify to which storage space it is pointing. Instead a generic pointer contains the address as well as the address space of the object it is pointing to.

See section 3.10.1.2 Generic Pointer Size and Format for details.

Generic pointers are only of interest for processors that use different instructions for accessing different address spaces. Most Harvard architectures like the XA use different instructions for accessing memory located on the code bus and data bus. The XA uses a special instruction MOVC for accessing ROM on the code bus and data MOV instructions for accessing the data bus.

To instruct the compiler to generate the correct instructions, pointers and the objects they are pointing to need to be qualified. This kind of storage qualification is supported with the so-called 'storage type specifiers'.

See section 3.2.1 Storage Types for details.

In contradiction to the above, generic pointers are not type qualified with address space information when they are declared. Instead, code is generated to determine at run-time the address space a generic pointer is pointing to.

The benefit of generic pointers is the ease of use and portability of code. You do not have to type qualify pointers for the correct address space. So you only have to write the functions once when using generic pointers in its parameter interface. Or you can use the Huge memory model that uses generic pointers for all unqualified default pointers. The drawback of generic pointers is larger code size and a decrease in code speed, which is the consequence of determining the correct address space at run-time.

3.10.1.1 The _generic Pointer Type

You can define a generic pointer by using the _generic keyword. The _generic keyword can only be used in the logical type of a pointer to identify that the target memory pointing to can be located on the code bus as well as on the data bus.

Example of a pointer residing in _near, pointing to an integer object located in memory on the code bus or data bus.

Generic pointers are not defined to be implicitly const type qualified, because generic pointers can be used for read and write operations to any space. No error is generated when you try to write to the code space using a generic pointer. Writing to the code space using a generic pointer will result in undefined run-time behavior. Instead a warning is generated when you assign the address of a const variable to a non-const generic pointer. Examples:

The warning "W 130: operands of '=' are pointers to different types" is generated when assigning the address of a constant variable to a non constant generic pointer. The _rom storage type specifier used for the declaration of the variable 'code' is defined to imply const (_rom is equal to _rom const).

On behalf of performance, the ANSI C standard does not prescribe run-time checking for integral and pointer operations. It is not preferred to check on illegal run-time operations for generic pointer access. No code is generated or implemented in run-time libraries for generic pointers that checks on write operations to the code space.

Generic function pointers are assumed to point to the code space of the XA. On behalf of performance, no code is generated that checks at run-time if a function is called indirectly from the data space of the XA.

Next example shows how generic pointers can be used to type qualify function parameters, to define one interface function for accessing constant data from the code and data space.

See section 3.9 Strings for details on how to allocate strings in ROM only.

3.10.1.2 Generic Pointer Size and Format

The size of a generic pointer is always 32-bit. The most significant bit of a generic pointer is used to identify if the pointer is pointing to memory located on the code bus or on the data bus of the XA.

A generic pointer refers to an object located in memory on the data bus when its most significant bit is set to zero and it refers to an object located in memory on the code bus when it is set to one. The storage space identifier bit of a generic pointer is set at initialization or at assignment of a generic pointer conform the storage space of the object it is referring to.

1 7 8 16 .. widths
s u g o
msb lsb msb lsb msb lsb .. order
s Storage space (0 for data bus, 1 for code bus)
u Unused
g Segment number
o Segment offset
msb Most significant bit
lsb Least significant bit

Figure 3-1: Generic pointer format in Large Memory mode

1 15 16 .. widths
s u o
msb lsb msb lsb .. order
s Storage space (0 for data bus, 1 for code bus)
u Unused
o Address offset
msb Most significant bit
lsb Least significant bit

Figure 3-2: Generic pointer format in Page Zero mode

A constant address can be assigned to a generic pointer by casting a constant value to be a generic pointer. Confirm that the generic pointer format constant values can be used for generic pointer assignments. For example, int _generic * p = (_generic int*)0x8000002, is a reference to an object located on address 2 in the code space. When the storage type specifier bit is not set in the constant value it is defined to be a constant address to the data space.

3.10.1.3 Generic Pointer Arithmetic

The arithmetic of a generic pointer must be equal to the arithmetic of the pointer that has the largest arithmetic supported by a memory model.

Generic pointer arithmetic is 32-bit for the Large memory mode models; medium, compact and large. It is required to use 32-bit pointer arithmetic for generic pointers, because they may refer to _rom and _huge qualified objects. Pointers qualified with this type of storage use 32-bit pointer arithmetic, because the object size of a _rom or _huge qualified object can be larger then 64KB.

Generic pointer arithmetic is 16-bit for the Page Zero memory models tiny and small. The address range for code and data space is limited to 16-bit in Page Zero mode, thus only requires generic pointers with 16-bit arithmetic.

Generic pointer subtraction is only possible when the storage space type identifier bit is the same for both operands. So, generic pointers can only be subtracted when both point to code space or data space. The behavior of subtracting generic pointers to different spaces is undefined. The result type of a generic pointer subtraction is long for the Large memory models and the result type of a generic pointer subtraction is int for the Page Zero memory models.

Generic pointer comparison is equal to unsigned long compare. The generic storage type bit is included in the unsigned long compare.

3.10.1.4 Generic Pointer Conversion

Assigning pointers with different storage types requires pointer conversion. Any pointer type can be converted to a _generic pointer, except for _xdat type qualified pointers.

Conversion of a generic pointer to any other pointer type may lead to run-time errors. A generic pointer contains an address reference to an object located on the code bus or data bus. When you assign a generic pointer that refers to a code space object to a data space pointer the result will be a run-time error when you de-reference the data space pointer. Also, when you assign a generic pointer that refers to a data space object to a code space pointer the result will be a run-time error when de-referencing the code space pointer. The XA C compiler cannot check at compile time on this illegal conversion, because generic pointers are dynamically assigned. On behalf of the performance, no code is generated to check at run-time if conversion from generic pointer is allowed. All conversions from generic pointers will result in a warning that there may be an incompatible storage space conversion that may lead to a run-time error.

3.10.1.5 Generic Pointer Restrictions

You can only declare generic pointers using the _generic keyword.

The XDATA space of the XA is not supported by the generic pointers. Data objects that are storage type qualified with _xdat are allocated in the XDATA space of the XA. The XA uses a special instruction MOVX for XDATA access.

The XDATA space is meant for 8051 compatibility. Supporting XDATA with generic pointers requires extra run-time checks. Due to this overhead and the limited use, XDATA typed generic pointers are not supported. An error is generated by the compiler when an _xdat qualified pointer, address, label or constant value is assigned to a generic pointer.

The generic pointer support is also not available for SmartXA applications running in user mode, because DS and ES segment register access is not allowed. An error is generated when the _generic keyword is used for SmartXA user mode only code generation.

3.11 Inline C Functions

The _inline keyword is used to signal the compiler to inline the function body instead of calling the function. An inline function must be defined in the same source file before it is 'called'. When an inline function has to be called in several source files, each file must include the definition of the inline function. Usually this is done by defining the inline function in a header file.

Not using a function which is defined as an _inline function does not produce any code.

Example (t.c):

No specific debug information is generated for inline functions. The debugger cannot step-into an inline function, it considers the inline function as one HLL source line.

The pragmas asm and endasm are allowed in inline functions. This makes it possible to define inline assembly functions. See also section 3.12 Inline Assembly in this chapter.

The generated code is:

3.12 Inline Assembly

cxa supports inline assembly using the following pragmas:

#pragma asm Insert assembly text following this pragma.

#pragma asm_noflush As #pragma asm, but the peephole optimizer does not flush the code buffer.

#pragma endasm Switch back to the C language.

C modules containing inline assembly are not portable and are very hard to prototype in other environments.

The peephole optimizer in the compiler maintains a code buffer for optimizing sequences of assembly instructions before they are written in the output file. The compiler does not interpret the text of inline assembly. It passes inline assembly lines directly to the output file. To prevent that instructions in the peephole buffer, which belong to C code before the inline assembly lines, will be written in the output file after the inline assembly text, the compiler flushes the instruction buffer in the peephole optimizer. All instructions in the buffer are written to the output file. If this behavior is not desired the pragma asm_noflush starts inline assembly without flushing the code buffer.

Section 7.8 Assembly Language Interfacing in the chapter Run-time Environment.

3.13 Calling Assembly Functions

For a fixed register-based interface between C and assembly functions the function qualifier _asmfunc is available. This function qualifier can be used for a prototype of an assembly function to be called from C.

Example:

The number of arguments that can be passed is limited by the number of available registers. If too many arguments are used, the compiler will issue an error. Passing some parameters over the stack is not an option, because the interface would become complex and the _asmfunc qualifier loses its value, i.e., creating a simple interface between C and assembly functions.

3.14 Intrinsic Functions

When you want to use some specific XA instructions, that have no equivalence in C, you would be forced to write assembly routines to perform these tasks. However, cxa offers a way of handling this in C. Therefore, cxa has a number of built-in functions, which are implemented as intrinsic functions.

To the programmer intrinsic functions appear as normal C functions, but the difference is that they are interpreted by the code generator, so that more efficient code may be generated. Several pre-declared functions are available to generate inline assembly code for the intrinsic function (call). This avoids the overhead that is normally introduced by parameter passing and context saving before executing the called function.

The names of the intrinsic functions all have a leading underscore, because the ANSI specification states that public C names starting with an underscore are implementation defined.

The advantages of using intrinsic functions, compared with in-line assembly (pragma asm/endasm) are:

The following intrinsic functions are implemented:

Function Description
_strmovc() Copy string from code space ROM to data space RAM
_strmovx() Copy string from external RAM to RAM
_memmovc() Copy characters from code space ROM to data space RAM
_memmovx() Copy characters from external RAM to RAM
_testclear() Read and clear bit
_da() Decimal adjust
_nop() NOP instruction, not optimized away
_rol() Rotate left
_ror() Rotate right
_rol8() Rotate byte left
_ror8() Rotate byte right
_rolc8() Rotate byte left through carry
_rorc8() Rotate byte right through carry
_rol16() Rotate word left
_ror16() Rotate word right
_rolc16() Rotate word left through carry
_rorc16() Rotate word right through carry
_intxa() Software interrupt
_div32() 32-bit by 16-bit signed divide
_divu32() 32-bit by 16-bit unsigned divide
_mod32() 32-bit by 16-bit signed modulo
_modu32() 32-bit by 16-bit unsigned modulo
_div16() 16-bit by 8-bit signed divide
_divu16() 16-bit by 8-bit unsigned divide
_mod16() 16-bit by 8-bit signed modulo
_modu16() 16-bit by 8-bit unsigned modulo
_getsp() Get stack pointer value
_getusp() Get user stack pointer value
_setsp() Set stack pointer value
_setusp() Set user stack pointer value
_addsp() Add value to stack pointer
_offsp() Get stack offset

Table 3-13: Intrinsic functions

Prototypes for the intrinsic functions are present in cxa.h. Below are the details of the implemented intrinsic functions (sample C source with generated assembly are given below):

_strmovc

Copy the string in ROM pointed to by s2 (including the terminating null character) into the RAM array to by s1 with the MOVC instruction. If copying takes places objects that overlap, the behavior is undefined.

Returns nothing.

_strmovx

Copy the string in (X)RAM pointed to by s2 (including the terminating null character) into the RAM array to by s1 with the MOVX instruction. If copying takes places objects that overlap, the behavior is undefined.

Returns nothing.

_memmovc

Copy n characters from the ROM object pointed to by s2 into the RAM object pointed to by s1 with the MOVC instruction. If copying takes places objects that overlap, the behavior is undefined.

Returns nothing.

_memmovx

Copy n characters from the (X)RAM object pointed to by s2 into the RAM object pointed to by s1 with the MOVC instruction. If copying takes places objects that overlap, the behavior is undefined.

Returns nothing.

_testclear

Read and clear semaphore using the JBC instruction.

Returns 0 if semaphore was not cleared by the JBC instruction, 1 otherwise.

_da

Decimal adjust operand after addition using the ADD and DA instructions.

Returns the result.

_nop

Generate NOP instructions.

Returns nothing.

_rol

Use the RL instruction to rotate byte operand count times to the left.

Returns the result.

_ror

Use the RR instruction to rotate byte operand count times to the right.

Returns the result.

_rol8

Use the RL instruction to rotate byte operand count times to the left.

Returns the result.

_ror8

Use the RR instruction to rotate byte operand count times to the right.

Returns the result.

_rolc8

Use the RLC instruction to rotate through carry byte operand count times to the left.

Returns the result.

_rorc8

Use the RRC instruction to rotate through carry byte operand count times to the right.

Returns the result.

_rol16

Use the RL instruction to rotate word operand count times to the left.

Returns the result.

_ror16

Use the RR instruction to rotate word operand count times to the right.

Returns the result.

_rolc16

Use the RLC instruction to rotate through carry word operand count times to the left.

Returns the result.

_rorc16

Use the RRC instruction to rotate through carry word operand count times to the right.

Returns the result.

_intxa

Execute a software interrupt specified by the interrupt vector number vector_number via the software TRAP instruction (vector_number = vector_address/4). RESET is generated when vector_number is 0. ICE denotes that the operand must be an Integral Constant Expression rather than any type of integral expression.

Returns nothing.

See figure 3-3 in section Interrupt and Using, for more information on interrupt vectors.

_div32

Use DIV.d instruction to perform a 32-bit by 16-bit signed divide and returning a signed 16-bit result. The overflow bit V is set by the CPU when the quotient cannot be represented in an int data type or when the divisor y was zero.

Returns the result when no overflow occurs.

_divu32

Use DIVU.d instruction to perform a 32-bit by 16-bit unsigned divide and returning an unsigned 16-bit result. The overflow bit V is set by the CPU when the quotient cannot be represented in an int data type or when the divisor y was zero.

Returns the result when no overflow occurs.

_mod32

Use DIV.d instruction to perform a 32-bit by 16-bit signed modulo and returning a signed 16-bit result. The overflow bit V is set by the CPU when the quotient cannot be represented in an int data type or when the divisor y was zero.

Returns the result when no overflow occurs.

_modu32

Use DIVU.d instruction to perform a 32-bit by 16-bit unsigned modulo and returning an unsigned 16-bit result. The overflow bit V is set by the CPU when the quotient cannot be represented in an int data type or when the divisor y was zero.

Returns the result when no overflow occurs.

_div16

Use DIV.w instruction to perform a 16-bit by 8-bit signed divide and returning a signed 8-bit result. The overflow bit V is set by the CPU when the quotient cannot be represented in a char data type or when the divisor y was zero.

Returns the result when no overflow occurs.

_divu16

Use DIVU.w instruction to perform a 16-bit by 8-bit unsigned divide and returning an unsigned 8-bit result. The overflow bit V is set by the CPU when the quotient cannot be represented in a char data type or when the divisor y was zero.

Returns the result when no overflow occurs.

_mod16

Use DIV.w instruction to perform a 16-bit by 8-bit signed modulo and returning a signed 8-bit result. The overflow bit V is set by the CPU when the quotient cannot be represented in a char data type or when the divisor y was zero.

Returns the result when no overflow occurs.

_modu16

Use DIVU.w instruction to perform a 16-bit by 8-bit unsigned modulo and returning an unsigned 8-bit result. The overflow bit V is set by the CPU when the quotient cannot be represented in a char data type or when the divisor y was zero.

Returns the result when no overflow occurs.

_getsp

Get the value of the stack pointer R7.

Returns the stack pointer value.

_getusp

Get the value of the user stack pointer USP.

Returns the stack pointer value.

_setsp

Set the value of the stack pointer R7.

Returns nothing.

_setusp

Set the value of the user stack pointer USP.

Returns nothing.

_addsp

Add signed value to stack pointer R7. The operand value must be an Integral Constant Expression rather than any type of intergral expression.

Returns nothing.

_offsp

Get stack offset in number of bytes. The stack offset is the total amount of stack space occupied by arguments passed on the stack, automatic objects, temporary saved registers, registers pushed in the interrupt frame and the return address of the current function.

Returns stack offset.

3.15 Interrupt and Using

The XA C language introduces two new reserved words: _interrupt and _using, which can be seen as special type qualifiers, only allowed with function declarations. A function can be declared to serve as an interrupt service routine. Interrupt functions cannot return anything and must have a void argument type list. For example, in:

The _interrupt function qualifier takes a list of one or more vector_numbers, that define the interrupt vector numbers, i.e. the vector addresses. The relation between the vector_number and the vector address is: vector_number = vector_address/4.

Argument psw of the _using function qualifier defines the value of the PSW placed in the interrupt vector table. PSW bits RS1 and RS2 select the active register bank used by the interrupt handler.

Because the vector is filled by the compiler (unless disabled by the -v option or novector pragma), the interrupt number and using number must be specified. The vector range of the XA is 0-70; other interrupt vectors are not supported by the XA core. Figure 3-3 contains an overview of the interrupt vectors.

Multiple pairs of _interrupt and _using qualifiers are allowed per interrupt service routine. This is to have multiple vectors pointing to the same interrupt service routine. A number of arguments can be specified in the _interrupt and _using qualifiers separated by a comma or different _interrupt and _using qualifiers can be specified per interrupt service routine. The number of arguments in the _interrupt and _using qualifier must be equal.

The hardware reset interrupt is used by the compiler in the run-time library startup code.

Figure 3-3: Interrupt vectors

_pagezero function qualifier

You can use the _pagezero function qualifier for _interrupt functions to define that the interrupt function itself is located in segment zero. The jump chain required in Large memory mode for calling interrupt functions outside segment zero is suppressed for _pagezero qualified interrupt service routines. This decreases the interrupt latency for the Large memory mode models Medium, Compact, Large and Huge.

_frame function qualifier

With the _frame function qualifier you can specify which registers must be saved for a particular interrupt function. Only the specified registers will be pushed and popped from the stack. The syntax is:

where, reg can be one of the following registers: R0..R6,DS,CS,ES or SSEL.

A warning is generated if some registers are missing which are normally required to be pushed and popped in an interrupt function prolog and epilog to avoid run-time problems.

Example:

See also section Interrupt Functions in chapter Run-time Environment.

3.16 Function Call with Software Trap Instruction

You can use the _trap function qualifier for functions to define that a function is called via a software trap instruction. The keyword _trap is allowed with function declarations and function prototypes. Contrary to an interrupt function a function declared with _trap can have parameters and can have a return value.

The syntax is:

The _trap function qualifier takes one argument trap_nr that defines the software trap number. trap_nr is a number in the range 0-15.

The _using function qualifier must be used in combination with the _trap function qualifier to define the value of PSW placed in the interrupt vector table.

The trap_nr is filled by the compiler (unless disabled by the -v option or novector pragma) with the software trap number and using number specified.

3.17 Safer C

Based upon the 'MISRA guidelines for the application of C language invehicle based software', the TASKING Safer C technology offers enhanced compiler error checking that will guide the programmer in writing better, more coherent and intrinsically safer applications. Through this configurable system of enhanced C language error checking, the use of error-prone C constructs can be prevented. A predefined configuration for compliance with the 'required rules' described in the MISRA guidelines is selectable through a single click in the EDE|Safer C Compiler Options menu. A custom set of applicable Safer C rules can be easily configured using the same menu. It is also possible to have a project team work with a Safer C configuration common to the whole project. In this case the Safer C configuration can be read from an external settings file. This too, is easily selected through the EDE|Safer C Compiler Options menu.

Unfortunately it has not been possible to implement support for all 127 rules described in the MISRA guidelines. The reason for this is that a number of rules are beyond the scope of what can be checked in a C compiler environment. These unsupported rules are visible in the EDE|Safer C Compiler Options menu dialog boxes, but cannot be selected (grayed out).

MISRA is a registered trademark of MIRA held on behalf of the Motor Industry Software Reliability Association.

Enabling Safer C

From the command line Safer C can be enabled by the following compiler option:

where n specifies the rule(s) which must be checked.

Error Messages

In case a Safer C rule is violated, an error message will be generated
e.g.:

See Appendix B, Safer C, for the supported and unsupported Safer C rules.

3.18 Structure Tags

A tag declaration is intended to specify the lay-out of a structure or union. If a memory type is specified, it is considered to be part of the declarator. A tag name itself, nor its members can be bound to any storage area, although members having type "... pointer to" do require one. A tag may then be used to declare objects of that type, and may allocate them in different memories (if that declaration is in the same scope). The following example illustrates this constraint.

struct S {
   _near int i;   /* referring to storage: not correct */
   _far char *p;  /* used to specify target memory: correct */
   };

In the example above cxa ignores the erroneous _near storage specifier (without displaying a warning message).

3.19 Typedef

Typedef declarations follow the same scope rules as any declared object. Typedef names may be (re-)declared in inner blocks but not at the parameter level. However, in typedef declarations, memory specifiers are allowed. A typedef declaration should at least contain one type specifier.

Examples:

typedef _near int NEARINT;    /* storage type _near: OK */
typedef int _near *PTR;       /* logical type _near
                                 storage type 'default' */

3.20 Switch Statement

cxa supports two ways of code generation for a switch statement: a jump chain (linear switch) or a jump table.

A jump chain is comparable with an if/else-if/else-if/else construction. A jump table is a table filled with case label entry points for each possible switch value. The switch argument is used as an index in the jump table. An indirect jump is performed to the indexed case label entry point.

By default, the compiler will try to use the switch method which uses the least space in ROM.

It is obvious that, especially for large switch statements, the jump table approach executes faster than the jump chain approach. Also, the jump table has a predictable behavior in execution speed. Every case is reached in the same execution time.

With a small number of cases, the jump chain method can be faster in execution and shorter in size.

The compiler chosen switch method can be overruled by using one of the following option combinations:

The last one is also the default of the compiler. Using a pragma cannot overrule the restrictions as described earlier.

3.21 Portable C Code

If you are developing C code for the XA using cxa, you might want to test some code on the host you are working on, using a C compiler for that host. Therefore, we deliver the include file cxa.h. This header file checks if _CXA is defined (cxa only), and redefines the storage type specifiers if it is not defined.

When using this include file, you are able to use the storage type specifiers (when needed) and yet write 'portable C code'.

Furthermore an adapted prototype of each C built-in function is present, because these functions are not known by another ANSI compiler. If you use these functions, you should write them in C, performing the same job as the XA and link these functions with your application for simulation purposes.

For compatibility with existing C-51 programs, the file c51.h is delivered.

3.22 How to Program Smart

If you want to get the best code out of cxa, the following guidelines should be kept in mind:

1. Always use function prototyping. So, char variables can be passed as char without being promoted to int.

2. If you are using the large model (because it is not possible to use a smaller model), try to declare the most frequently used variables (static) with storage type _near.

We recommend to use the smallest model (from tiny, small, medium, compact, large to huge) which best fits your application and explicitly declare big data items as _far or _huge. You can make your own C functions to access these far data objects. If you want to use the Standard C library functions on far data objects, you have to use the large model.

3. Try to use the unsigned qualifier as much as possible (e.g. for (i = 0; i < 500; i++) ), because unsigned comparisons require less code than signed comparisons.

4. Try to use the smallest data type as possible: bit for boolean usage (flags), character for small loops and so on. See also sections 3.3.2 Character Arithmetic and 3.3.3 The _bit Type.

5. If execution speed is important (e.g. interrupt functions and time consuming loops), you must use the -OS option or #pragma optimize S.

3.23 Some Examples of Complex Declarators

Because cxa has some extensions to support the various memory types of the XA processor family, declarations of objects may need some explanation.

First of all, declaration of simple objects is done exactly the same way as in standard C.

For example:

When programming portable C code, declaration of pointers is also standard.

For example:

However, for code density it may be desired to place an object in another memory area, this can be done by preceding the object type by the requested data area specifier.

For example:

also correct is :

Now, pointers to another area than the default (specified by the memory model, see section 4.2.1 Detailed description of the Compiler Options) are declared as follows:

_near char * pnc; Pointer resides in default memory, points to a character in near.

_near int * pni; Pointer resides in default memory, points to an integer in near.

_far long * pfl; Pointer resides in default memory, points to a long in far.

_huge long * phl; Pointer resides in default memory, points to a long in far.

Even more difficult, these pointers may be placed in some other data area than the default.

For example:

_near char * _near npnc; Pointer resides in near, points to a character in near.

_near int * _near npni; Pointer resides in near, points to an integer in near.

_far long * _near fppl; Pointer resides in near, points to a long in far.

_huge long * _near hppl; Pointer resides in near, points to a long in huge.

Using objects located in _near always produce less code than objects in _far/_huge. So the smallest code size (and often the fastest execution speed) can be achieved by placing as many objects as possible in _near. When it is not possible to place all objects in internal RAM, select the objects which are most referenced in the code.

Some examples of complex declarators are given below.

Now ppp is a pointer located in near, points to a pointer in default memory, this points to a pointer in far, which is a pointer to a character in near.

Now fp is a pointer located in near, points to a function with no arguments, returning a pointer to an integer in far.


Copyright © 2000 TASKING, Inc.