This chapter contains the following sections:
Data Types
Fundamental Data Types
Fractional Data Types
Bit Data Types
Packed Data Types
Memory Qualifiers
Declare a Data Object in a Special Part of Memory
Declare a Data Object at an Absolute Address: __at() and __atbit()
Data Type Qualifiers
Circular Buffers: __circ
Declare an SFR Bit Field: __sfrbit16 and ___sfrbit32
Using Assembly in the C Source: __asm
Controlling the Compiler: Pragmas
Functions
Inlining Functions: inline
Interrupt and Trap Functions
Defining an Interrupt Service Routine
Defining a Trap Service Routine
Defining a Trap Service Routine Class 6: __syscallfunc()
Enabling Interrupt Requests: __enable_, __bisr_()
Function Calling Modes: __indirect
Parameter Passing and the Stack Model: __stackparm
Libraries
Overview of Libraries
Printf and Scanf Formatting Routines
Rebuilding Libraries
The TASKING C cross-compiler (ctc) fully supports the ISO C standard and adds extra possibilities to program the special functions of the TriCore.
In addition to the standard C language, the compiler supports the following:
All non-standard keywords have two leading underscores (__).
In this chapter the TriCore specific characteristics of the C language are described, including the above mentioned extensions.
The TriCore architecture defines the following fundamental data types:
The next table shows the mapping between these fundamental data types and the C language data types.
Type | Keyword |
Size (bit) | Align (bit) | Ranges |
Boolean | _Bool | 8 | 8 | 0 or 1 |
Character | char signed char | 8 | 8 | -27 .. 27-1 |
unsigned char | 8 | 8 | 0 .. 28-1 | |
Integral | short signed short | 16 | 16 | -215 .. 215-1 |
unsigned short | 16 | 16 | 0 .. 216-1 | |
int signed int long signed long | 32 | 16 | -231 .. 231-1 | |
unsigned int unsigned long | 32 | 16 | 0 .. 232-1 | |
enum |
8 16 32 |
8 16 |
-27 .. 27-1 -215 .. 215-1 -231 .. 231-1 | |
long long
signed long long | 64 | 32 | -263 .. -263-1 | |
unsigned long long | 64 | 32 | 0 .. 264-1 | |
Pointer | pointer to data pointer to func | 32 | 32 | 0 .. 232-1 |
Floating Point | float | 32 | 16 |
-3.402e38 .. -1.175e-38 1.175e-38 .. 3.402e38 |
double long double | 64 | 32 |
-1.797e308 .. -2.225e-308 2.225e-308 .. 1.797e308 |
Table 3-1: Data Types
When you use the enum type, the compiler will use the smallest sufficient integer type, unless you use compiler option --integer-enumeration (always use 32-bit integers for enumeration).
See also the TriCore Embedded
Applications Binary Interface (EABI).
The TASKING TriCore C compiler ctc additionally supports the following fractional types:
Type | Keyword |
Size (bit) | Align (bit) | Ranges |
Fract | __sfract | 16 | 16 | [-1, 1> |
__fract | 32 | 32 | [-1, 1> | |
Accum | __laccum | 64 | 64 | [-131072,131072> |
Table 3-2: Fractional Data Types
The __sfract
type has 1 sign bit + 15 mantissa bits
The __fract
type has 1 sign bit + 31 mantissa bits
The __laccum
type has 1 sign bit + 17 integral bits + 46 mantissa bits.
The _accum type is only included for compatibility reasons and is mapped to __laccum.
The TASKING C compiler ctc fully supports fractional data types which allow you to use normal expressions:
__fract f, f1, f2 /* Declaration of fractional variables */ f1 = 0.5; /* Assignment of a fractional constants */ f2 = 0.242; f = f1 * f2 /* Multiplication of two fractionals */
The TriCore instruction set supports most basic operation on fractional types directly. To obtain more portable code, you can use several intrinsic functions that use fractional types. Fractional values are automatically saturated.
Section 3.5,
Intrinsic Functions explains intrinsic functions.
Section 1.5.2, Fractional Arithmetic Support in Chapter TriCore C Language of the Reference Guide lists the intrinsic functions.
For the three fractional types, the promotion rules are similar to the promotion rules for char, short, int, long and long long. This means that for an operation on two different fractional types, the smaller type is promoted to the larger type before the operation is performed.
When you mix a fractional type with a float or double type, the fractional number is first promoted to float respectively double.
When you mix an integer type with the __laccum type, the integer is first promoted to __laccum.
Because of the limited range of __sfract and __fract, only a few operations make sense when combining an integer with an __sfract or __fract. Therefore, the TriCore compiler only supports the following operations for integers combined with fractional types:
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, long long |
Table 3-3: Fractional operations for integers with fractional types
The TASKING TriCore C compiler ctc additionally supports the bit data type:
Type | Keyword |
Size (bit) | Align (bit) | Range |
Bit | __bit | 8 | 8 | 0 or 1 |
Table 3-4: Bit Data Type
The TriCore instruction set supports some operations of the __bit type directly.
The following rules apply to __bit type variables:
bit_variable = int_variable; bit_variable = int_variable ? 1 : 0;
For the __bit type, the promotion rules are similar to the promotion rules for char, short, int, long and long long.
The TASKING TriCore C compiler ctc additionally supports the following packed types:
Type | Keyword |
Size (bit) | Align (bit) | Ranges |
Packed | __packb signed __packb | 32 | 16 | 4x: -27 .. 27-1 |
unsigned __packb | 32 | 16 | 4x: 0 .. 28-1 | |
__packhw signed __packhw | 32 | 16 | 2x: -215 .. 215-1 | |
unsigned __packhw | 32 | 16 | 2x: 0 .. 216-1 |
Table 3-5: Fractional Data Types
A __packb
value consists of four signed or unsigned char values.
A __packhw
value consists of two signed or unsigned short values.
The TriCore instruction set supports a number of arithmetic operations on packed data types directly. For example, the following function:
__packb add4 ( __packb a, __packb b ) { return a + b; }
results into the following assembly code:
add4: add.b d2,d4,d5 ret16
Section 3.5,
Intrinsic Functions explains intrinsic functions.
Section 1.5.3, Packed Data Type Support in Chapter TriCore C Language of the Reference Guide lists the intrinsic functions.
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, __laccum 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 #pragma pack 2 you can disable the LD.D/ST.D structure and union copy optimization 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, __laccum or pointer type members and that are not qualified with #pragma align to get an alignment larger than 2-byte. With #pragma pack 0 you turn off halfword packing again.
#pragma pack 2 typedef struct { unsigned char uc1; unsigned char uc2; unsigned short us1; unsigned short us2; unsigned short us3; } packed_struct; #pragma pack 0
When you place a #pragma pack 0 before a structure or union, its alignment will not be changed:
#pragma pack 0 packed_struct pstruct;
The alignment of data sections and stack can 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.
You can use static memory qualifiers to allocate static objects in a particular part of the addressing space of the processor.
In addition, you can place variables at absolute addresses with the keyword __at(). If you declare an integer at an absolute address, you can declare a single bit of that variable as bit variable with the keyword __atbit().
With a memory qualifier you can declare a variable in a specific part of the addressing space. You can use the following memory qualifiers:
__near The declared data object will be located in the first 16 kB of a 256 MB block. These parts of memory are directly addressable with the absolute addressing mode (see section 4.4.1, Operands and Addressing Modes, in Chapter TriCore Assembly Language).
__far The data object can be located anywhere in the indirect addressable memory region.
If you do not specify __near or __far, the compiler chooses where to place the declared object. With the compiler option -N (maximum size in bytes for data elements that are default located in __near sections) you can specify the size of data objects which the compiler then by default places in near memory.
__a0 The data object is located in a section that is addressable with a sign-extended 16-bit offset from address register A0.
__a1 The data object is located in a section that is addressable with a sign-extended 16-bit offset from address register A1.
__a8 The data object is located in a section that is addressable with a sign-extended 16-bit offset from address register A8.
__a9 The data object is located in a section that is addressable with a sign-extended 16-bit offset from address register A9.
Address registers A0, A1, A8, and A9 are designated as system global registers. They are not part of either context partition and are not saved/restored across calls. They can be protected against write access by user applications.
By convention, A0 and A1 are reserved for compiler use, while A8 and A9 are reserved for OS or application use. A0 is used as a base pointer to the small data section, where global data elements can be accessed using base + offset addressing. A0 is initialized by the execution environment.
A1 is used as a base pointer to the literal data section. The literal data section is a read-only data section intended for holding address constants and program literal values. Like A0, it is initialized by the execution environment.
As noted, A8 and A9 are reserved for OS use, or for application use in cases where the application and OS are tightly coupled.
All these memory qualifiers (__near, __far, __a0, __a1, __a8 and __a9) are related to the object being defined, they influence where the object will be located in memory. They are not part of the type of the object defined. Therefore, you cannot use these qualifiers in typedefs, type casts or for members of a struct or union.
To declare a fast accessible integer in directly addressable memory:
int __near Var_in_near;
To allocate a pointer in far memory (the compiler will not use absolute addressing mode):
__far int *Ptr_in_far;
To declare and initialize a string in A0 memory:
char __a0 string[] = "TriCore";
If you use the __near memory qualifier, the compiler generates faster access code for those (frequently used) variables. Pointers are always 32-bit.
Functions are by default allocated in ROM. In this case you can omit the a memory qualifier. You cannot use memory qualifiers for function return values.
Some examples of using memory qualifiers:
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 */
You cannot use memory qualifiers in structure declarations:
struct S { __near int i; /* put an integer in near memory: Incorrect ! */ __far int *p; /* put an integer pointer in far memory: Incorrect ! */ }
If a library function declares a variable in near memory and you try to redeclare the variable in far memory, the linker issues an error:
extern int _near foo; /* extern int in near memory*/
int __far foo; /* int in far memory */
The usage of the variables is always without a storage specifier:
char __near example; example = 2;
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 16 kB of of each 256 MB block.
With the linker it is possible to control
the location of sections manually. See Chapter 7
Linker
.
Just like you can declare a variable in a specific part of memory, you can also place an object at an absolute address in memory. This may be useful to interface with other programs using fixed memory schemes, or to access special function registers.
With the attribute __at() you can specify an absolute address.
Examples
int myvar __at(0x100);
The variable myvar is placed at address 0x100.
unsigned char Display[80*24] __at( 0x2000 )
The array Display is placed at address 0x2000. In the generated assembly, an absolute section is created. On this position space is reserved for the variable Display.
Restrictions
Take note of the following restrictions if you place a variable at an absolute address:
If you have defined a 32-bits base variable (int, long) you can declare a single bit of that variable as a bit variable with the keyword __atbit(). The syntax is:
name is the name of an integer variable in which the bit is located. offset (range 0-31) is the bit-offset within the variable.
If you have defined an absolute integer variable with the keyword __at(), you can declare a single bit of that variable as an absolute bit variable with __atbit().
Example
int bw __at(0x100); __bit myb __atbit( bw, 3 );
Note that the keyword __bit is used to declare the variable myb as a bit, and that the keyword __atbit() is used to declare that variable at an absolute offset in variable bw.
See also section 3.2.3
, Bit Data Type.
Restrictions
The TriCore core has support for implementing specific DSP tasks, such as finite impulse response (FIR) and infinite impulse response (IIR) filters and fast Fourier transforms (FFTs). For the FIR and IIR filters the TriCore architecture supports the circular addressing mode and for the FFT the bit-reverse addressing mode. The TriCore C compiler supports circular buffers for these DSP tasks. This way, the TriCore C compiler makes hardware features available at C source level instead of at assembly level only.
A circular buffer is a linear (one dimensional) array that you can access by moving a pointer through the data. The pointer can jump from the last location in the array to the first, or vice-versa (the pointer wraps-around). This way the buffer appears to be continuous. The TriCore C compiler supports the __circ keyword (circular addressing mode) for this type of buffer.
__fract __circ circbuffer[10]; __fract __circ *ptr_to_circbuffer = circbuffer;
Here, circbuffer is declared as a circular buffer. The compiler aligns the base address of the buffer on the access width (in this example an int, so 4 bytes). The compiler keeps the buffer size and uses it to control pointer arithmetic of pointers that are assigned to the buffer later.
You can perform operations on circular pointers with the usual C pointer arithmetic with the difference that the pointer will wrap. When you acces the circular buffer with a circular pointer, it wraps at the buffer limits. Circular pointer variables are 64 bits in size.
Example:
while( *Pptr_to_circbuf++ );
Indexing in the circular buffer, using an integer index, is treated equally to indexing in a non-circular array.
Example:
int i = circbuf[3];
The index is not calculated modulo; indexing outside the array boundaries yields undefined results.
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 unsigned short s = sizeof(__fract); __fract *ptr_to_circbuf = calloc( N, s ); circbuf = __initcirc( ptr_to_circbuf, N * s, 0 * s );
With the data type qualifiers __sfrbit16 and __sfrbit32 you can declare bit fields in special function registers.
According to the TriCore Embedded Applications Binary Interface, 'normal' bit fields are accessed as char, short or int. Thus:
If you declare bit fields in special function registers, this behavior is not always desired: some special function registers require 16-bit or 32-bit access. To force 16-bit or 32-bit access, you can use the data type qualifiers __sfrbit16 and __sfrbit32.
For each supported target, a special function register
file (regcpu_name.sfr) is delivered with the TriCore toolchain. In normal circumstances you should not need to declare SFR bit fields.
The next example is part of an SFR file and illustrates the declaration of a special function register using the data type qualifier __sfrbit32:
typedef volatile union { struct { unsigned __sfrbit32 SRPN : 1; /* BCU Service Priority Number */ unsigned int : 2; unsigned __sfrbit32 TOS : 2; /* BCU Type-of-Service Control */ unsigned __sfrbit32 SRE : 1; /* BCU Service Request Enable Control */ unsigned __sfrbit32 SRR : 1; /* BCU SerService Request Flag */ unsigned __sfrbit32 CLRR : 1; /* BCU Request Clear Bit */ unsigned __sfrbit32 SETR : 1; /* BCU Request Set Bit */ unsigned int : 16; } B;
int I; } BCU_SRC_type;
#define BCU_SRC (*(BCU_SRC_type*)(0xF00002FC)) /* BCU Service Request Node */
You can now access the register and bit fields as follows:
#include <regtc10gp.sfr> BCU_SRC.I |= 0xb32a; /* access BCU Service Request Control register as a whole */ BCU_SRC.B.SRE = 0x1; /* access SRE bit field of BCU Service Request Control register */
You can use the __sfrbit32 and __sfrbit16 data type qualifiers only for int types. The compiler issues an error if you use for example __sfrbit32 char x : 8;
When you use the __sfrbit32 and __sfrbit16 data type qualifiers for other types than a bit field, the compiler ignores this without a warning. For example, __sfrbit32 int global; is equal to int global;.
Structure or unions that contain a member qualified with
__sfrbit16,
are zero padded to complete a halfword if necessary. The structure or
union will be halfword aligned.
Structures or unions that contain a member qualified with
__sfrbit32, are zero padded to complete a full word if necessary. The structure
or union will be word aligned.
Some specific TriCore assembly instructions have no equivalence in C. Intrinsic functions give the possibility to use these instructions. Intrinsic functions are predefined functions that are recognized by the compiler. The compiler then generates the most efficient assembly code for these functions.
The compiler always inlines the corresponding assembly instructions in the assembly source rather than calling the function. This avoids unnecessary parameter passing and register saving instructions which are normally necessary when a function is called.
Intrinsic functions produce very efficient assembly code. Though it is possible to inline assembly code by hand, registers are used even more efficient by intrinsic functions. At the same time your C source remains very readable.
You can use intrinsic functions in C as if they were ordinary C (library) functions. All intrinsics begin with a double underscore character. The following example illustrates the use of an intrinsic function and its resulting assembly code.
x = __min( 4,5 );
The resulting assembly code is inlined rather than being called:
mov16 d2,#4 min d2,d2,#5
The intrinsics cover the following subjects:
For extended information about all
available intrinsic functions, refer to section 1.5,
Intrinsic Functions, in Chapter TriCore C Language of the Reference Guide.
With the __asm() keyword you can use assembly instructions in the C source and pass C variables as operands to the assembly code. Be aware that C modules that contain assembly are not portable and harder to compile in other environments.
The compiler does not interpret assembly blocks but passes the assembly code to the assembly source file. Possible errors can only be detected by the assembler.
__asm( "instruction_template" [ : output_param_list [ : input_param_list [ : register_save_list]]] );
instruction_template Assembly instructions that may contain parameters from the input list or output list in the form: %parm_nr
%parm_nr[.regnum] Parameter number in the range 0 .. 9. With the optional .regnum you can access an individual register from a register pair or register quad. For example, with register pair d0/d1, .0 selects register d0.
output_param_list [[ "=[&]constraint_char"(C_expression)],...]
input_param_list [[ "constraint_char"(C_expression)],...]
& Says that an output operand is written to before the inputs are read, so this output must not be the same register as any input.
constraint _char Constraint character:
the type of register to be used for the C_expression.
(see table 3-6)
C_expression Any C expression. For output parameters it must be an lvalue, that is, something that is legal to have on the left side of an assignment.
register_save_list [["register_name"],...]
register_name Name of the register you want to reserve.
int a,b,result; void main( void ) { __asm("mul\t%1,%2,%0" : "=d"(result) : "d"(a), "d"(b) ); }
generated code:
ld.w d15,a ld.w d0,b mul d15,d0,d15 st.w result,d15
%0 corresponds to the first C variable, %1 corresponds to the second and so on. The escape sequence \t generates a tab.
With a constraint character you specify the register type for a parameter. In the example above, the d is used to force the use of data registers for the parameters a, b and result.
You can reserve the registers that are used in the assembly instructions, either in the parameter lists or in the reserved register list (register_save_list). The compiler takes account of these lists, so no unnecessary register saves and restores are placed around the inline assembly instructions.
Constraint character | Type | Operand | Remark |
a | Address register | a0 .. a15 | |
d | Data register | d0 .. d15 | |
e | Data register pair | e0 .. e7 | |
m | Memory | variable | Stack or memory operand |
number | Type of operand it is associated with | same as %number | Indicates that %number and number are the same register. |
Table 3-6: Available input/output operand constraints
The compiler does not detect loops with multiple __asm statements or (conditional) jumps across __asm statements and will generate incorrect code for the registers involved.
If you want to create a loop with __asm, the whole loop must be contained in a single __asm statement. The same counts for (conditional) jumps. As a rule of thumb, all references to a label in an __asm statement must be in that same statement.
A simple example without input or output parameters. You can just output any assembly instruction:
__asm( "nop" );
Generated code:
nop
Assign the result of inline assembly to a variable. With the constraint d a data register is chosen for the parameter; the compiler decides which data register it uses. The %0 in the instruction template is replaced with the name of this data register. Finally, the compiler generates code to assign the result to the output variable.
int result; void main( void ) { __asm( "mov %0,#0xFF" : "=d"(result)); }
Generated assembly code:
mov d15,#0xFF st.w result,d15
Multiply two C variables and assign the result to a third C variable. Data type registers are necessary for the input and output parameters (constraint d, %0 for result, %1 for a and %2 for b in the instruction template). The compiler generates code to move the input expressions into the input registers and to assign the result to the output variable.
int a, b, result; void multiply( void ) { __asm( "mul %1, %2, %0": "=d"(result): "d"(a), "d"(b) ); } void main(void) { multiply(); }
Generated assembly code:
multiply: ld.w d15,a ld.w d0,b mul d15, d0, d15 st.w result,d15
main: jg multiply
If you use registers in the __asm statement, reserve them. Same as Example 3, but now register d0 is a reserved register. You can do this by adding a reserved register list (: "d0") (sometimes referred to as 'clobber list'). As you can see in the generated assembly code, register d0 is not used (the first register used is d1).
int a, b, result; void multiply( void ) { __asm( "mul %1, %2, %0": "=d"(result): "d"(a), "d"(b) : "d0" ); }
Generated assembly code:
ld.w d15,a ld.w d1,b mul d15, d1, d15 st.w result,d15
If the input and output must be the same you must use a number constraint. The following example inverts the value of the input variable ivar and returns this value to ovar. Since the assembly instruction not uses only one register, the return value has to go in the same place as the input value. To indicate that ivar uses the same register as ovar, the constraint '0' is used which indicates that ivar also corresponds with %0.
int ovar; void invert(int ivar) { __asm ("not %0": "=d"(ovar): "0"(ivar) ); } void main(void) { invert(255); }
Generated assembly code:
invert: not d4 st.w ovar,d4
main: mov d4,#255 jg invert
Because you can use any assembly instruction with the __asm keyword, you can use the __asm keyword to create your own intrinsic functions. The essence of an intrinsic function is that it is inlined.
First write a function with assembly in the body using the keyword __asm. We use the multiply routine from Example 3.
Next make sure that the function is inlined rather than being called. You can do this with the function qualifier inline. This qualifier is discussed in more detail in section 3.9.1, Inlining Functions.
int a, b, result; inline void __my_mul( void ) { __asm( "mul %1, %2, %0": "=d"(result): "d"(a), "d"(b) ); } void main(void) { // call to function __my_mul __my_mul(); }
Generated assembly code:
main: ; __my_mul code is inlined here ld.w d15,a ld.w d0,b mul d15, d0, d15 st.w result,d15
As you can see, the generated assembly code for the function __my_mul is inlined rather than called.
You can access the individual registers in a register pair by adding a '.' after the operand specifier in the assembly part, followed by the index in the register pair.
int f1, f2; void foo(double d) { __asm ("ld.w %0, %2.0\n" " ld.w %1, %2.1" : "=&d"(f1), "=d"(f2): "e"(d) ); }
The first ld.w instruction uses index #0 of argument 2 (which is a double placed in a DxDx register) and the second ld.w instruction uses index #1. The input operand is located in register pair d4/d5. The assembly output becomes:
ld.w d15, d4 ld.w d0, d5 st.w f1,d15 st.w f2,d0 ret16
If the index is not a valid index (for example, the register is not a register pair, or the argument has not a register constraint), the '.' is passed into the assembly output. This way you can still use the '.' in assembly instructions.
Pragmas are keywords in the C source that control the behavior of the compiler. Pragmas sometimes overrule compiler options and keywords. In general pragmas give directions to the code generator of the compiler.
For example, you can set a compiler option to specify which optimizations the compiler should perform. With the #pragma optimize flags you can set an optimization level for a specific part of the C source. This overrules the general optimization level that is set in the compiler options dialog (command line option -O).
Some pragmas have an equivalent command line option. This is useful if you want to overrule certain keywords in the C source without the need to change the C source itself.
See section 4.1
, Compiler Options, in Chapter 4,
Tool Options, of the Reference Guide.
The compiler recognizes the following pragmas, other pragmas are ignored.
#pragma align n #pragma align restore
#pragma clear #pragma noclear
#pragma default_a0_size [value]
#pragma default_near_size [value]
#pragma inline #pragma noinline #pragma smartinline
#pragma optimize flags #pragma endoptimize #pragma optimize restore
#pragma pack 2 #pragma pack 0
#pragma section all "section_name" #pragma section section_type "section_name" #pragma section code_init #pragma section data_overlay
#pragma source #pragma nosource
#pragma switch auto #pragma switch jumptab #pragma switch linear #pragma switch lookup #pragma switch restore
In addition to the predefined macros required by the ISO C standard, the TASKING TriCore C compiler supports the predefined macros as defined in Table 3-7. The macros are useful to create conditional C code.
Macro | Description |
__DOUBLE_FP__ | Defined when you do not use compiler option -F (Treat double as float) |
__SINGLE_FP__ | Defined when you use compiler option -F (Treat double as float) |
__FPU__ | Defined when you use compiler option --fpu-present (Use hardware floating point instructions) |
__CTC__ | Identifies the compiler. You can use this symbol to flag parts of the source which must be recognized by the ctc compiler only. It expands to the version number of the compiler. |
__TASKING__ | Identifies the compiler as the TASKING TriCore compiler. It expands to 1. |
__DSPC__ | Indicates conformation to the DSP-C standard. It expands to 1. |
__DSPC_VERSION__ | Expands to the decimal constant 200001L. |
Table 3-7: Predefined macros
#ifdef __CTC__ /* this part is for the TriCore compiler */ ... #endif
You can use the inline keyword to tell the compiler to inline the function body instead of calling the function. Use the __noinline keyword to tell the compiler not to inline the function body.
You must define inline functions in the same source module as in which you call the function, because the compiler only inlines a function in the module that contains the function definition. When you need to call the inline function from several source modules, you must include the definition of the inline function in each module (for example using a header file).
The compiler inserts the function body at the place the function is called. If the function is not called at all, the compiler does not generate code for it.
int w,x,y,z; inline int add( int a, int b ) { int i = 4; return( a + b ); } void main( void ) { w = add( 1, 2 ); z = add( x, y ); }
The function add() is defined before it is called. The compiler inserts (optimized) code for both calls to the add() function. The generated assembly is:
main: mov16 d15,#3 st.w w,d15
ld.w d15,x ld.w d0,y add16 d0,d15 st.w z,d0
Instead of the inline qualifier, you can also use #pragma inline and #pragma noinline to inline a function body:
int w,x,y,z; #pragma inline int add( int a, int b ) { int i=4; return( a + b ); } #pragma noinline void main( void ) { w = add( 1, 2 ); z = add( x, y ); }
If a function has an inline/__noinline function qualifier, then this qualifier will overrule the current pragma setting.
Default, small fuctions that are not too often called, are inlined. This reduces execution speed at the cost of code size (compiler option -Oi ).
With the #pragma noinline / #pragma smartinline you can temporarily disable this optimization.
With the compiler options --inline-max-incr and --inline-max-size you have more control over the function inlining process of the compiler.
See for more information of these options,
section Compiler Options in Chapter Tool Options of the TriCore
Reference Guide.
With the keyword __asm it is possible to use assembly instructions in the body of an inline function. Because the compiler inserts the (assembly) body at the place the function is called, you can create your own intrinsic function.
See section 3.6
, Using Assembly in the C Source, for more information about the __asm keyword.
Example 6 in that section shows how in combination with the inline keyword an intrinsic function is created.
The TriCore C compiler supports a number of function qualifiers and keywords to program interrupt service routines (ISR) or trap handlers. Trap handlers may also be defined by the operating system if your target system uses one.
An interrupt service routine (or: interrupt function,
or: interrupt handler) is called when an interrupt event (or: service request)
occurs. This is always an external event; peripherals or external inputs can generate an
interrupt signals to the CPU to request for service.
Unlike other interrupt systems, each interrupt has a unique interrupt request priority number (IRPN). This number is (0 to 255) is set
as the pending interrupt priority number (PIPN) in the interrupt
control register (ICR) by the interrupt control unit. If multiple interrupts occur
at the same time, the priority number of the request with the hightest priority
is set, so this interrupt is handled.
The TriCore vector table provides an entry for each pending interrupt priority number, not for a specific interrupt source. A request is handled if the priority number is higher then the CPU priority number (CCPN). An interrupt service routine can be interrupted again by another interrupt request with a higher priority. Interrupts with priority number 0 are never handled.
A trap service routine (or: trap function, or: trap handler) is called when a trap event occurs. This is always an event generated within or by the application. For example, a devide by zero or an invalid memory access.
With the following function qualifiers you can declare an interrupt handler or trap handler:
__interrupt() __interrupt_fast() __trap() __trap_fast()
There is one special type of trap function which you can call manually, the system call exception (trap class 6). See section 3.9.2.3 , Defining a Trap Service Routine Class 6.
__syscallfunc()
During the execution of an interrupt service routine or trap service routine, the system blocks the CPU from taking further interrupt requests. With the following keywords you can enable interrupts again, immediately after an interrupt or trap function is called:
__enable_ __bisr_()
Interrupt functions cannot accept arguments and do not return anything:
void __interrupt( vector ) isr( void ) { ... }
The argument vector identifies the entry into the interrupt vector table (0..255). Unlike other interrupt systems, the priority number (PIPN) of the interrupt now being serviced by the CPU identifies the entry into the vector table.
For an extensive description of the
TriCore interrupt system, see the TriCore 1 Unified Processor Core v1.3 Architecture
Manual, Doc v1.3.3 [2002-09, Infineon]
The compiler generates an interrupt service frame for interrupts. 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 you define an interrupt service routine with the __interrupt() qualifier, the compiler generates an entry for the interrupt vector table. This vector jumps to the interrupt handler.
When you define an interrupt service routine with the __interrupt_fast() qualifier, the interrupt handler is directly placed in the interrupt vector table, thereby eliminating the jump code. You should only use this when the interrupt handler is very small, as there is only 32 bytes of space available in the vector table. The compiler does not check this restriction.
The next example illustrates the function definition for a function for a software interrupt with vector number 0x30:
int c;
void __interrupt( 0x30 ) transmit( void ) { c = 1; }
The definition of a trap service routine is similar to the definition of an interrupt service routine. Trap functions cannot accept arguments and do not return anything:
void __trap( class ) tsr( void ) { ... }
The argument class identifies the entry into the trap vector table. TriCore defines eight classes of trap functions. Each class has its own trap handler.
When a trap service routine is called, the d15 register contains the so-called Trap Identification Number (TIN). This number identifies the cause of the trap. In the trap service routine you can test and branch on the value in d15 to reach the sub-handler for a specific TIN.
The next table shows the classes supported by TriCore.
Class | Description |
Class 0 | Reset |
Class 1 | Internal Protection Traps |
Class 2 | Instruction Errors |
Class 3 | Context Management |
Class 4 | System Bus and Peripheral Errors |
Class 5 | Assertion Traps |
Class 6 | System Call |
Class 7 | Non-Maskable Interrupt |
For a complete overview of the trap
system and the meaning of the trap identification numbers, see the TriCore 1 Unified
Processor Core v1.3 Architecture Manual, Doc v1.3.3 [2002-09, Infineon]
Analogous to interrupt service routines, the compiler generates a trap service frame for interrupts.
When you define a trap service routine with the __trap() qualifier, the compiler generates an entry for the interrupt vector table. This vector jumps to the trap handler.
When you define a trap service routine with the __trap_fast() qualifier, the trap handler is directly placed in the trap vector table, thereby eliminating the jump code. You should only use this when the trap handler is very small, as there is only 32 bytes of space available in the vector table. The compiler does not check this restriction.
A special kind of trap service routine is the system call trap. With a system call the trap service routine of class 6 is called. For the system call trap, the trap identification number (TIN) is taken from the immediate constant specified with the function qualifier __syscallfunc():
__syscallfunc(TIN)
The TIN is a value in the range 0 and 255. You can only use __syscallfunc() in the function declaration. A function body is useless, because when you call the function declared with __syscallfunc(), a trap class 6 occurs which calls the corresponding trap service routine.
In case of the other traps, when a trap service routine
is called, the system places a trap identification number in
d15.
Unlike the other traps, a class 6 trap service routine can contain arguments and return a value. 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, cannot be checked by the compiler.
The next example illustrates the definition of a class 6 trap service routine and the corresponding system call:
__syscallfunc(1) int syscall_a( int, int ); __syscallfunc(2) int syscall_b( int, int );
int x;
void main( void ) { x = syscall_a(1,2); // causes a trap class 6 with TIN = 1 x = syscall_b(4,3); // causes a trap class 6 with TIN = 2 }
int __trap( 6 ) trap6( int a, int b ) // trap class 6 handler { int tin; __asm(st.w tin,d15); // put d15 in C variable 'tin'
switch( tin ) { case 1: a += b; break; case 2: a -= b; break; default: break; } return a; }
During the execution of an interrupt service routine or trap service routine, the system blocks the CPU from taking further interrupt requests. You can immediately re-enable the system to accept interrupt requests:
__interrupt(vector) __enable_ isr( void ) __trap(class) __enable_ tsr( void )
The compiler generates an enable instruction as first instruction in the routine. The enable instruction sets the interrupt enable bit (ICR.IE) in the interrupt control register.
You can also generate the enable instruction with the __enable() intrinsic function, but it is not guaranteed that it will be the first instruction in the routine.
The function qualifier __bisr_() also re-enables the system to accept interrupt requests. In addition, the current CPU priority number (CCPN) in the interrupt control register is set:
__interrupt(vector) __bisr_(CCPN) isr( void ) __trap(class) __bisr_(CCPN) tsr( void )
The argument CCPN is a number between 0 and 255. The system accepts all interrupt requests that have a higher pending interrupt priority number (PIPN) than the current CPU priority number. So, if the CPU priority number is set to 0, the system accepts all interrupts. If it is set to 255, no interrupts are accepted.
The compiler generates a bisr instruction as first instruction in the routine. The bisr instruction sets the interrupt enable bit (ICR.IE) and the current CPU priority number (ICR.CCPN) in the interrupt control register.
You can also generate the bisr instruction with the __bisr() intrinsic function, but it is not guaranteed that it will be the first instruction in the routine.
The bisr instruction saves the lower context so passing and returning arguments is not possible. Therefore, you cannot use the function qualifier __bisr_() for class 6 traps.
Instead, you can use the function qualifier __enable_ to set the ICR.IE bit, and the intrinsic function __mtcr( int, int ) to set the ICR.CCPN value at the beginning of a class 6 trap service routine (or use the intrinsic function __mtcr() to set both the ICR.IE bit and the ICR.CCPN value).
Functions are default called with a single word direct call. However, when you link the application and the target address appears to be out of reach (+/- 16 MB from the callg or jg instruction), the linker generates an error. In this case you can use the __indirect keyword to force the less efficient, two and a half word indirect call to the function:
int __indirect foo( void ) { ... }
With compiler option --indirect you tell the compiler to generate far calls for all functions.
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 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.
void plain_func ( int ); void __stackparm stack_func ( int );
void call_indirect ( unsigned int fp, int arg ) { typedef __stackparm void (*SFP)( int ); typedef void (*RFP)( int ); SFP fp_stack; RFP fp_reg; if ( fp & 1 ) { fp_stack = (SFP) fp; fp_stack( arg ); } else { fp_reg = (RFP) fp; fp_reg( arg ); } }
void main ( void ) { call_indirect( (unsinged int) plain_func, 1 ); call_indirect( (unsinged int) stack_func, 2 ); }
The compiler generates code and data in several types of sections. The compiler uses the following section naming convention:
The prefix depends on the type of the section and determines if the section is initialized, constant or uninitialized and which addressing mode is used.
Type | Name prefix | Description |
code | .text | program code |
neardata | .zdata | initialized __near data |
fardata | .data | initialized __far data |
nearrom | .zrodata | constant __near data |
farrom | .rodata | constant __far data |
nearbss | .zbss | uninitialized __near data (cleared) |
farbss | .bss | uninitialized __far data (cleared) |
nearnoclear | .zbss | uninitialized __near data |
farnoclear | .bss | uninitialized __far data |
a0data | .sdata | initialized __a0 data |
a0rom | .srodata | constant __a0 data |
a0bss | .sbss | uninitialized __a0 data (cleared) |
a1rom | .ldata | constant __a1 data |
a8data | .data_a8 | initialized __a8 data |
a8rom | .rodata_a8 | constant __a8 data |
a8bss | .bss_a8 | uninitialized __a8 data (cleared) |
a9data | .data_a9 | initialized __a9 data |
a9rom | .rodata_a9 | constant __a9 data |
a9bss | .bss_a9 | uninitialized __a9 data (cleared) |
Table 3-8: Section types and name prefixes
You can change the default section names with one of the following pragmas:
#pragma section type "string"
#pragma section neardata "where"
#pragma section all "string"
#pragma section all "here"
Example:
#pragma section all "rename_1" // .text.rename_1 // .data.rename_1 #pragma section code "rename_2" // .text.rename_2 // .data.rename_1
See also compiler option -R in section Compiler Options in Chapter Tool
Options of the Reference Guide.
The following pragmas also influence the section definition:
#pragma section code_init
#pragma section data_overlay
ctc 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. A binary search lookup is performed to select the correct target address.
By default, the compiler will automatically choose the most efficient switch implementation based on code and data size and execution speed. You can influence the selection of the switch method with compiler option -t (--tradeoff) , which determines the speed/size tradeoff.
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.
You can overrule the compiler chosen switch method with a pragma:
#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.
On the command line you can use compiler option --switch .
The compiler ctc comes with standard C libraries (ISO/IEC 9899:1999) and header files with the appropriate prototypes for the library functions. The standard C libraries are available in object format and in C or assembly source code.
A number of standard operations within C are too complex to generate inline code for. These operations are implemented as run-time library functions.
The lib directory contains subdirectories with separate libraries for the TriCore 1 and the TriCore 2. Furthermore, protected libraries are available for the TC112 and TC113 functional problems.
The protected library sets provide software bypasses for all TC112 and TC113 supported CPU functional problems. They must be used in conjunction with the appropriate C compiler workarounds for CPU functional problems. For more details refer to Chapter 8 , CPU Functional Problems in the Reference Guide.
The directory structure is:
\ctc\lib\ tc1\ TriCore 1 libraries tc2\ TriCore 2 libraries p\ tc112 Protected libraries for TC112 problems tc113 Protected libraries for TC113 problems
Table 3-9 lists the libraries included in the TriCore (ctc) toolchain.
Library to link | Description |
libc.a |
C library (With full printf/scanf functionality. Some functions require the floating point library. Also includes the startup code.) |
libcs.a |
C library single precision (compiler option -F) (With full printf/scanf functionality. Some functions require the floating point library. Also includes the startup code.) |
libcs_fpu.a | C library single precision with FPU instructions (compiler option -F and --fpu-present) |
libfp.a | Floating point library (non-trapping) |
libfpt.a |
Floating point library (trapping) (Control program option -fptrap) |
libfp_fpu.a |
Floating point library (non-trapping, with FPU instructions) (Compiler option --fpu-present) |
libfpt_fpu.a |
Floating point library (trapping, with FPU instructions) (Control program option -fptrap, compiler option --fpu-present) |
librt.a | Run-time library |
Table 3-9: Overview of libraries
See section 2.1.2
, Library Functions, in Chapter Libraries of the Reference Guide for
an extensive description of all standard C library functions.
The C library functions printf(), fprintf(), vfprintf(), vsprintf(), ... call one single function, _doprint(), that deals with the format string and arguments. This is a rather big function because the number of possibilities of the format specifiers in a format string are large. If you do not need all the possibilities of the format specifiers, you can use a smaller _doprint(). Three different versions exist:
The same applies to all scanf type functions, which call the function _doscan().
If you want to use the MEDIUM or SMALL formatters you must rebuild the C library libc.a as described in section 3.12.3 , Rebuilding Libraries.
The printf and scanf type functions support two additional format specifiers for the conversion of fixed-point types (fractional and accumulator types).
For printf type functions:
%lR | An __laccum argument representing a fixed-point accumulator number is converted to decimal notation in the style [-]ddd.ddd, where the number of digits after the decimal-point character is equal to the precision specification. |
%r | A __fract argument representing a fixed-point fractional number is converted to decimal notation in the style [-] d.ddd, where there is one digit (which is non-zero if the argument is -1.0) before the decimal point character and the number of digits after it is equal to the precision. |
For scanf type functions:
%lR | Matches an optionally signed fixed-point accumulator number. The corresponding argument shall be a pointer to __laccum. |
%r | Matches an optionally signed fixed-point fractional number. The corresponding argument shall be a pointer to __fract. |
Example:
#include <stdio.h> __fract fvalue = 1.0/3; __laccum lacvalue = 1.234; void main(void) { printf("fvalue is: %r\n", fvalue); printf("lacvalue is: %lR\n", lacvalue); }
If you have manually changed one of the standard C library functions, or you want to use the MEDIUM or SMALL printf and scanf routines, you need to recompile the standard C libraries.
The sources of the libraries are present in the lib\src directory. This directory also contains subdirectories with a makefile for each type of library:
lib\src\ p\ tc112\ libc\makefile libcs\makefile libcs_fpu\makefile tc113\ libc\makefile libcs\makefile libcs_fpu\makefile tc1\ libc\makefile libcs\makefile libcs_fpu\makefile tc2\ libc\makefile libcs\makefile libcs_fpu\makefile
To rebuild the libraries, follow the steps below. As an example the instructions are given to rebuild a C library with MEDIUM sized printf routines for the TriCore 2.
First make sure that the bin directory for the TriCore toolchain is included in your PATH environment variable. (See section 1.3.2 , Configuring the Command Line Environment.
1. Make the directory lib\src\tc2\libc the current working directory.
2. Edit the makefile as follows to set the macro MEDIUM:
CC = $(PRODDIR)\bin\ctc -DMEDIUM
See section 8.3
, Make Utility, in Chapter Utilities for an extensive description of the
make utility and makefiles.
3. Assuming the lib\src\tc2\libc directory is still the current working directory, type:
mktc
4. Make a backup copy of the original library and copy the new library to the lib\tc2 directory of the product.