3 TRICORE C LANGUAGE

This chapter contains the following sections:

Introduction

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

Intrinsic Functions

Using Assembly in the C Source: __asm

Controlling the Compiler: Pragmas

Predefined Macros

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

Compiler Generated Sections

Switch Statement

Libraries
Overview of Libraries
Printf and Scanf Formatting Routines
Rebuilding Libraries

3.1 Introduction

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.

3.2 Data Types

3.2.1 Fundamental Data Types

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).

3.2.2 Fractional Data Types

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:

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.

Promotion rules

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

3.2.3 Bit Data Type

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:

Promotion Rules

For the __bit type, the promotion rules are similar to the promotion rules for char, short, int, long and long long.

3.2.4 Packed Data Types

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:

results into the following assembly code:

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.

Halfword Packed Unions and Structures

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.

When you place a #pragma pack 0 before a structure or union, its alignment will not be changed:

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.

3.3 Memory Qualifiers

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().

3.3.1 Declare a Data Object in a Special Part of Memory

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.

Examples:

To declare a fast accessible integer in directly addressable memory:

To allocate a pointer in far memory (the compiler will not use absolute addressing mode):

To declare and initialize a string in A0 memory:

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:

You cannot use memory qualifiers in structure declarations:

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:

The usage of the variables is always without a storage specifier:

The generated assembly would be:

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 .

3.3.2 Declare a Data Object at an Absolute Address: __at() and __atbit()

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

The variable myvar is placed at address 0x100.

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:

Declaring a bit variable with __atbit()

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

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

3.4 Data Type Qualifiers

3.4.1 Circular Buffers: __circ

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.

Example: __circ

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:

Indexing in the circular buffer, using an integer index, is treated equally to indexing in a non-circular array.

Example:

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():

3.4.2 Declare an SFR Bit field: __sfrbit16 and __sfrbit32

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.

Example

The next example is part of an SFR file and illustrates the declaration of a special function register using the data type qualifier __sfrbit32:

You can now access the register and bit fields as follows:

Restrictions

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.

3.5 Intrinsic Functions

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.

The resulting assembly code is inlined rather than being called:

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.

3.6 Using Assembly in the C Source: __asm()

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.

General syntax of the __asm keyword

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.

Typical example: multiplying two C variables using assembly

generated code:

%0 corresponds to the first C variable, %1 corresponds to the second and so on. The escape sequence \t generates a tab.

Specifying registers for C variables

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

Loops and conditional jumps

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.

Example 1: no input or output

A simple example without input or output parameters. You can just output any assembly instruction:

Generated code:

Example 2: using output parameters

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.

Generated assembly code:

Example 3: using input and output parameters

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.

Generated assembly code:

Example 4: reserve registers

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).

Generated assembly code:

Example 5: input and output are the same

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.

Generated assembly code:

Example 6: writing your own intrinsic function

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.

Generated assembly code:

As you can see, the generated assembly code for the function __my_mul is inlined rather than called.

Example 7: accessing individual registers in a register pair

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.

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:

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.

3.7 Controlling the Compiler: Pragmas

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.

3.8 Predefined Macros

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

Example

#ifdef __CTC__
/* this part is for the TriCore compiler */
...
#endif

3.9 Functions

3.9.1 Inlining Functions: inline

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.

Example: inline

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:

Example: #pragma inline / #pragma noinline

Instead of the inline qualifier, you can also use #pragma inline and #pragma noinline to inline a function body:

If a function has an inline/__noinline function qualifier, then this qualifier will overrule the current pragma setting.

#pragma smartinline

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.

Combining inline with __asm to create intrinsic functions

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.

3.9.2 Interrupt and Trap Functions

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:

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.

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:

3.9.2.1 Defining an Interrupt Service Routine

Interrupt functions cannot accept arguments and do not return anything:

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.

Example

The next example illustrates the function definition for a function for a software interrupt with vector number 0x30:

3.9.2.2 Defining a Trap Service Routine

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:

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.

3.9.2.3 Defining a Trap Service Routine Class 6: __syscallfunc()

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():

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:

Example

3.9.2.4 Enabling Interrupt Requests: __enable_, __bisr_()

Enabling interrupt service requests

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:

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.

Enabling interrupt service requests and setting CPU priority number

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:

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.

Setting the CPU priority number in a Class 6 trap service 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).

3.9.3 Function Calling Modes: __indirect

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:

With compiler option --indirect you tell the compiler to generate far calls for all functions.

3.9.4 Paramater Passing and the Stack Model: __stackparm

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.

Stack Model: __stackparm

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

3.10 Compiler Generated Sections

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

Rename sections

You can change the default section names with one of the following pragmas:

#pragma section type "string"
#pragma section all "string"

Example:

See also compiler option -R in section Compiler Options in Chapter Tool Options of the Reference Guide.

Influence section definition

The following pragmas also influence the section definition:

#pragma section code_init
#pragma section data_overlay

3.11 Switch Statement

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.

How to overrule the default switch method

You can overrule the compiler chosen switch method with a pragma:

Pragma switch auto is also the default of the compiler.

On the command line you can use compiler option --switch .

3.12 Libraries

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:

3.12.1 Overview of Libraries

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.

3.12.2 Printf and Scanf Formatting Routines

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.

Fixed point format specifiers

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:

3.12.3 Rebuilding Libraries

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:

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:

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:

4. Make a backup copy of the original library and copy the new library to the lib\tc2 directory of the product.


Copyright © 2003 Altium BV