This chapter contains the following sections:
Introduction
Accessing Memory
Storage Types
The _at( ) Attribute
The _atbit( ) Attribute
Data Types
Signed Characters
ANSI C Type Conversions
Fractional Data Types
Additional Basic Types
Type Conversions
Promotion Rules
Intrinsic Functions
Type Qualifier _sat
Packed Data Types
Additional Basic Types
Intrinsic Functions
Halfword Packed Unions and Structures
Bit Data Type
The _bit Type
Type Qualifiers _sfrbit16 and _sfrbit32
Parameter Passing
System Call Function Qualifier
Stack Model Function Qualifiers
Function Qualifiers
Interrupt Function Qualifiers
Trap Function Qualifiers
Enable Interrupt/Trap Function Qualifier
Bisr Interrupt/Trap Function Qualifier
System Call Function Qualifier
Stack Model Function Qualifier
Far Function Storage Qualifier
Type Qualifier volatile
Type Qualifiers restrict and _restrict
Strings
Variable Argument Lists
Inline C Functions
Inline Assembly
Intrinsic Functions
MISRA C
Structure Tags
Typedef
Circular Buffers
Switch Statement
The TASKING C cross-compiler (ctri) offers a new approach to high-level language programming for the TriCore family. It conforms to the ANSI standard, but allows you to control the special functions of the TriCore in C.
This chapter describes the C language implementation in relation to the TriCore architecture.
The extensions to the C language in ctri are:
In addition to the standard data type, ctri supports three additional basic types to perform fixed point arithmetic (_fract, _sfract and _accum). Two additonal basic types were added to the C compiler to support the packed arithmetic instructions (_packb and _packhw). The intregal type _bit is added to support the bit instructions.
You can specify a variable to be at an absolute address.
You can specify a variable to be at a bit offset within a _bitword or bit-addressable _sfr variable.
You can use the type qualifiers _sfrbit16 and _sfrbit32 to control the access of SFR bit fields.
Apart from a memory category (extern, static, ...) you can specify a storage type in each declaration (_near, _far, _a0, _a1, _a8, _a9).
You can specify interrupt functions directly through interrupt vectors in the C language (_interrupt and _interrupt_fast keywords).
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.
ctri supports circular buffers through the data type _circ.
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 storage types 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 have some of the following properties. They
- access I/O, using the special function registers
- need high execution speed
- need high code density
- access non-default memory
- are used to service interrupts
Static storage specifiers can be used to allocate static objects in a particular memory area of the addressing space of the processor. All objects taking static storage may be declared with an explicit storage specifier. By default static variables will be allocated in _near or _far memory according to some heuristic rules (see -N option).
ctri recognizes the following storage type specifiers:
Storage Type | Description |
_near | The data object must be directly addressable using the absolute addressing mode. The first 16K of each 256M block are directly addressable. |
_far | The data must not be allocated in a direct addressable memory region. |
_a0 | The data is allocated in a section that is addressable with a sign-extended 16-bit offset from A0. |
_a1 | The data is allocated in a section that is addressable with a sign-extended 16-bit offset from A1. |
_a8 | The data is allocated in a section that is addressable with a sign-extended 16-bit offset from A8. |
_a9 | The data is allocated in a section that is addressable with a sign-extended 16-bit offset from A9. |
Table 3-1: Storage type specifiers
int _near Var_in_near; /* fast accessible integer in directly addressable memory */ int _near * _far Ptr_in_far_to_near; /* allocate pointer in _far memory, compiler will not use absolute addressing mode */ char _a0 string[] = "TriCore"; /* string in A0 memory */
Using the _near addressing qualifier, allows the compiler to generate faster access code for frequently used variables. Pointers are always 32-bit.
Functions are by default allocated in ROM Memory; the storage specifier may be omitted in that case. Also, 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:
int myvar _at(0x100);
This is useful to interface to other programs using fixed memory schemes, or to access special function registers.
Some examples of using storage specifiers:
int _near *p; // pointer to int in _near memory // (pointer has 32-bit size) int _far *g; // pointer to int in _far memory // (pointer has 32-bit size) g = p; /* the compiler issues a warning */
If a library function declares:
extern int _near foo; //extern int in _near memory
and a data object is declared as:
int _far foo; //int in _far memory
the linker will flag this as an error. The usage of the variables is always without a storage specifier:
char _near example; /* define a char in _near memory */ example = 2; /* assign example */
The generated assembly would be:
mov16 d15,2 st.b example,d15
All allocations with the same storage specifiers are collected in units called 'sections'. The section with the _near attribute will be located within the first 16K of of each 256M block. It is always possible to control the location of sections manually.
In C for the TriCore 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:
unsigned char Display[80*24] _at( 0x2000 );
The example above creates a variable with the name Display at address 0x2000. In the generated assembly code an absolute section will appear. On this position space is reserved for the variable Display.
A number of restrictions are in effect when placing variables on an absolute address:
In C for the TriCore it is possible to define bit variables within an int variable. This can be done with the _atbit() attribute. The syntax is:
name is the name of a int variable and offset (range 0-31) is the bit-offset within the variable. The int variable must be defined with the _at() attribute to make the _atbit attribute valid.
Examples:
int bw _at(0x...); _bit myb _atbit( bw, 3 );
Variable bw must have the _at attribute to make _atbit valid.
All ANSI C data types are supported. Three types of pointers are recognized. Object size and ranges:
Data Type |
Size (in bytes) | Range |
_bit | 1 | [0,1] |
signed char | 1 | -128 to +127 |
unsigned char | 1 | 0 to 255U |
signed short | 2 | -32768 to +32767 |
unsigned short | 2 | 0 to 65535U |
signed int | 4 | -2147483648 to +2147483647 |
unsigned int | 4 | 0 to 4294967295UL |
signed long | 4 | -2147483648 to +2147483647 |
unsigned long | 4 | 0 to 4294967295UL |
float | 4 | +/- 1,1755E-38 to +/- 3,402E+38 |
double | 8 | +/- 2,225E-308 to +/- 1,798E+308 |
enum | 4 | 0 to 4294967295 |
pointer | 4 | 0 to 4294967295 |
_sfract | 2 | [-1,+1> |
_fract | 4 | [-1,+1> |
_accum | 8 | [-131072,+131071> |
Table 3-2: Data types
- _bit, char, short, int and long are all integral types, supporting all implicit (automatic) conversions.
- the TriCore convention is used, storing variables with the most significant part at the higher memory address (Little Endian).
- float is implemented in little endian IEEE-754 32-bit single precision format.
- double is implemented in little endian IEEE-754 64-bit double precision format.
The character type is treated as signed char by default. You can overrule this default with the -u command line option, which sets the default to unsigned char.
Examples:
char c; signed char c;
char c; unsigned char c;
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 promotions (as defined in the ANSI C standard).
In case of the Tricore this implies that among other things, all character and short integers, whether they are unsigned or signed, will be promoted to signed integer, when integral promotions is applied.
Integral promotions 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 (as defined in the ANSI C standard).
For the TriCore this means the following:
1. if necessary use long double, double or float (in this order).
2. (un)signed char and (un)signed short are mapped onto
signed int.
3. if necessary use unsigned long, long or unsigned int
(in this order)
The following pseudo code gives the conversion algorithm in more detail:
IF either operand is long double
ELSE IF either operand is double
ELSE IF either operand is float
ELSE
Sometimes surprising results may occur, for example when
an unsigned char is promoted to int. You can always use explicit casting
to obtain the type required. The following example makes this clear:
static unsigned char a=0xFF, b, c; void f() { b=~a; if ( b == ~a ) { /* This code is never reached,because * 0x00000000 is compared to 0xFFFFFF00. * The compiler converts character 'a' to an * int before applying the ~ operator, * so '~a' yields 0xFFFFFF00 in the * assignment '~a' is cast into an unsigned * char again, so the last byte is assigned * to b => b = 0x00 in the comparison '~a' is * again 0xFFFFFF00, b however is promotod to * an integer with value 0x00000000. */ ... } c=a+1; while( c != a+1 ) { /* This loop never stops because * 0x00000000 is compared to 0x00000100. * The compiler evaluates 'a+1' as an integer * expression, so 'a+1' is 0x000000FF + * 0x00000001, is 0x00000100. * In the assignment the last byte of the * result is assigned to c, so c = 0x00. * In the comparison both sides are promoted * to integer, the left sight to 0x00000000 * and the right side to 0x00000100 again. */ ... } }
To overcome this 'unwanted' behavior use an explicit cast:
static unsigned char a=0xFF, b, c; void f() { b=~a; if ( b == (unsigned char)~a ) { /* This code is always reached */ ... } c=a+1; while( c != (unsigned char)(a+1) ) { /* This code is never reached */ ... } }
Keep in mind that the arithmetic conversions apply to multiplications also:
static int h, i, j; static long k, l, m; /* In C the following rules apply: * int * int result: int * long * long result: long * * and NOT int * int result: long */ void f() { h = i * j; /* int * int = int */ k = l * m; /* long * long = long */ l = i * j; /* int * int = int, * afterwards promoted (sign * or zero extended) to long */ l = (long) i * j; /* long * long = long */ l = (long)(i * j); /* int * int = int, * afterwards casted to long */ }
The TriCore C compiler supports three additional basic types to perform fixed point arithmetic. These types are:
16 bits: 1 sign bit + 15 mantissa bits
32 bits: 1 sign bit + 31 mantissa bits
64 bits: 1 sign bit + 17 integral bits + 46 mantissa bits
Most basic operations on the first two types are directly supported by the TriCore instruction set. For example, the following assignment:
_sfract a, b, c; ... a = b + c;
will result in the following assembly code:
ld.q d15,b ld.q d7,c add16 d15,d7 st.q a,d15
A conversion from an integer to an _sfract/_fract or visa versa is of very limited use, as there are only two numbers that are both an integer and an _sfract/_fract: 0 and -1. These conversions are most likely the result of a programming error, so the compiler will flag them as an error.
Conversions of integers to/from the _accum type may be useful and are supported.
All three fractional data types can be converted to/from the types float and double.
For the three fractional types _sfract, _fract and _accum, the promotion rules are similar to the promotion rules for char, short, int and long. This means that for an operation on two different fractional types, the smaller type will be promoted to the larger type before the operation is performed. This larger type will also be the type of the result.
When a fractional type is mixed with a float or double type, the fractional number is first promoted to float respectively double. The latter type will also be the type of the result.
When an integer type is mixed with the _accum type, the integer is first promoted to _accum. The result of the operation will also be of type _accum.
Because of the limited range of _sfract and _fract, only a few operations make sense when combining an integer with an _sfract or _fract. For that reason, only the following operations are supported in this case:
left | oper | right | result |
fractional | * | integer | fractional |
integer | * | fractional | fractional |
fractional | / | integer | fractional |
integer | / | fractional | integer |
fractional | << | integer | fractional |
fractional | >> | integer | fractional |
fractional: _sfract, _fract integer: char, short, int, long |
Table 3-3: Fractional operations
A number of intrinsic functions are defined for the fractional data types. All these functions except for the first one are not strictly necessary, because the compiler supports these operations directly. The functions are provided so that you can write more portable code.
The following intrinsic function is defined for the case where you are interested in the integer part of the multiplication of a _fract with an integer:
The following intrinsic can be used to convert a 32 bit fractional data type to a 16 bit fractional data type by rounding the value instead of truncating it. When a _sat qualified argument is passed (see next section) then rounding will be done with saturation:
The following intrinsic can be used to convert an _accum value to a _fract:
The following intrinsic can be used to count the number of consecutive bits which have the same value as bit 15 of an _sfract:
These intrinsics can be used to perform a left or right shift on one of the fractional data types. A negative second operand performes a right shift:
When a variable is declared with the _sat type qualifier, all operations on that variable will be performed using saturating arithmetic. When an operation is performed on a plain variable and a _sat variable, the _sat takes precedence, and the operation is done using saturating arithmetic. The type of the result of such an operation also includes the _sat qualifier, so that another operation on the result will also be saturated. In this respect, the behavior of the _sat type qualifier is comparable to the unsigned keyword. You can overrule this behavior by inserting type casts with or without the _sat type qualifier in an expression.
The _sat type qualifier can be used on both fractional and integer types. It is advisable to pay extra attention when using the _sat type qualifier on either (un)signed shorts or (un)signed characters, since these types will in many cases be promoted to signed integers before the actual operation takes place, this in accordance with the principles of usual arithmetic conversions and integral promotions See also: ANSI C Type Conversionsin this chapter.The result will then be stored in a saturated way, if the result should be of a saturated small integer type.
Care should also be taken when combining signed and unsigned types, since no saturation between signed and unsigned is done.
Examples:
_sat int si = 0x7FFFFFFF;
int i = 0x12345;
unsigned int ui = 0xFFFFFFFF;
si + i // a saturated addition is performed, yielding a saturated int
si + ui // a saturated unsigned addition is performed, // yielding a saturated unsigned int
i + ui // a normal unsigned addition is performed, // yielding an unsigned int
Two additional basic types were added to the C compiler to support the packed arithmetic instructions of the TriCore. These types are:
A 32 bit word consisting of 4 bytes.
A 32 bit word consisting of 2 halfwords.
A number of arithmetic operations on packed data types are directly supported by the TriCore instruction set. For example, the following function:
_packb add4 ( _packb a, _packb b ) { return a + b; }
is translated into the following assembly code:
add4: add.b d2,d4,d5 ret16
The following intrinsic functions let you manipulate packed data types in a way that conforms to the standard C syntax. Code using these intrinsics is portable to another platform, provided that you implement the intrinsics with a library or with macro definitions.
Type conversion of a long integer value to a _packb or _packhw data type:
Build a _packb/_packhw value from four/two separate values:
Extract an individual byte/halfword from a _packb/_packhw value:
Insert one byte/halfword into a _packb/_packhw value:
Mix four halfwords or 8 bytes into a double register that is represented by the additional datatype _packw. To access the values in a _packw variable, you can use a nunion data type: typedef double _packw.
The next inrinsic exchanges the values of value and memory, but only those bits that are allowed by mask. Before the _swapmsk instruction is generated, the parameters value and mask are moved into a double register.
Another set of intrinsics is available to give you access to a number of specific TriCore instructions that operate on packed data.
Calculate the absolute value of a _packb/_packhw value:
Calculate the absolute value of a _packhw value, using saturating arithmetic:
Calculate the minimum/maximum value for either signed or unsigned _packb/_packhw values:
To minimize space consumed by alignment padding with unions and structures, elements follow the minimum alignment requirements imposed by the architecture. The TriCore arichitecture supports access to 32-bit integer variables on halfword boundaries.
Because only doubles, circular buffers, _accum or pointers require the full word access, structures that do not contain members of these types are automatically halfword (2-bytes) packed.
Structures and unions that are divisible by 64-bit or contain members that are divisible by 64-bit, are word packed to allow efficient access through LD.D and ST.D instructions. These load and store operations require word aligned structures that are divisible by 64-bit. If necessary, 64-bit divisible structure elements are aligned or padded to make the structure 64-bit accessible.
With the pack 2 pragma the "LD.D/ST.D" structure and union copy optimization can be disabled to ensure halfword structure and union packing when possible. This "limited" halfword packing only supports structures and unions that do not contain double, circular buffer, _accum or pointer type members and that are not qualified with #pragma align to get an alignment larger than 2-byte.
See also Halfword Packed Unions and Structures.
The alignment of data sections and stack may also affect the alignment of the base address of a halfword packed structure. A halfword packed structure can be aligned on a halfword boundary or larger alignment. When located on the stack or at the beginning of a section, the alignment becomes a word, because of the minimum required alignment of data sections and stack objects. A stack or data section can contain any type of object. To avoid wrong word alignment of objects in the section, the section base is also word aligned.
The main use of the _bit type is to define and manipulate bit objects in memory. A _bit type variable can be handled in the same manner as a variable of type int. Only some operations of the _bit type are directly supported by the TriCore instruction set.
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 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:
bit_variable = int_variable;
can be seen as:
bit_variable = int_variable ? 1 : 0;
3. Pointer to _bit is not allowed.
4. The _bit type is allowed as a structure member.
5. A _bit type variable is allowed as a parameter of a function.
6. A _bit type variable is allowed as a return type of a function.
7. A _bit typed expression is allowed as switch expression.
8. The sizeof of a _bit type is 1.
9. Global or static _bit type variable can be initialized.
10. A _bit type variable can be declared absolute using the _atbit attribute. See The _atbit() Attribute for more details.
11. A _bit type variable can be declared volatile.
For the _bit type, the promotion rules are similar to the promotion rules for char, short, int and long.
The type qualifiers _sfrbit16 and _sfrbit32 control the access of (SFR) bit fields. Bit fields qualified with the type qualifiers _srfbit16 are only accessed as word or half-word. Bit fields qualified with the type qualifiers _srfbit32 are only accessed as word.
To support SFR bit field access, SFR data structures are defined containing SFR bit field members. For example the ctri/include/regcpu_name.sfr files contain SFR structure definitions that contain SFR bit field members.
In ANSI C, bit fields must be declared as integer type. However, in implementations compliant with the TriCore Embedded Applications Binary Interface, the alignment requirements they impose as members of unions or structures, are the same as those that would be imposed by the smallest integer-based data types wide enough to hold the fields. Thus:
Byte type bit fields are accessed with byte instructions and half-word type bit fields are accessed with half-word instructions. This works well for SFR's that allow byte access, but some of the TriCore SFR's are restricted to 32-bit or 16-bit access.
When you define an SFR bit field with a size equal or smaller than 8-bit for a 32-bit accessible SFR, this may result in byte or half-word access. Also, when you define an SFR bit field with a size equal or smaller than 16-bit for a 32-bit accessible SFR, this may result in half-word access. But byte and half-word operations are not allowed on 32-bit accessible SFR's. Therefor SFR's that only allow 32-bit access, require type qualification with _sfrbit32 of its bit field members that are equal or smaller than 16-bit. The _sfrbit32 type qualifier instructs the compiler to generate word access only for accessing the SFR bit field members.
#define BCU_Base 0xf0000200 /* BCU block base address */ typedef volatile union { struct { unsigned _sfrbit32 BCUSRPN : 8; /* BCU Service Request Priority Number. */ unsigned _sfrbit32 : 2; unsigned _sfrbit32 BCUTOS : 2; /* BCU Type-of-Service Control. */ unsigned _sfrbit32 BCUSRE : 1; /* BCU Service Request Enable Control. */ unsigned _sfrbit32 BCUSRR : 1; /* BCU Service Request Flag. */ unsigned _sfrbit32 BCUCLRR : 1; /* BCU Request Clear Bit. */ unsigned _sfrbit32 BCUSETR : 1; /* BCU Request Set Bit. */ unsigned _sfrbit32 : 16; } B; int I; } BCU_SRC_type; #define BCU_SRC (*(BCU_SRC_type*)(BCU_Base + 0xfc)) /* BCU Service Request Node */
SFR's that allow only 16-bit or 32-bit access, require type qualification with _sfrbit16 of its bit field members that are equal or smaller than 8-bit. The _sfrbit16 type qualifier instructs the compiler to generate half-word or word access only for accessing the SFR bit field members.
The _sfrbit32 and _sfrbit16 type qualifiers can only be used for int
types. For example, an error is generated for _sfrbit32 char x : 8;
When the _sfrbit32 and _sfrbit16 type qualifiers are used for qualifying other types than a bit field, this is ignored without warning. For example _sfrbit32 int global; is equal to int global;.
Structures or unions that contain a member qualified with _sfrbit32, are zero padded when needed to complete a full word. The structure or union will be word aligned. Structure or unions that contain a member qualified with _sfrbit16, are zero padded when needed to complete a half-word.
The parameter registers D4..D7 and A4..A7 are used to pass the initial function arguments. Up to 4 arithmetic types and 4 pointers can be passed this way. A 64-bit argument is passed in an even/odd data register pair. Parameter registers skipped because of alignment for a 64-bit argument are used by subsequent 32-bit arguments. Any remaining function arguments are passed on the stack. Stack arguments are pushed in reversed order, so that the first one is at the lowest address. On function entry, the first stack parameter is at the address (SP+0).
All function arguments passed on the stack are aligned on a multiple of 4 bytes. As a result, the stack offsets for all types except float are compatible with the stack offsets used by a function declared without a prototype.
Structures up to eight bytes are passed via a data register or data register pair. Larger structures are passed via the stack.
Arithmetic function results of up to 32 bits are returned in the D2 register. 64-bit arithmetic types are returned in the register pair D2/D3. Pointers are returned in A2, and circular pointers are returned in A2/A3.
When the function return type is a structure, it is copied to a "return area" that is allocated by the caller. The address of this area is passed as an implicit first argument in A4.
The C compiler supports two so-called function qualifiers, to change the calling convention of a function: _syscallfunc and _stackparm.
The TriCore C language introduces introduces two new reserved words: _interrupt and _interrupt_fast, 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:
void _interrupt(vector) _isr(void) { ... };
The compiler generates an interrupt service frame for interrupts. The _interrupt function qualifier takes one argument, vector, that defines the interrupt vector number. The difference between a normal function and an interrupt function is that an interrupt function ends with an RFE instruction instead of a RET, and that the lower context is saved and restored with a pair of SVLCX/RSLCX instructions when one of the lower context registers is used in the interrupt handler.
When the interrupt handler function is defined with the _interrupt() qualifier, the compiler will generate an entry for the interrupt vector table. This vector will jump to the interrupt handler function. When the function is defined with the _interrupt_fast() qualifier, the interrupt handler is directly placed in the interrupt vector table, thereby eliminating the jump redirection code. This should only be used when the interrupt handler is very small, as there is only 32 bytes of space available in the vector table.
Suppose, you want an interrupt function for a software interrupt, and the vector number is 0x30:
int c; void _interrupt( 0x30 ) transmit(void) { c = 1; }
With the _trap and _trap_fast function qualifier you can declare a function to serve as a trap service routine. Trap functions cannot return anything and must have a void argument type list, except class 6 SYS trap handlers. For example, in:
void _trap( 7 ) _trapnmi( void ) { int tin;
#pragma asm( d15 ) /* tin number is implicitly */ #pragma endasm( tin=d15) /* passed via register d15 */
switch( tin ) { case 0: .... } }
The compiler generates a trap service frame for traps. The _trap function qualifier takes the class argument which defines the interrupt trap vector number. The TriCore architecture specifies eight general classes for traps. Each class has its own trap handler, accessed through a trap vector of 32 bytes per entry, indexed by the hardware-defined trap class number. Within each class, specific traps are distinguished by a Trap Identification Number (TIN) that is loaded by hardware into register D15 before the first instruction of the trap handler is executed. The trap handler must test and branch on the value in D15 to reach the sub-handler for a specific TIN.
The difference between a normal function and a trap function is that a trap function ends with an RFE instruction instead of a RET, and that the lower context is saved and restored with a pair of SVLCX/RSLCX instructions if one of the lower context registers is used in the interrupt handler. For class 6 SYS traps the lower context is not saved and restored which allows passing and returning parameters.
When the trap handler function is defined with the
_trap() qualifier,
the compiler generates an entry for the trap vector table. This vector
jumps to the trap handler function.
When the function is defined with the _trap_fast() qualifier, the interrupt handler is directly placed in the trap vector table while eliminating the jump redirection code. This should only be used when
the trap handler is very small because there is only 32 bytes of space
available in the vector table. This restriction is not checked by the compiler.
The class 6 SYS trap is raised immediately after execution
of the SYSCALL instruction to initiate a system call. The SYS trap can be called
by functions that are defined with the _syscallfunc qualifier. (See
System Call Function Qualifier).
In contradiction to all other traps the SYS trap can return and pass arguments like _syscallfunc qualified functions. Arguments that are passed via the stack, remain on the stack of the caller because it
is not possible to pass arguments from the user stack to the interrupt stack
on a system call. This restriction, caused by the TriCore's run-time behavior,
can not be checked by the compiler.
_syscallfunc(1) int syscall1( int, int ); _syscallfunc(2) int syscall2( int, int ); int x; void main( void ) { x = syscall1(1,2); x = syscall2(4,3); } int _trap( 6 ) trap6( int a, int b ) { int tin;
#pragma asm( d15 ) #pragma endasm( tin=d15 )
switch( tin ) { case 1: a += b; break; case 2: a -= b; break; default: break; } return a; }
With the _enable_ function qualifier you can declare an interrupt or trap service routine to enable the interrupts immediately at function entry. When entering an interrupt or trap service routine, the TriCore interrupt system globally disables the interrupts. For example:
void _interrupt(1) _enable_ isr( void ) { }
The ENABLE instruction is generated as the first instruction in the interrupt or trap service routine. The ENABLE instruction sets the Interrupt Enable bit (ICR.IE) in the Interrupt Control Register.
The ENABLE instruction can also be generated with the _enable() intrinsic function, but for this intrinsic function it is not guaranteed that it will be the first instruction that is executed at interrupt service entry. See also Intrinsic Functions
With the _bisr_ interrupt/trap function qualifier you can set the current cpu priority number (ICR.CCPN) to a value in the range of 0 to 511 and enable the interrupts for an interrupt or trap service routine. The lower context is also saved at function entry and restored at function exit for _bisr_ qualified interrupt or trap functions. When entering an interrupt or trap service routine the TriCore interrupt system globally disables the interrupts. For example:
#define CCPN 10 void _interrupt(1) _bisr_(CCPN) isr( void ) { }
The BISR instruction is generated as the first instruction in the interrupt or trap service routine. The BISR instruction saves the lower context, sets the Interrupt Enable bit (ICR.IE) in the Interrupt Control Register and sets the CPU priority number (ICR.CCPN) to the specified CCPN value. At function exit the lower context is restored with a RSLCX instruction.
The BISR instruction can also be generated with the _bisr() intrinsic function, but for this intrinsic function it is not guaranteed that it will be the first instruction that is executed at interrupt service entry. See also Intrinsic Functions
The _enable_ function qualifier is superfluous for _bisr_ qualified functions. A warning will be generated and the _enable_ qualifier will be ignored.
The _bisr_ function qualifier is illegal for class 6 traps. The SYS trap, class 6, is raised immediately after execution of the SYSCALL instruction, to initiate a system call. In contradiction to all other traps the SYS trap can return and pass arguments, conform _syscallfunc qualified functions. Returning values requires that the lower context may not be restored, return values are returned in the lower context registers. Therefor BISR can not be used, because it saves the lower context. Instead the _enable_ function qualifier can be used to ENABLE the interrupts and _mtcr() intrinsic function can be used to set the ICR.CCPN value at the beginning of a class 6 trap.
When you call a function declared with the _syscallfunc function qualifier, a SYSCALL instruction is generated rather than a function call. The _syscallfunc function qualifier can only be used at a function declaration, not at a function definition. The _syscallfunc function qualifier takes one constant argument which is used as the operand of the SYSCALL instruction.
Example:
_syscallfunc(42) void trap42(int d4, int d5);
The function call trap42(1, 2) will result in the following code:
mov16 d4,#1 mov16 d5,#2 syscall #42
The function qualifier _stackparm changes the standard calling convention of a function into a convention where all function arguments are passed via the stack, conforming a so called stack model. This qualifier is only needed for situations where you need to use an indirect call to a function for which you do not have a valid prototype.
The compiler sets the least significant bit of the function pointer when you take the address of a function declared with the _stackparm qualifier, so that these function pointers can be identified at run-time. The least significant bit of a function pointer address is ignored by the hardware.
Example:
void plain_func ( int ); void _stackparm stack_func ( int );
void call_indirect ( void (*fp)( int ), int arg ) { typedef _stackparm void (*SFP)( int ); SFP fp_stack; if ( (int) fp & 1 ) { fp_stack = (SFP) fp; fp_stack( arg ); } else { fp( arg ); } }
void main ( void ) { call_indirect( plain_func, 1 ); call_indirect( (void(*)) stack_func, 2 ); }
With the _far function storage qualifier you can qualify a function to be called indirectly instead of PC relative or absolute. The _far function storage qualifier only effects non C function pointer calls; C function pointer calls always use the indirect call operation of the TriCore.
Use the -indirect option to enable code generation for indirect function calling.
The TriCore architecture provides three alternative addressing modes for calls and unconditional jumps: PC relative, absolute and register indirect.
The PC relative and absolute addressing mode only require one word of code. The indirect addressing requires a 2.5 word sequence (extended load address in address register, call indirect address register). Because of the equal instruction size, PC relative addressing and absolute addressing mode can be combined in a generic call or generic unconditional jump.
To allow the compiler to generate efficient calls to external functions whose final segment and offset remain unknown until locate time, a default compilation mode is defined. You can overrule this mode with the _far function storage qualifier or the -indirect compiler option. The model for the default operation is as follows:
1. The compiler issues a callg or jg to the external symbol. These instructions imply generic addressing.
2. The assembler generates a relocatable expression for this generic addressing mode that may be resolved by the locator as a PC relative or absolute call or unconditional jump.
3. If the branch address resolves to a location within +/- 16 Mb range of the callg or jg instruction, the locator resolves it as a PC relative call or unconditional jump.
4. If neither of the above two conditions holds, the locator generates an error telling that the target address is not in range of the generic branch. In this case the function that defines this target address can be qualified with _far to solve this problem.
In case you do not want to check your code on target addresses that might be out of range, you can use the -indirect compiler option to implicitly qualify all functions to be called indirectly. The (less efficient) 2.5 word indirect call sequences will then be generated.
The CODE sections generated for _far qualified functions use a section name farcode.mod_name instead of the default code section name code.mod_name. This naming convention makes it possible to identify farcode and group farcode sections together. Within one source module, all farcode sections are grouped together. With the -R compiler option or with the #pragma section all farcode sections of your application can be grouped together.
See Detailed Description of the Compiler Options for a description of the compiler option -R.Pragmas
See Pragmas for a description of #pragma section.
The C libraries conform to the default compilation model. To call the C library functions indirectly, use the -indirect option or use C function pointer calls. C library functions should not be qualified _far and the run-time library functions, mostly written in assembly, cannot be qualified _far. To use indirect calling, you do not have to compile the C library functions because most library functions are leaf functions or call other library functions. The library functions linked with your application are located in one section per library, PC relative addressing would be sufficient for internal library calls.
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.
const volatile _near int real_time_clock _at(0x1234); /* define the real time clock register; it is read-only (const); read operations must access the real memory location (volatile) */
You can apply the restrict or _restrict type qualifier to pointer types that point to an object and not a function. An object that is accessed through a restrict-qualified pointer has a special association with that pointer. This association requires that all accesses to that object use, directly or indirectly, the value of that particular pointer. The intended use of the restrict qualifier is to promote optimization, and deleting all instances of the qualifier from a program does not change the meaning of the program.
Only the restrict keyword can be enabled or disabled by the -Ar (default) or -AR option respectively.
The restrict keyword is described in detail in the ISO/IEC 9899:1999(E) standard, Programming languages - C.
_sfract * restrict a; _sfract * restrict b;
declares two restrict qualified pointers to an _sfract object. If an object is accessed using one of a or b, and that object is modified anywhere in the program, then it is never accessed using the other one.
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'.
Strings and literals in a C source program, which are not used to initialize an array, have static storage duration. The ANSI X3.159-1989 standard permits string literals to be put in ROM. You can instruct the compiler to allocate strings in ROM (CODE) memory with the -c option. By default, ctri allocates strings in DATA memory. Note that initialized arrays are still located in RAM.
char ramhelp[] = "help"; /* allocation of 5 bytes in RAM and 5 bytes in ROM */
Example of an array in ROM only, initialized with the addresses of strings, also ROM only:
char * message[] = {"hello","alarm","exit"};
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 (ANSI limit 509 characters, actual compiler limit 1500 characters).
The ANSI Standard states that identical string literals need not be distinct, i.e. may share the same memory. Because memory can be very scarce with microcontroller applications, the ctri 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 with pointers, or even worse:
"st ing"[2] = 'r';
Note that identical strings are overlayed!
A special function call convention is used when a function is declared to accept a variable number of arguments. The difference with the normal parameter passing convention is that all variable arguments are always passed on the stack. Default argument promotion takes place for these arguments. The variable arguments can be accessed with the ANSI C macros defined in stdarg.h.
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):
int w,x,y,z; _inline int add( int a, int b ) { return( a + b ); } void main( void ) { w = add( 1, 2 ); z = add( x, y ); }
No specific debug information is generated about 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 the section Inline Assembly in this chapter.
The generated code is:
main: mov16 d15,#3 st.w w,d15 ld.w d15,y ld.w d7,x add16 d15,d7 st.w z,d15 ret16
ctri supports inline assembly using the following pragmas:
#pragma asm Insert assembly text following this pragma.
#pragma asm_noflush As #pragma asm, but without flushing optimizer information.
#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.
When the compiler encounters a pragma asm, it discards some information gathered for optimization. This means that optimizations like copy and constant propagation are not performed across inline assembly code. If you use pragma asm_noflush instead, these optimizations are not disabled.
The pragmas can be followed by a specification of the register interface between the C code and the inline assembly code. After the #pragma asm or asm_noflush, you can allocate fixed registers or scratch registers that can be used in the assembly code. These registers may optionally be initialized with the value of a C variable. The compiler makes sure that C variables that are needed after the inline assembly fragment are not allocated in those registers.
After the #pragma endasm, you can add assignments from a register back to a C variable. All registers mentioned here, should have been allocated in the corresponding #pragma asm.
The syntax of the pragmas is as follows:
The arguments of the pragmas are:
varname name of a C variable,
reg a fixed register or a scratch register.
You can use the following fixed registers:
A scratch register name has the following syntax:
index is a user defined number in the range 0-9. In the inline assembly code, escape sequences consisting of a percent sign followed by a number are replaced by the corresponding scratch register that was allocated by the compiler. Registers are not replaced inside strings or comments. When the scratch register is a register pair (e%0, c%0), you can substitute the low or high part of the register pair by adding an 'l' or 'h' after the '%' sign.
Example:
int doit ( int a, int b ) { #pragma asm ( d0, d1=a, d%2, d%3=b ) MOV d0,d1 MOV %2,%3 #pragma endasm ( a=d0, b=d%2 ) return a + b; }
This example allocates four data registers. The first two, d0 and d1, are real registers, the last two, d%2 and d%3 are scratch registers that are allocated automatically. Register d1 is initialized with the value of variable a, and the scratch register d%3 is initialized with the value of variable b. Afterwards, the compiler makes sure that the value of d0 becomes accessible as the C variable a, and scratch register d%2 becomes available as variable b. This will be done by either allocating the C variable in the register, or when this is not possible, by generating a MOV instruction.
You can use inline assembly code in a function that is declared _inline. This makes it possible to write an _inline function that acts like a C wrapper around your inline assembly code.
When you want to use specific TriCore instructions that have no equivalence in C, you would be forced to write assembly routines to perform these tasks. However, ctri offers a way of handling this in C. ctri 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 at the location of 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 intrinsic _disable yields an indication whether the interrupts where enabled before. This indication can be passed to the _restore intrinsic to restore the state of the interrupt handling.
The following intrinsic functions generate the single instruction that corresponds to the function name. The _syscall and _bisr functions require a constant argument:
These intrinsics can be used to access controll registers with the MFCR and MTCR instructions. The first argument is the constant 16-bit register offset:
A number of specialized instructions that operate on a register value and return a value in another register, can be accessed via the following intrinsic functions:
To determine the minimum or maximum value of a signed or unsigned value with a single instruction, you can use the following intrinsics:
Extracting a bit field from an integer with the EXTR or EXTRU instruction can be done with the _extr() or _extru() intrinsics. Inserting a bit field with the INSERT instruction is done with the _insert() intrinsic. The pos and width arguments must be constant:
Access to the INS and INSN instruction is provided through the _ins() and _insn() intrinsics. The oldbit and newbit arguments specify the bit positions in the original value and new value. These bit positions must be constants:
To multiply two 32-bit number to a 64-bit result, and scale back the result to a 32-bit result, you can use the _mulsc() intrinsic. The third operand specifies the number of bits that should be dropped from the result:
Additional intrinsic functions are available for manipulating fractional numbers, packed data types and circular pointers. Refer to the description of these language extensions for a description of the associated intrinsic functions.
When you invoke ctri with the command line option -builtin, a list of prototypes for all intrinsic functions is displayed.
With the _imaskldmst() intrinsic function you can peform atomic Load-Modify-Store of a bit-field from an integer value with the IMASK and LDMST instruction. This intrinsic writes the number of bits of an integer value at a certain address location in memory with a bitoffset. The number of bits must be a constant value:
With the intrinsic macro _putbit() you can store a single bit atomicly in memory at a specified bit offset. The bit at offset 0 in value is stored at an address location in memory with a bitoffset:
This intrinsic is implemented as a macro definition which uses the _imaskldmst() intrinsic:
With the intrinsic macro _getbit() you can load a single bit from memory at a specified bit offset. A bit value is loaded from an address location in memory with a bitoffset and returned as an unsigned integer value:
This intrinsic is implemented as a macro definition which uses the _extru() intrinsic:
Based upon the 'MISRA guidelines for the application of C language invehicle based software', the TASKING MISRA 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|MISRA C Compiler Options menu. A custom set of applicable MISRA C rules can be easily configured using the same menu. It is also possible to have a project team work with a MISRA C configuration common to the whole project. In this case the MISRA C configuration can be read from an external settings file. This too, is easily selected through the EDE|MISRA C Compiler Options menu. In order to provide proof that installed company MISRA C requirements have in fact been adhered to throughout the entire project, the TriCore Linker/Locator can generate a MISRA C Quality Assurance report. This report lists the various modules in the project with the respective MISRA C settings under which these have been compiled.
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|MISRA 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.
From the command line MISRA C can be enabled by the following compiler option:
-misracn,n,...
where n specifies the rule(s) which must be checked.
In case a MISRA C rule is violated, an error message will be generated
e.g.:
See Appendix B MISRA C for the supported and unsupported MISRA C rules.
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 ctri ignores the erroneous _near storage qualifier (without displaying a warning message).
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.
typedef _near int NEARINT; /* storage type _near: OK */ typedef int _far *PTR; /* logical type _far storage type 'default' */
The TriCore provides a specific addressing mode that can be used to efficiently implement a circular buffer. This type of buffer is often used in DSP applications. The C compiler ctri supports this addressing mode with the pointer qualifier _circ. When you define a pointer variable with this keyword, operations on this pointer will be performed using the circular addressing mode. Because a circular pointer consists of two address registers, it is eight bytes in size.
The following example shows how a circular pointer is defined:
_fract _circ buffer[SIZE]; _fract _circ *circBuffer = buffer;
If you want to initialize a circular pointer with a dynamically allocated buffer at run-time, you should use the intrinsic function _initcirc():
#define N 100 _fract *buf = calloc( N, sizeof(_fract) ); circBuffer = _initcirc( buf, N, 0 );
ctri supports three ways of code generation for a switch statement: a jump chain (linear switch), a jump table or a lookup table.
A jump chain is comparable with an if/else-if/else-if/else construction. A jump table is a table filled with target addresses for each possible switch value. The switch argument is used as an index within this table. A lookup table is a table filled with a value to compare the switch argument with and a target address to jump to. The compiler generates a call to a run-time library function, which performs a binary search lookup.
By default, the compiler will automatically choose the most efficient switch implementation based on code and data size and execution speed. The selection of the switch method can be influenced by the -Os option, which favors execution speed over code size.
It is obvious that, especially for large switch statements, the jump table approach executes faster than the lookup table approach. Also the jump table has a predictable behavior in execution speed. No matter the switch argument, every case is reached in the same execution time. However, when the case labels are distributed far apart, the jump table becomes sparse, wasting code memory. The compiler will not use the jump table method when the waste becomes excessive.
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:
#pragma switch linear /* force jump chain code */ #pragma switch jumptab /* force jump table code */ #pragma switch lookup /* force lookup table code */ #pragma switch auto /* let the compiler decide the switch method used */ #pragma switch restore /* restore previous switch method */
Pragma switch auto is also the default of the compiler.
It is not possible to change the switch method selection
inside a function definition. Therefore, you should only use
#pragma switch at file scope.