TMS320C28x Optimizing C/C++ Compiler v15.9.0.STS User's Guide
SPRU514 - REVISED SEPTEMBER, 2015

7 Run-Time Environment

This chapter describes the TMS320C28x C/C++ run-time environment. To ensure successful execution of C/C++ programs, it is critical that all run-time code maintain this environment. It is also important to follow the guidelines in this chapter if you write assembly language functions that interface with C/C++ code.

7.1 Memory Model

The C28x compiler treats memory as two linear blocks of program and data memory:

  • Program memory contains executable code, initialization records, and switch tables.
  • Data memory contains external variables, static variables, and the system stack.

Blocks of code or data generated by a C/C++ program are placed into contiguous blocks in the appropriate memory space.

NOTE

The Linker Defines the Memory Map

The linker, not the compiler, defines the memory map and allocates code and data into target memory. The compiler assumes nothing about the types of memory available, about any locations not available for code or data (holes), or about any locations reserved for I/O or control purposes. The compiler produces relocatable code that allows the linker to allocate code and data into the appropriate memory spaces. For example, you can use the linker to allocate global variables into on-chip RAM or to allocate executable code into external ROM. You can allocate each block of code or data individually into memory, but this is not a general practice (an exception to this is memory-mapped I/O, although you can access physical memory locations with C/C++ pointer types).

7.1.1 Sections

The compiler produces relocatable blocks of code and data called sections. The sections are allocated into memory in a variety of ways to conform to a variety of system configurations. For more information about sections and allocating them, see the introductory object file information in the TMS320C28x Assembly Language Tools User's Guide.

There are two basic types of sections:

  • Initialized sections contain data or executable code. Initialized sections are usually, but not always, read-only. The C/C++ compiler creates the following initialized sections:
    • The .binit section contains boot time copy tables. This is a read-only section. For details on BINIT, see the TMS320C28x Assembly Language Tools User's Guide for linker command file information.
    • The .cinit section and the .pinit section contain tables for initializing variables and constants. These are read-only sections. The C28x .cinit record is limited to 16 bits. This limits initialized objects to 64K.
    • The .econst section contains string constants, string literals, switch tables, the declaration and initialization of global and static variables, and data defined with the C/C++ qualifier const (provided the constant is not also defined as volatile). This is a read-only section. String literals are placed in the .econst:.string subsection to enable greater link-time placement control.
    • The .switch section contains tables for switch statements.
    • The .text section contains all the executable code and compiler-generated constants. This section is usually read-only.
  • Uninitialized sections reserve space in memory (usually RAM). A program can use this space at run time to create and store variables. The compiler creates the following uninitialized sections:
    • The .ebss section reserves space for global and static variables defined. At program startup time, the C/C++ boot routine copies data out of the .cinit section (which can be in ROM) and uses it for initializing variables in the .ebss section.
    • The .stack section reserves memory for the C/C++ software stack. This memory is used to pass arguments to functions and to allocate space for local variables.
    • The .esysmem section reserves space for dynamic memory allocation. The reserved space is used by dynamic memory allocation routines, such as malloc, calloc, realloc, or new. If a C/C++ program does not use these functions, the compiler does not create the .esysmem section.

The assembler creates the default sections .text, .ebss, and .data. You can instruct the compiler to create additional sections by using the CODE_SECTION and DATA_SECTION pragmas (see Section 6.9.4 and Section 6.9.6).

The linker takes the individual sections from different object files and combines sections that have the same name. The resulting output sections and the appropriate placement in memory for each section are listed in Table 7-1. You can place these output sections anywhere in the address space as needed to meet system requirements.

Table 7-1 Summary of Sections and Memory Placement

Section Type of Memory Page Section Type of Memory Page
.cinit ROM or RAM 0 .pinit ROM or RAM 0
.data RAM .stack RAM 1
.ebss RAM .switch ROM or RAM 0, 1
.econst ROM or RAM 1 .text ROM or RAM 0
.esysmem RAM 1

You can use the SECTIONS directive in the linker command file to customize the section-allocation process. For more information about allocating sections into memory, see the linker description chapter in the TMS320C28x Assembly Language Tools User's Guide.

7.1.2 C/C++ System Stack

The C/C++ compiler uses a stack to:

  • Allocate local variables
  • Pass arguments to functions
  • Save the processor status
  • Save function return addresses
  • Save temporary results

The run-time stack grows up from low addresses to higher addresses. By default, the stack is allocated in the .stack section. (See the run-time-support boot.asm file.) The compiler uses the hardware stack pointer (SP) to manage this stack.

NOTE

Linking the .stack Section

The .stack section has to be linked into the low 64K of data memory. The SP is a 16-bit register and cannot access addresses beyond 64K.

For frames that exceed 63 words in size (the maximum reach of the SP offset addressing mode), the compiler uses XAR2 as a frame pointer (FP). Each function invocation creates a new frame at the top of the stack, from which local and temporary variables are allocated. The FP points at the beginning of this frame to access memory locations that cannot be referenced directly using the SP.

The stack size is set by the linker. The linker also creates a global symbol, __STACK_SIZE_, and assigns it a value equal to the size of the stack in bytes. The default stack size is 1K words. You can change the size of the stack at link time by using the --stack_size linker option.

NOTE

Stack Overflow

The compiler provides no means to check for stack overflow during compilation or at run time. A stack overflow disrupts the run-time environment, causing your program to fail. Be sure to allow enough space for the stack to grow. You can use the --entry_hook option to add code to the beginning of each function to check for stack overflow; see Section 2.13.

7.1.3 Allocating .econst to Program Memory

If your system configuration does not support allocating an initialized section such as .econst to data memory, then you have to allocate the .econst section to load in program memory and run in data memory. At boot time, copy the .econst section from program to data memory. The following sequence shows how you can perform this task.

  1. Extract boot.asm from the source library:
  2. ar2000 -x rts.src boot.asm
  3. Edit boot.asm and change the CONST_COPY flag to 1:
  4. CONST_COPY .set 1
  5. Assemble boot.asm:
  6. cl2000 boot.asm
  7. Archive the boot routine into the object library:
  8. ar2000 -r rts2800_ml.lib boot.obj

For an .econst section, link with a linker command file that contains the following entries:

SECTIONS { ... .econst : load = PROG PAGE 1, run = DATA PAGE 1 { /* GET RUN ADDRESS */ __econst_run = .; /* MARK LOAD ADDRESS */ *(.ec_mark) /* ALLOCATE .econst */ *(.econst) /* COMPUTE LENGTH */ __econst_length = - .__econst_run; } }

In your linker command file, you can substitute the name PROG with the name of a memory area on page 0 and DATA with the name of a memory area on page 1. The rest of the command file must use the names as above. The code in boot.asm that is enabled when you change CONST_COPY to 1 depends on the linker command file using these names in this manner. To change any of the names, you must edit boot.asm and change the names in the same way.

7.1.4 Dynamic Memory Allocation

The run-time-support library supplied with the C28x compiler contains several functions (such as malloc, calloc, and realloc) that allow you to allocate memory dynamically for variables at run time.

Memory is allocated from a global pool, or heap, that is defined in the .esysmem section. You can set the size of the .esysmem section by using the --heap_size=size option with the linker command. The linker also creates a global symbol, __SYSMEM_SIZE, and assigns it a value equal to the size of the heap in words. The default size is 1K words. For more information on the --heap_size option, see the linker description chapter in the TMS320C28x Assembly Language Tools User's Guide.

If you use any C I/O function, the RTS library allocates an I/O buffer for each file you access. This buffer will be a bit larger than BUFSIZ, which is defined in stdio.h and defaults to 256. Make sure you allocate a heap large enough for these buffers or use setvbuf to change the buffer to a statically-allocated buffer.

Dynamically allocated objects are not addressed directly (they are always accessed with pointers) and the memory pool is in a separate section (.esysmem); therefore, the dynamic memory pool can have a size limited only by the amount of available memory in your system. To conserve space in the .ebss section, you can allocate large arrays from the heap instead of defining them as global or static. For example, instead of a definition such as:

struct big table[100];

Use a pointer and call the malloc function:

struct big *table table = (struct big *)malloc(100*sizeof(struct big));

7.1.5 Initialization of Variables

The C/C++ compiler produces code that is suitable for use as firmware in a ROM-based system. In such a system, the initialization tables in the .cinit section are stored in ROM. At system initialization time, the C/C++ boot routine copies data from these tables (in ROM) to the initialized variables in .ebss (RAM).

In situations where a program is loaded directly from an object file into memory and run, you can avoid having the .cinit section occupy space in memory. A loader can read the initialization tables directly from the object file (instead of from ROM) and perform the initialization directly at load time instead of at run time. You can specify this to the linker by using the --ram_model link option. For more information, see Section 7.9.

7.1.6 Allocating Memory for Static and Global Variables

A unique, contiguous space is allocated for all static variables declared in a C/C++ program. The linker determines the address of the space. The compiler ensures that space for these variables is allocated in multiples of words so that each variable is aligned on a word boundary.

The C/C++ compiler expects global variables to be allocated into data memory. (It reserves space for them in .ebss.) Variables declared in the same module are allocated into a single, contiguous block of memory.

7.1.7 Field/Structure Alignment

When the compiler allocates space for a structure, it allocates as many words as are needed to hold all of the structure's members and to comply with alignment constraints for each member.

All non-field types are aligned on word boundaries. Fields are allocated as many bits as requested. Adjacent fields are packed into adjacent bits of a word, but they do not overlap words. If a field would overlap into the next word, the entire field is placed into the next word.

Fields are packed as they are encountered; the least significant bits of the structure word are filled first.

7.1.8 Character String Constants

In C, a character string constant is used in one of the following ways:

  • To initialize an array of characters. For example:
  • char s[] = "abc";

    When a string is used as an initializer, it is simply treated as an initialized array; each character is a separate initializer. For more information about initialization, see Section 7.9.

  • In an expression. For example:
  • strcpy (s, "abc");

    When a string is used in an expression, the string itself is defined in the .econst section with the .string assembler directive, along with a unique label that points to the string; the terminating 0 byte is included. For example, the following lines define the string abc, and the terminating 0 byte (the label SL5 points to the string):

    .sect ".econst" SL5: .string "abc",0

    String labels have the form SLn, where n is a number assigned by the compiler to make the label unique. The number begins at 0 and is increased by 1 for each string defined. All strings used in a source module are defined at the end of the compiled assembly language module.

    The label SLn represents the address of the string constant. The compiler uses this label to reference the string expression.

    Because strings are stored in the .econst section (possibly in ROM) and shared, it is bad practice for a program to modify a string constant. The following code is an example of incorrect string use:

    const char *a = "abc" a[1] = 'x'; /* Incorrect! undefined behavior */

7.2 Register Conventions

Strict conventions associate specific registers with specific operations in the C/C++ environment. If you plan to interface an assembly language routine to a C/C++ program, you must understand and follow these register conventions.

The register conventions dictate how the compiler uses registers and how values are preserved across function calls. There are two types of register variable registers, save on entry and save on call. The distinction between these two types of registers is the method by which they are preserved across function calls. It is the called function’s responsibility to preserve save-on-entry registers, and the calling function’s responsibility to preserve save-on-call registers if you need to preserve that register’s value.

7.2.1 TMS320C28x Register Use and Preservation

Table 7-2 summarizes how the compiler uses the TMS320C28x registers and shows which registers are defined to be preserved across function calls.

The FPU uses all the C28x registers as well as the registers described in Table 7-3.

Table 7-2 Register Use and Preservation Conventions

Register Usage Save on Entry Save on Call
AL Expressions, argument passing, and returns 16-bit results from functions No Yes
AH Expressions and argument passing No Yes
DP Data page pointer (used to access global variables) No No
PH Multiply expressions and Temp variables No Yes
PL Multiply expressions and Temp variables No Yes
SP Stack pointer (1) (1)
T Multiply and shift expressions No Yes
TL Multiply and shift expressions No Yes
XAR0 Pointers and expressions No Yes
XAR1 Pointers and expressions Yes No
XAR2 Pointers, expressions, and frame pointing (when needed) Yes No
XAR3 Pointers and expressions Yes No
XAR4 Pointers, expressions, argument passing, and returns 32-bit pointer values from functions No Yes
XAR5 Pointers, expressions, and arguments No Yes
XAR6 Pointers and expressions No Yes
XAR7 Pointers, expressions, indirect calls and branches (used to implement pointers to functions and switch statements) No Yes
(1) The SP is preserved by the convention that everything pushed on the stack is popped off before returning.

Table 7-3 FPU Register Use and Preservation Conventions

Register Usage Save on Entry Save on Call
R0H Expressions, argument passing, and returns 32-bit float from functions No Yes
R1H Expressions and argument passing No Yes
R2H Expressions and argument passing No Yes
R3H Expressions and argument passing No Yes
R4H Expressions Yes No
R5H Expressions Yes No
R6H Expressions Yes No
R7H Expressions Yes No

7.2.2 Status Registers

Table 7-4 shows all of the status fields used by the compiler. Presumed value is the value the compiler expects in that field upon entry to, or return from, a function; a dash in this column indicates the compiler does not expect a particular value. The modified column indicates whether code generated by the compiler ever modifies this field.

Table 7-4 Status Register Fields

Field Name Presumed Value Modified
ARP Auxiliary Register Pointer - Yes
C Carry - Yes
N Negative flag - Yes
OVM Overflow mode 0(1) Yes
PAGE0 Direct/stack address mode 0(1) No
PM Product shift mode 0(1) Yes
SPA Stack pointer align bit - Yes
(in interrupts)
SXM Sign extension mode - Yes
TC Test/control flag - Yes
V Overflow flag - Yes
Z Zero flag - Yes
(1) The initialization routine that sets up the C run-time environment sets these fields to the presumed value.

Table 7-5 shows the additional status fields used by the compiler for FPU Targets.

Table 7-5 Floating-Point Status Register (STF(1)) Fields For FPU Targets Only

Field Name Presumed Value Modified
LVF(2)(3) Latched overflow float flag - Yes
LUF(2)(3) Latched underflow float flag - Yes
NF(2) Negative float flag - Yes
ZF(2) Zero float flag - Yes
NI(2) Negative integer flag - Yes
ZI(2) Zero integer flag bit - Yes
TF(2) Test flag bit - Yes
RNDF32 Round F32 mode(4) - Yes
RNDF64 Round F64 mode(4) - Yes
SHDWS Shadow mode status - Yes
(1) Unused STF register bits read 0 and writes are ignored.
(2) Specified flags in the STF register can be exported to the ST0 register by way of the MOVST0 instruction.
(3) The LVF and LUF flag signals can be connected to the PIE to generate an overflow-and-underflow interrupt. This can be a useful debug tool.
(4) If RNDF32 or RNDF64 is 0, mode is round to zero (truncate), otherwise mode is round to nearest (even).

All other status register fields are not used and do not affect code generated by the compiler.

7.3 Function Structure and Calling Conventions

The C/C++ compiler imposes a strict set of rules on function calls. Except for special run-time support functions, any function that calls or is called by a C/C++ function must follow these rules. Failure to adhere to these rules can disrupt the C/C++ environment and cause a program to fail.

Figure 7-1 illustrates a typical function call. In this example, parameters that cannot be placed in registers are passed to the function on the stack. The function then allocates local variables and calls another function. This example shows the allocated local frame and argument block for the called function. Functions that have no local variables and do not require an argument block do not allocate a local frame.

The term argument block refers to the part of the local frame used to pass arguments to other functions. Parameters are passed to a function by moving them into the argument block rather than pushing them on the stack. The local frame and argument block are allocated at the same time.

Figure 7-1 Use of the Stack During a Function Call stack_func_pru514.gif

7.3.1 How a Function Makes a Call

A function (parent function) performs the following tasks when it calls another function (child function).

  1. Any registers whose values are not necessarily preserved by the function being called (registers that are not save-on-entry (SOE) registers), but will be needed after the function returns are saved on the stack.
  2. If the called function returns a structure, the calling function allocates the space for the structure and pass the address of that space to the called function as the first argument.
  3. Arguments passed to the called function are placed in registers and, when necessary, placed on the stack.
  4. Arguments are placed in registers using the following scheme:

    1. If the target is FPU and there are any 32-bit float arguments, the first four float arguments are placed in registers R0H-R3H.
    2. If there are any 64-bit integer arguments (long long), the first is placed in ACC and P (ACC holds the upper 32 bits and P holds the lower 32 bits). All other 64-bit arguments are placed on the stack in reverse order.
    3. If the P register is used for argument passing, then prolog/epilog abstraction is disabled for that function. See Section 3.10 for more information on abstraction.

    4. If there are any 32-bit arguments (longs or floats) the first is placed in the 32-bit ACC (AH/AL). All other 32-bit arguments are placed on the stack in reverse order.
    5. func1(long a, long long b, int c, int* d); stack ACC/P XAR5, XAR4
    6. Pointer arguments are placed in XAR4 and XAR5. All other pointers are placed on the stack.
    7. Remaining 16-bit arguments are placed in the order AL, AH, XAR4, XAR5 if they are available.
  5. Any remaining arguments not placed in registers are pushed on the stack in reverse order. That is, the leftmost argument that is placed on the stack is pushed on the stack last. All 32-bit arguments are aligned to even addresses on the stack.
  6. A structure argument is passed as the address of the structure. The called function must make a local copy.

    For a function declared with an ellipsis, indicating that it is called with varying numbers of arguments, the convention is slightly modified. The last explicitly declared argument is passed on the stack so that its stack address can act as a reference for accessing the undeclared arguments.

  7. The stack pointer (SP) must be even-aligned by the parent function prior to making a call to the child function. This is done by incrementing the stack pointer by 1, if necessary. If needed, the coder should increment the SP before making the call.
  8. Some examples of function calls that show where arguments are placed are listed below:

    func1 (int a, int b. long c) XAR4 XAR5 AH/AL func1 (long a, int b, long c) ; AH/AL XAR4 stack vararg (int a, int b, int c, ...) AL AH stack
  9. The caller calls the function using the LCR instruction. The RPC register value is pushed on the stack. The return address is then stored in the RPC register.
  10. The stack is aligned at function boundary.

7.3.2 How a Called Function Responds

A called function (child function) must perform the following tasks:

  1. If the called function modifies XAR1, XAR2, or XAR3, it must save them, since the calling function assumes that the values of these registers are preserved upon return. If the target is FPU, then in addition to the C28x registers, the called function must save registers R4H, R5H, R6H or R7H, if it modifies any of them. Any other registers may be modified without preserving them.
  2. The called function allocates enough space on the stack for any local variables, temporary storage area, and arguments to functions that this function might call. This allocation occurs once at the beginning of the function by adding a constant to the SP register.
  3. The stack is aligned at function boundary.
  4. If the called function expects a structure argument, it receives a pointer to the structure instead. If writes are made to the structure from within the called function, space for a local copy of the structure must be allocated on the stack and the local structure must be copied from the passes pointer to the structure. If no writes are made to the structure, it can be referenced in the called function indirectly through the pointer argument.
  5. You must be careful to properly declare functions that accept structure arguments, both at the point where they are called (so that the structure argument is passed as an address) and at the point where they are declared (so the function knows to copy the structure to a local copy).

  6. The called function executes the code for the function.
  7. The called function returns a value. It is placed in a register using the following convention:
  8. 16-bit integer value AL
    32-bit integer value ACC
    64-bit integer value ACC/P
    32-bit pointer XAR4
    structure reference XAR6

    If the target is FPU and a 32-bit float value is returned, the called function places this value in R0H.

    If the function returns a structure, the caller allocates space for the structure and passes the address of the return space to the called function in XAR6. To return a structure, the called function copies the structure to the memory block pointed by the extra argument.

    In this way, the caller can be smart about telling the called function where to return the structure. For example, in the statement s= f(x), where S is a structure and F is a function that returns a structure, the caller can actually make the call as f(&s, x). The function f then copies the return structure directly into s, performing the assignment automatically.

    If the caller does not use the return structure value, an address value of 0 can be passed as the first argument. This directs the called function not to copy the return structure.

    You must be careful to properly declare functions that return structures both at the point where they are called (so that the extra argument is passed) and at the point where they are declared (so the function knows to copy the result). Returning 64-bit floating-point values (long double) are returned similarly to structures.

  9. The called function deallocates the frame by subtracting the value that was added to the SP earlier.
  10. The called function restores the values of all registers saved in Step 1.
  11. The called function returns using the LRETR instruction. The PC is set to the value in the RPC register. The previous RPC value is popped from the stack and stored in the RPC register.

7.3.3 Special Case for a Called Function (Large Frames)

If the space that needs to be allocated on the stack (step 2 in the previous section) is larger than 63 words, additional steps and resources are required to ensure that all local nonregister variables can be accessed. Large frames require using a frame pointer register (XAR2) to reference local non-register variables within the frame. Prior to allocating space on the frame, the frame pointer is set up to point at the first argument on the stack that was passed on to the called function. If no incoming arguments are passed on to the stack, the frame pointer points to the return address of the calling function, which is at the top of the stack upon entry to the called function.

Avoid allocating large amounts of local data when possible. For example, do not declare large arrays within functions.

7.3.4 Accessing Arguments and Local Variables

A function accesses its local nonregister variables and its stack arguments indirectly through either the SP or the FP (frame pointer, designated to be XAR2). All local and argument data that can be accessed with the SP use the *−SP [offset] addressing mode since the SP always points one past the top of the stack and the stack grows toward larger addresses.

NOTE

The PAGE0 Mode Bit Must Be Reset

Since the compiler uses the *-SP [offset] addressing mode, the PAGE0 mode bit must be reset (set to 0).

The largest offset available using *-SP [offset] is 63. If an object is too far away from the SP to use this mode of access, the compiler uses the FP (XAR2). Since FP points at the bottom of the frame, accesses made with the FP use either *+FP [offset] or *+FP [AR0/AR1] addressing modes. Since large frames require utilizing XAR2 and possibly an index register, extra code and resources are required to make local accesses.

7.3.5 Allocating the Frame and Accessing 32-Bit Values in Memory

Some TMS320C28x instructions read and write 32 bits of memory at once (MOVL, ADDL, etc.). These instructions require that 32-bit objects be allocated on an even boundary. To ensure that this occurs, the compiler takes these steps:

  1. It initializes the SP to an even boundary.
  2. Because a call instruction adds 2 to the SP, it assumes that the SP is pointing at an even address.
  3. It makes sure that the space allocated on the frame totals an even number, so that the SP points to an even address.
  4. It makes sure that 32-bit objects are allocated to even addresses, relative to the known even address in the SP.
  5. Because interrupts cannot assume that the SP is odd or even, it aligns the SP to an even address.

For more information on how these instructions access memory, see the TMS320C28x Assembly Language Tools User’s Guide.

7.4 Accessing Linker Symbols in C and C++

See the section on "Using Linker Symbols in C/C++ Applications" in the TMS320C28x Assembly Language Tools User's Guide for information about referring to linker symbols in C/C++ code.

7.5 Interfacing C and C++ With Assembly Language

The following are ways to use assembly language with C/C++ code:

  • Use separate modules of assembled code and link them with compiled C/C++ modules (see Section 7.5.1).
  • Use assembly language variables and constants in C/C++ source (see Section 7.5.3).
  • Use inline assembly language embedded directly in the C/C++ source (see Section 7.5.5).
  • Use intrinsics in C/C++ source to directly call an assembly language statement (see Section 7.5.6).

7.5.1 Using Assembly Language Modules With C/C++ Code

Interfacing C/C++ with assembly language functions is straightforward if you follow the calling conventions defined in Section 7.3, and the register conventions defined in Section 7.2. C/C++ code can access variables and call functions defined in assembly language, and assembly code can access C/C++ variables and call C/C++ functions.

Follow these guidelines to interface assembly language and C:

  • All functions, whether they are written in C/C++ or assembly language, must follow the register conventions outlined in Section 7.2.
  • Dedicated registers modified by a function must be preserved. Dedicated registers include:
  • XAR1 R4H (FPU only)
    XAR2 R5H (FPU only)
    XAR3 R6H (FPU only)
    SP R7H (FPU only)

    If the SP is used normally, it does not need to be preserved explicitly. The assembly function is free to use the stack as long as anything that is pushed on the stack is popped back off before the function returns (thus preserving the SP).

    Any register that is not dedicated can be used freely without being preserved.

  • The stack pointer (SP) must be even-aligned by the parent function prior to making a call to the child function. This is done by incrementing the stack pointer by 1, if necessary. If needed, the coder should increment the SP before making the call.
  • The stack is aligned at function boundary.
  • Interrupt routines must save all the registers they use. For more information, see Section 7.6.
  • When you call a C/C++ function from assembly language, load the designated registers with arguments and push the remaining arguments onto the stack as described in Section 7.3.1.
  • When accessing arguments passed in from a C/C++ function, these same conventions apply.

  • Longs and floats are stored in memory with the least significant word at the lower address.
  • Structures are returned as described in Section 7.3.2.
  • No assembly module should use the .cinit section for any purpose other than autoinitialization of global variables. The C/C++ startup routine assumes that the .cinit section consists entirely of initialization tables. Disrupting the tables by putting other information in .cinit can cause unpredictable results.
  • The compiler prepends an underscore ( _ ) to the beginning of all identifiers. In assembly language modules, you must use the prefix _ for all objects that are to be accessible from C/C++. For example, a C/C++ object named x is called _x in assembly language. For identifiers that are to be used only in an assembly language module or modules, any name that does not begin with an underscore can be safely used without conflicting with a C/C++ identifier.
  • The compiler assigns linknames to all external objects. Thus, when you write assembly language code, you must use the same linknames as those assigned by the compiler. See Section 6.11 for details.
  • Any object or function declared in assembly language that is accessed or called from C/C++ must be declared with the .def or .global directive in the assembly language modifier. This declares the symbol as external and allows the linker to resolve references to it.
  • Likewise, to access a C/C++ function or object from assembly language, declare the C/C++ object with the .ref or .global directive in the assembly language module. This creates an undeclared external reference that the linker resolves.

  • Because compiled code runs with the PAGE0 mode bit reset, if you set the PAGE0 bit to 1 in your assembly language function, you must set it back to 0 before returning to compiled code.
  • If you define a structure in assembly and access it in C using extern struct, the structure should be blocked. The compiler assumes that structure definitions are blocked to optimize the DP load. So the definition should honor this assumption. You can block the structure by specifying the blocking flag in the .usect directive. See the TMS320C28x Assembly Language Tools User’s Guide for more information on these directives.

7.5.2 Accessing Assembly Language Functions From C/C++

Functions defined in C++ that will be called from assembly should be defined as extern "C" in the C++ file. Functions defined in assembly that will be called from C++ must be prototyped as extern "C" in C++.

illustrates a C++ function called main, which calls an assembly language function called asmfunc, . The asmfunc function takes its single argument, adds it to the C++ global variable called gvar, and returns the result.

Calling an Assembly Language Function From a C/C++ Program

extern "C"{ extern int asmfunc(int a); /* declare external asm function */ int gvar = 0; /* define global variable */ } void main() { int i = 5; i = asmfunc(i); /* call function normally */ }

Assembly Language Program Called by

.global _gvar .global _asmfunc _asmfunc: MOVZ DP,#_gvar ADDB AL,#5 MOV @_gvar,AL LRETR

In the C++ program in , the extern “C” declaration tells the compiler to use C naming conventions (i.e., no name mangling). When the linker resolves the .global _asmfunc reference, the corresponding definition in the assembly file will match.

The parameter i is passed in register AL.

7.5.3 Accessing Assembly Language Variables From C/C++

It is sometimes useful for a C/C++ program to access variables or constants defined in assembly language. There are several methods that you can use to accomplish this, depending on where and how the item is defined: a variable defined in the .ebss section, a variable not defined in the .ebss section, or a linker symbol.

7.5.3.1 Accessing Assembly Language Global Variables

Accessing variables from the .ebss section or a section named with .usect is straightforward:

  1. Use the .usect directive to define the variable.
  2. Use the .def or .global directive to make the definition external.
  3. Use the appropriate linkname in assembly language.
  4. In C/C++, declare the variable as extern and access it normally.

and show how you can access a variable defined in .ebss.

Assembly Language Variable Program

* Note the use of underscores in the following lines .ebss _var,1 ; Define the variable .global _var ; Declare it as external

C Program to Access Assembly Language From

extern int var; /* External variable */ var = 1; /* Use the variable */
7.5.3.2 Accessing Assembly Language Constants

You can define global constants in assembly language by using the .set directive in combination with either the .def or .global directive, or you can define them in a linker command file using a linker assignment statement. These constants are accessible from C/C++ only with the use of special operators.

For variables defined in C/C++ or assembly language, the symbol table contains the address of the value contained by the variable. When you access an assembly variable by name from C/C++, the compiler gets the value using the address in the symbol table.

For assembly constants, however, the symbol table contains the actual value of the constant. The compiler cannot tell which items in the symbol table are addresses and which are values. If you access an assembly (or linker) constant by name, the compiler tries to use the value in the symbol table as an address to fetch a value. To prevent this behavior, you must use the & (address of) operator to get the value (_symval). In other words, if x is an assembly language constant, its value in C/C++ is &x. See the section on "Using Linker Symbols in C/C++ Applications" in the TMS320C28x Assembly Language Tools User's Guide for more examples that use _symval.

For more about symbols and the symbol table, refer to the section on "Symbols" in the TMS320C28x Assembly Language Tools User's Guide.

You can use casts and #defines to ease the use of these symbols in your program, as in and .

Accessing an Assembly Language Constant From C

extern int table_size; /*external ref */ #define TABLE_SIZE ((int) (&table_size)) . /* use cast to hide address-of */ . . for (I=0; i<TABLE_SIZE; ++I) /* use like normal symbol */

Assembly Language Program for

_table_size .set 10000 ; define the constant .global _table_size ; make it global

Because you are referencing only the symbol's value as stored in the symbol table, the symbol's declared type is unimportant. In , int is used. You can reference linker-defined symbols in a similar manner.

7.5.4 Sharing C/C++ Header Files With Assembly Source

You can use the .cdecls assembler directive to share C headers containing declarations and prototypes between C and assembly code. Any legal C/C++ can be used in a .cdecls block and the C/C++ declarations will cause suitable assembly to be generated automatically, allowing you to reference the C/C++ constructs in assembly code. For more information, see the C/C++ header files chapter in the TMS320C28x Assembly Language Tools User's Guide.

7.5.5 Using Inline Assembly Language

Within a C/C++ program, you can use the asm statement to insert a single line of assembly language into the assembly language file created by the compiler. A series of asm statements places sequential lines of assembly language into the compiler output with no intervening code. For more information, see Section 6.8.

The asm statement is useful for inserting comments in the compiler output. Simply start the assembly code string with a semicolon (;) as shown below:

asm(";*** this is an assembly language comment");

NOTE

Using the asm Statement

Keep the following in mind when using the asm statement:

  • Be extremely careful not to disrupt the C/C++ environment. The compiler does not check or analyze the inserted instructions.
  • Avoid inserting jumps or labels into C/C++ code because they can produce unpredictable results by confusing the register-tracking algorithms that the code generator uses.
  • Do not change the value of a C/C++ variable when using an asm statement. This is because the compiler does not verify such statements. They are inserted as is into the assembly code, and potentially can cause problems if you are not sure of their effect.
  • Do not use the asm statement to insert assembler directives that change the assembly environment.
  • Avoid creating assembly macros in C code and compiling with the --symdebug:dwarf (or -g) option. The C environment’s debug information and the assembly macro expansion are not compatible.

7.5.6 Using Intrinsics to Access Assembly Language Statements

The C28x compiler recognizes a number of intrinsic operators. Intrinsics allow you to express the meaning of certain assembly statements that would otherwise be cumbersome or inexpressible in C/C++. Intrinsics are used like functions; you can use C/C++ variables with these intrinsics, just as you would with any normal function.

The intrinsics are specified with a leading double underscore, and are accessed by calling them as you do a function. For example:

long lvar; int ivar; unsigned int uivar; lvar = __mpyxu(ivar, uivar);

The intrinsics listed in Table 7-6 are included. They correspond to the indicated TMS320C28x assembly language instruction(s). See the TMS320C28x CPU and Instruction Set Reference Guide for more information.

Table 7-6 TMS320C28x C/C++ Compiler Intrinsics

Intrinsic Assembly Instruction(s) Description
int __abs16_sat( int src); SETC OVM

MOV AH,src

ABS ACC

MOVdst, AH

CLRC OVM

Clear the OVM status bit. Load src into AH. Take absolute value of ACC. Store AH into dst. Clear the OVM status bit.
void __add( int *m, int b); ADD *m, b Add the contents of memory location m to b and store the result in m, in an atomic way.
long __addcu( long src1, unsigned int src2); ADDCU ACC, {mem | reg} The contents of src2 and the value of the carry bit are added to ACC. The result is in ACC.
void __addl( long *m, long b); ADDL *m, b Add the contents of memory location m to b and store the result in m, in an atomic way.
void __and( int *m, int b); AND *m, b AND the contents of memory location m to b and store the result in m, in an atomic way.
int &__byte( int *array, unsigned int byte_index); MOVBarray[byte_index].LSB, src

or

MOVBdst, array[byte_index ].LSB

The lowest addressable unit in C28x is 16 bits. Therefore, normally you cannot access 8-bit entities off a memory location. This intrinsic helps access an 8-bit quantity off a memory location, and can be invoked as follows: __byte(array,5) = 10;
b = __byte(array,20);
void __dec( int *m); DEC * m Decrement the contents of memory location m in an atomic way.
unsigned int __disable_interrupts( ); PUSH ST1
SETC INTM, DBGM
POPreg16
Disable interrupts and return the old value of the interrupt vector.
void __dmac( long *src1, long *src2, long &accum1, long &accum2, int shift); SPMn ; the PM value required for shift
MOVLACC,accum1
MOVL P, accum2
MOVL XARx,src1
MOVL XAR7,src2
DMAC ACC:P, *XARx++, *XAR7++
Set the required PM value for shift.
Move accum1 and accum2 into ACC and P.
Move the addresses src1 and src2 into XARx and XAR7.
ACC = ACC + (src1[i+1] * src2[i+1]) << PM
P = P + (src1[i] * src2[i]) << PM
See Section 3.11.3 for more information.
void __eallow( void ); EALLOW Permits the CPU to write freely to protected registers.
void __edis( void ); EDIS Prevents the CPU from writing freely to protected registers after EALLOW is used.
unsigned int __enable_interrupts( ); PUSH ST1
CLRC INTM, DBGM
POPreg16
Enable interrupts and return the old value of the interrupt vector.
int __flip16(int src); Reverses order of bits in int src.
long __flip32(long src); Reverses order of bits in long src.
long long __flip64(long long src); Reverses order of bits in long long src.
void __inc( int *m); INC * m Increment the contents of memory location m in an atomic way.
long=__IQ( long double A, int N); Convert the long double A into the correct IQN value returned as a long type. If both arguments are constants the compiler converts the arguments to the IQ value during compile time. Otherwise a call to the RTS routine, __IQ, is made. This intrinsic cannot be used to initialize global variables to the .cinit section.
long dst =__IQmpy( long A, long B, int N); Perform optimized multiplication using the C28 IQmath library. The dst becomes ACC or P, A becomes XT:
If   N == 0: IMPYL {ACC|P}, XT, B The dst is ACC or P. If dst is ACC, the instruction takes 2 cycles. If dst is P, the instruction takes 1 cycle.
If    0 < N < 16: IMPYL P, XT, B
QMPYL ACC, XT, B
ASR64 ACC:P, # N
If    15 < N < 32: IMPYL P, XT, B
QMPYL ACC, XT, B
LSL64 ACC:P, #(32- N )
If    N == 32: QMPYL {ACC|P}, XT, B
If    N is a variable: IMPYL P, XT, B
QMPYL ACC, XT, B
MOV T,N
LSR64 ACC:P, T
long dst= __IQsat( long A, long max, long min); The dst becomes ACC. Different code is generated based on the value of max and/or min.
If    max and min are 22-bit unsigned constants: MOVL ACC, A
MOVL XARn, #22bits
MINL ACC, P
MOVL XARn, #22bits
MAXL ACC, P
If    max and min are other constants: MOVL ACC, A
MOV PL, #max lower 16 bits
MOV PH, #max upper 16 bits
MINL ACC, P
MOV PL, #min lower 16 bits
MOV PH, #min upper 16 bits
MAXL ACC, P
If    max and/or min are variables: MOVL ACC, A
MINL ACC, max
MAXL ACC, min
long dst= __IQxmpy(long A, long B, int N); Perform optimized multiplication by a power of 2 using the C28 IQmath library. The dst becomes ACC or P; A becomes XT. Code is generated based on the value of N.
If    N == 0: IMPYL ACC/P, XT, B The dst is in ACC or P.
If    0 < N < 17: IMPYL P, XT, B
QMPYL ACC, XT, B
LSL64 ACC:P, #N
The dst is in ACC.
If    0 > N > -17: QMPYL ACC, XT, B
SETC SXM
SFR ACC, #abs(N)
The dst is in ACC.
If    16 < N < 32: IMPYL P, XT, B
QMPYL ACC, XT, B
ASR64 ACC:P, #N
The dst is in P.
If    N == 32: IMPYL P, XT, B The dst is in P.
If    -16 > N > -33 QMPYL ACC, XT, B
SETC SXM SRF ACC, #16
SRF ACC, #abs( N )−16
The dst is in ACC.
If    32 < N < 49: IMPYL ACC, XT, B
LSL ACC, #N -32
The dst is in ACC.
If    -32 > N > -49: QMPYL ACC, XT, B
SETC SXM SFR ACC, #16
SFR ACC, #16
The dst is in ACC.
If    48 < N < 65: IMPYL ACC, XT, B
LSL64 ACC:P, #16
LSL64 ACC:P, #N−48
The dst is in ACC.
If    -48 > N > -65: QMPYL ACC, XT, B
SETC SXM SFR ACC, #16
SFR ACC, #16
The dst is in ACC.
long long __llmax(long long dst, long long src); MAXL ACC, src.hi32
MAXCUL P, src.lo32
If src > dst, copy src to dst.
long long __llmin(long long dst, long long src); MINL ACC, src.hi32
MINCUL P, src.lo32
If src < dst, copy src to dst
long __lmax(long dst, long src); MAXL ACC, src If src > dst, copy src to dst.
long __lmin(long dst, long src); MINL ACC, src If src < dst, copy src to dst
int __max(int dst, int src); MAX dst , src If src > dst, copy src to dst
int __min(int dst, int src); MIN dst , src If src < dst, copy src to dst
int __mov_byte( int *src, unsigned int n); MOVB AX.LSB,*+XARx[ n ]

or

MOVZ AR0/AR1, @n

MOVB AX.LSB,*XARx[ {AR0|AR1} ]

Return the 8-bit nth element of a byte table pointed to by src.

This intrinsic is provided for backward compatibility. The intrinsic __byte is preferred as it returns a reference. Nothing can be done with __mov_byte() that cannot be done with __byte().

long __mpy( int src1, int src2); MPY ACC,src1, #src2 Move src1 to the T register. Multiply T by a 16-bit immediate (src2). The result is in ACC.
long __mpyb( int src1, uint src2); MPYB {ACC | P}, T, #src2 Multiply src1 (the T register) by an unsigned 8-bit immediate (src2). The result is in ACC or P.
long __mpy_mov_t( int src1, int src2, int *dst2); MPY ACC, T,src2
MOV @dst2, T
Multiply src1 (the T register) by src2. The result is in ACC. Move src1 to *dst2.
unsigned long __mpyu(unit src2, unit srt2); MPYU {ACC | P}, T,src2 Multiply src1 (the T register) by src2. Both operands are treated as unsigned 16-bit numbers. The result is in ACC or P.
long __mpyxu( int src1, uint src2); MPYXU ACC, T, {mem|reg} The T register is loaded with src1. The src2 is referenced by memory or loaded into a register. The result is in ACC.
long dst= __norm32(long src, int *shift); CSB ACC
LSLL ACC, T
MOV @shift, T
Normalize src into dst and update *shift with the number of bits shifted.
long long dst= __norm64(long long src,
    int *shift);
CSB ACC
LSL64 ACC:P, T
MOV @shift, T
CSB ACC
LSL64 ACC:P, T
MOV TMP16, AH
MOV AH, T
ADD shift, AH
MOV AH, TMP16
Normalize 64-bit src into dst and update *shift with the number of bits shifted.
void __or( int *m, int b); OR *m, b OR the contents of memory location m to b and store the result in m, in an atomic way.
long __qmpy32( long src32a, long src32b, int q); CLRC OVM SPM − 1
MOV T, src32a + 1
MPYXU P, T, src32b + 0
MOVP T, src32b + 1
MPYXU P, T, src32a + 0
MPYA P, T, src32a + 1
Extended precision DSP Q math. Different code is generated based on the value of q.
If   q = 31,30: SPMq − 30
SFR ACC, #45 − q
ADDL ACC, P
If   q = 29: SFR ACC, #16
ADDL ACC, P
If   q = 28 through 24: SPM q - 30
SFR ACC, #16
SFR ACC, #29 - q
ADDL ACC, P
If   q = 23 through 13: SFR ACC, #16
ADDL ACC, P
SFR ACC, #29 − q
If   q = 12 through 0: SFR ACC, #16
ADDL ACC, P
SFR ACC, #16
SFR ACC, #13 − q
long __qmpy32by16(long src32, int src16, int q); CLRC OVM
MOV T, src16 + 0
MPYXU P, T, src32 + 0
MPY P, T, src32 + 1
Extended precision DSP Q math. Different code is generated based on the value of q.
If    q = 31, 30: SPM q − 30
SFR ACC, #46 − q
ADDL ACC, P
If    q = 29 through 14: SPM 0
SFR ACC, #16
ADDL ACC, P
SFR ACC, #30 − q
If    q = 13 through 0: SPM 0
SFR ACC, #16
ADDL ACC, P
SFR ACC, #16
SFR ACC, #14 − q
void __restore_interrupts(unsigned int val); PUSH val
POP ST1
Restore interrupts and set the interrupt vector to value val.
long __rol( long src); ROL ACC Rotate ACC left.
long __ror( long src); ROR ACC Rotate ACC right.
void *result= __rpt_mov_imm(void *dst, int src,
       int count);
MOV result, dst
MOV ARx,dst
RPT #count
|| MOV *XARx++, #src
Move the dst register to the result register. Move the dst register to a temp (ARx) register. Copy the immediate src to the temp register count + 1 times.

The src must be a 16-bit immediate. The count can be an immediate from 0 to 255 or a variable.

int __rpt_norm_inc( long src, int dst, int count); MOV ARx, dst
RPT #count
|| NORM ACC, ARx++
Repeat the normalize accumulator value count + 1 times.

The count can be an immediate from 0 to 255 or a variable.

int __rpt_norm_dec(long src, int dst, int count); MOV ARx, dst
RPT #count
|| NORM ACC, ARx--
Repeat the normalize accumulator value count + 1 times.

The count can be an immediate from 0 to 255 or a variable.

long __rpt_rol(long src, int count); RPT #count
|| ROL ACC
Repeat the rotate accumulator left count + 1 times. The result is in ACC.

The count can be an immediate from 0 to 255 or a variable.

long __rpt_ror(long src, int count); RPT #count
|| ROR ACC
Repeat the rotate accumulator right count + 1 times. The result is in ACC.

The count can be an immediate from 0 to 255 or a variable.

long __rpt_subcu(long dst, int src, int count); RPT count
|| SUBCU ACC, src
The src operand is referenced from memory or loaded into a register and used as an operand to the SUBCU instruction. The result is in ACC.

The count can be an immediate from 0 to 255 or a variable. The instruction repeats count + 1 times.

long __sat( long src); SAT ACC Load ACC with 32-bit src. The result is in ACC.
long __sat32( long src, long limit); SETC OVM
ADDL ACC, {mem|P}
SUBL ACC, {mem|P}
SUBL ACC, {mem|P}
ADDL ACC, {mem|P}
CLRC OVM
Saturate a 32-bit value to a 32-bit mask. Load ACC with src. Limit value is either referenced from memory or loaded into the P register. The result is in ACC.
long __sathigh16(long src, int limit); SETC OVM
ADDL ACC, {mem|P}<<16
SUBL ACC, {mem|P}<<16
SUBL ACC, {mem|P}<<16
ADDL ACC, {mem|P}<<16
CLRC OVM
SFR ACC, rshift
Saturate a 32-bit value to 16-bits high. Load ACC with src. The limit value is either referenced from memory or loaded into register. The result is in ACC. The result can be right shifted and stored into an int. For example: ivar=__sathigh16(lvar, mask)>>6;
long __satlow16( long src); SETC OVM
MOV T, #0xFFFF
CLR SXM ; if necessary
ADD ACC, T <<15
SUB ACC, T <<15
SUB ACC, T <<15
ADD ACC, T <<15
CLRC OVM
Saturate a 32-bit value to 16-bits low. Load ACC with src. Load T register with #0xFFFF. The result is in ACC.
long __sbbu( long src1, uint src2); SBBU ACC, src2 Subtract src2 + logical inverse of C from ACC (src1). The result is in ACC.
void __sub( int *m, int b); SUB *m, b Subtract b from the contents of memory location m and store the result in m, in an atomic way.
long __subcu( long src1, int src2); SUBCU ACC, src2 Subtract src2 shifted left 15 from ACC (src1). The result is in ACC.
void __subl( long *m, long b); SUBL *m, b Subtract b from the contents of memory location m and store the result in m, in an atomic way.
void __subr( int *m, int b); SUBR *m, b Subtract the contents of memory location m from b and store the result in m, in an atomic way.
void __subrl( long *m, long b); SUBRL *m, b Subtract the contents of memory location m from b and store the result in m, in an atomic way.
if (__tbit( int src, int bit) ); TBIT src, #bit SET TC status bit if specified bit of src is 1.
void __xor( int *m, int b); XOR *m, b XOR the contents of memory location m to b and store the result in m, in an atomic way.

Table 7-7 C/C++ Compiler Intrinsics for FPU

Intrinsic Assembly Instruction(s) Description
double __einvf32( double x); EINVF32x Compute and return 1/x to about 8 bits of precision.
double __eisqrtf32( double x); EISQRTF32x Find the square root of 1/x to about 8 bits of precision.
void __f32_max_idx( double &dst, double src,
    double &idx_dst, double idx_src);
   MAXF32dst, src
|| MOV32idx_dst, idx_src
If src>dst, copy src to dst, and copy idx_src to idx_dst.
void __f32_min_idx( double &dst, double src,
    double &idx_dst, double idx_src);
   MINF32dst, src
|| MOV32idx_dst, idx_src
If src<dst, copy src to dst, and copy idx_src to idx_dst.
int __f32toi16r(double src); F32TOI16R dst , src Convert double to int and round.
unsigned int __f32toui16r(double src); F32TOUI16R dst , src Convert double to unsigned int and round.
float __fmax( float x, float y); MAXF32 dst , src If src>dst, copy src to dst
float __fmin( float x, float y); MINF32 dst , src If src<dst, copy src to dst
double __fracf32(double src); FRACF32 dst , src Return the fractional portion of src.
double __fsat(double val, double max, double min); MAXF32 dst , src2
MINF32 dst , src1
Return val if min < val < max. Else return min if value < min. Else return max if val > max.
void __swapf( double &a, double &b); swapfa, b Swap the contents of a and b.
void __swapff( float &a, float &b); swapffa, b Swap the contents of a and b.

The following intrinsics perform hardware instructions using the Trigonometric Math Unit (TMU). These intrinsics are enabled if the --tmu_support=tmu0 compiler option is used.

Table 7-8 C/C++ Compiler Intrinsics for TMU

Intrinsic Assembly Instruction(s) Description
double __divf32( double num , double denom); DIVF32 dst , num , denom Return num divided by denom using the TMU hardware instruction for floating point division.
double __sqrt( double src); SQRTF32 dst , src Return the square root of src.
double __sin( double src); SINF32 dst , src Return the sine of src radians, where src is provided in radians.
double __cos( double src); COSF32 dst , src Return the cosine of src radians, where src is provided in radians.
double __atan( double src); ATANF32 dst , src Return the principal value of the arc tangent of src radians.
double __atan2( double y , double x); QUADF32 quadrant , ratio , y , x
ATANPUF32 atanpu , ratio
ADDF32 atan2pu , atanpu
MPY2PIF32 atan2 , atan2pu
Return the principal value of the arc tangent plus the quadrant for x, y. The value is returned in radians.
double __mpy2pif32( double src); MPY2PIF32 dst , src Return the result of multiplying src by 2pi. This converts a per unit value to radians. Per unit values are commonly used in control applications to represent normalized radians.
double __div2pif32( double src); DIV2PIF32 dst , src Return the result of multiplying src by 1/2pi (effectively dividing by 2pi). This converts a value in radians to a per unit value.
double __sinpuf32( double src); SINPUF32 dst , src Return the sine of src in radians, where src is provided as a per unit value.
double __cospuf32( double src); COSPUF32 dst , src Return the cosine of src in radians, where src is provided as a per unit value.
double __atanpuf32( double src); ATANPUF32 dst , src Return the principal value of the arc tangent of src, which is provided as a per unit value.
double __atan2puf32( double x, double y); QUADF32 quadrant , ratio , y , x
ATANPUF32 atanpu , ratio
ADDF32 dst , atanpu
Return the principal value of the arc tangent plus the quadrant value for y, x. The value is returned as a per unit value.
double __quadf32( double ratio, double y, double x); QUADF32 quadrant , ratio , y , x Return the quadrant value (0.0, +/-0.25, or +/-0.5) and the ratio of x and y, which are provided as per unit values.

7.6 Interrupt Handling

As long as you follow the guidelines in this section, you can interrupt and return to C/C++ code without disrupting the C/C++ environment. When the C/C++ environment is initialized, the startup routine does not enable or disable interrupts. If the system is initialized by way of a hardware reset, interrupts are disabled. If your system uses interrupts, you must handle any required enabling or masking of interrupts. Such operations have no effect on the C/C++ environment and are easily incorporated with asm statements or calling an assembly language function.

7.6.1 General Points About Interrupts

An interrupt routine can perform any task performed by any other function, including accessing global variables, allocating local variables, and calling other functions.

When you write interrupt routines, keep the following points in mind:

  • An interrupt handling routine cannot have arguments. If any are declared, they are ignored.
  • An interrupt handling routine can be called by normal C/C++ code, but it is inefficient to do this because all the registers are saved.
  • An interrupt handling routine can handle a single interrupt or multiple interrupts. The compiler does not generate code that is specific to a certain interrupt, except for c_int00, which is the system reset interrupt. When you enter this routine, you cannot assume that the run-time stack is set up; therefore, you cannot allocate local variables, and you cannot save any information on the run-time stack.
  • To associate an interrupt routine with an interrupt, the address of the interrupt function must be placed in the appropriate interrupt vector. You can use the assembler and linker to do this by creating a simple table of interrupt addresses using the .sect assembler directive.
  • In assembly language, remember to precede the symbol name with an underscore. For example, refer to c_int00 as _c_int00.

7.6.2 Using C/C++ Interrupt Routines

If a C/C++ interrupt routine does not call any other functions, only those registers that the interrupt handler uses are saved and restored. However, if a C/C++ interrupt routine does call other functions, these functions can modify unknown registers that the interrupt handler does not use. For this reason, the compiler saves all the save-on-call registers if any other functions are called.

A C/C++ interrupt routine is like any other C/C++ function in that it can have local variables and register variables; however, it should be declared with no arguments and should return void. Interrupt handling functions should not be called directly.

Interrupts can be handled directly with C/C++ functions by using the INTERRUPT pragma or the __interrupt keyword. For information about the INTERRUPT pragma, see Section 6.9.13. For information about the __interrupt keyword, see Section 6.5.3.

7.7 Integer Expression Analysis

This section describes some special considerations to keep in mind when evaluating integer expressions.

7.7.1 Operations Evaluated With Run-Time-Support Calls

The TMS320C28x does not directly support some C/C++ integer operations. Evaluating these operations is done with calls to run-time-support routines. These routines are hard-coded in assembly language. They are members of the object and source run-time-support libraries (rts2800_ml.lib and rtssrc.zip) in the toolset.

The conventions for calling these routines are modeled on the standard C/C++ calling conventions.

Operation Type Operations Evaluated With Run-Time-Support Calls
16-bit int Divide (signed)
Modulus
32-bit long Divide (signed)
Modulus
64-bit long long Multiply(1)
Divide
Bitwise AND, OR, and XOR
Compare
(1) 64-bit long long multiplies are inlined if -mf=5 is specified.

7.7.2 C/C++ Code Access to the Upper 16 Bits of 16-Bit Multiply

The following methods provide access to the upper 16 bits of a 16-bit multiply in C/C++ language:

  • Signed-results method:
  • int m1, m2; int result; result = ((long) m1 * (long) m2) >> 16;
  • Unsigned-results method:
  • unsigned m1, m2; unsigned result; result = ((unsigned long) m1 * (unsigned long) m2) >> 16;

NOTE

Danger of Complicated Expressions

The compiler must recognize the structure of the expression for it to return the expected results. Avoid complicated expressions such as the following example:

((long)((unsigned)((a*b)+c)<5)*(long)(z*sin(w)>6))>>16

7.8 Floating-Point Expression Analysis

The C28x C/C++ compiler represents float and double floating-point values as IEEE single-precision numbers. Long double floating-point values are represented as IEEE double-precision numbers. Single-precision floating-point numbers are represented as 32-bit values and double-precision floating-point numbers are represented as 64-bit values.

The run-time-support library, rts2800_ml.lib, contains a set of floating-point math functions that support:

  • Addition, subtraction, multiplication, and division
  • Comparisons (>, <, >=, <=, ==, !=)
  • Conversions from integer or long to floating-point and floating-point to integer or long, both signed and unsigned
  • Standard error handling

The conventions for calling these routines are the same as the conventions used to call the integer operation routines. Conversions are unary operations.

7.9 System Initialization

Before you can run a C/C++ program, you must create the C/C++ run-time environment. The C/C++ boot routine performs this task using a function called c_int00 (or _c_int00). The run-time-support source library, rts.src, contains the source to this routine in a module named boot.c (or boot.asm).

To begin running the system, the c_int00 function can be called by reset hardware. You must link the c_int00 function with the other object files. This occurs automatically when you use the --rom_model or --ram_model link option and include a standard run-time-support library as one of the linker input files.

When C/C++ programs are linked, the linker sets the entry point value in the executable output file to the symbol c_int00.

The c_int00 function performs the following tasks to initialize the environment:

  1. Defines a section called .stack for the system stack and sets up the initial stack pointers
  2. Initializes global variables by copying the data from the initialization tables to the storage allocated for the variables in the .ebss section. If you are initializing variables at load time (--ram_model option), a loader performs this step before the program runs (it is not performed by the boot routine). For more information, see Section 7.9.2.
  3. Executes the global constructors found in the global constructors table. For more information, see Section 7.9.2.4.
  4. Calls the main() function to run the C/C++ program

You can replace or modify the boot routine to meet your system requirements. However, the boot routine must perform the operations listed above to correctly initialize the C/C++ environment.

7.9.1 Run-Time Stack

The run-time stack is allocated in a single continuous block of memory and grows down from low addresses to higher addresses. The SP points to the top of the stack.

The code does not check to see if the run-time stack overflows. Stack overflow occurs when the stack grows beyond the limits of the memory space that was allocated for it. Be sure to allocate adequate memory for the stack.

The stack size can be changed at link time by using the --stack_size link option on the linker command line and specifying the stack size as a constant directly after the option.

7.9.2 Automatic Initialization of Variables

Some global variables must have initial values assigned to them before a C/C++ program starts running. The process of retrieving these variables' data and initializing the variables with the data is called autoinitialization.

The compiler builds tables in a special section called .cinit that contains data for initializing global and static variables. Each compiled module contains these initialization tables. The linker combines them into a single table (a single .cinit section). The boot routine or a loader uses this table to initialize all the system variables.

NOTE

Initializing Variables

In ANSI/ISO C, global and static variables that are not explicitly initialized must be set to 0 before program execution. The C/C++ compiler does not perform any preinitialization of uninitialized variables. Explicitly initialize any variable that must have an initial value of 0.

The easiest method is to have a loader clear the .ebss section before the program starts running. Another method is to set a fill value of 0 in the linker control map for the .ebss section.

You cannot use these methods with code that is burned into ROM.

Global variables are either autoinitialized at run time or at load time; see Section 7.9.2.2 and Section 7.9.2.3. Also see Section 6.12.

7.9.2.1 Initialization Tables

The tables in the .cinit section consist of variable-size initialization records. Each variable that must be autoinitialized has a record in the .cinit section. Figure 7-2 shows the format of the .cinit section and the initialization records.

Figure 7-2 Format of Initialization Records in the .cinit Section cinit_recs_c28.png

The fields of an initialization record contain the following information:

  • The first field of an initialization record contains the size (in words) of the initialization data. That is, this field contains the size of the third field. The size is specified as a negative value; this is legacy behavior, and the absolute value of this field is the size of the data.
  • The second field contains the starting address of the area within the .ebss section where the initialization data must be copied. The second field requires two words to hold the address.
  • The third field contains the data that is copied into the .ebss section to initialize the variable. The width of this field is variable.

Each variable that must be autoinitialized has an initialization record in the .cinit section.

shows initialized global variables defined in C. shows the corresponding initialization table.

Initialized Variables Defined in C

int i= 23; far int j[2] = { 1,2};

Initialized Information for Variables Defined in

.global _i .ebss _i,1,1,0 .global _j _j: .usect .ebss,2,1,0 .sect ".cinit" .align 1 .field 1,16 .field _i+0,16 .field 23,16 ; _i @ 0 .sect ".cinit" .align 1 .field -IR_1,16 .field _j+0,32 .field 1,16 ; _j[0] @ 0 .field 2,16 ; _j[1] @ 16 IR_1: .set2

The .cinit section must contain only initialization tables in this format. When interfacing assembly language modules, do not use the .cinit section for any other purpose.

The table in the .pinit section simply consists of a list of addresses of constructors to be called (see Figure 7-3). The constructors appear in the table after the .cinit initialization.

Figure 7-3 Format of Initialization Records in the .pinit Section pinit_recs_tdz054.gif

When you use the --rom_model or --ram_model option, the linker combines the .cinit sections from all the C modules and appends a null word to the end of the composite .cinit section. This terminating record appears as a record with a size field of 0 and marks the end of the initialization tables.

Likewise, the --rom_model or --ram_model link option causes the linker to combine all of the .pinit sections from all C/C++ modules and append a null word to the end of the composite .pinit section. The boot routine knows the end of the global constructor table when it encounters a null constructor address.

The const-qualified variables are initialized differently; see Section 6.5.1.

7.9.2.2 Autoinitialization of Variables at Run Time

Autoinitializing variables at run time is the default method of autoinitialization. To use this method, invoke the linker with the --rom_model option.

Using this method, the .cinit section is loaded into memory along with all the other initialized sections, and global variables are initialized at run time. The linker defines a special symbol called cinit that points to the beginning of the initialization tables in memory. When the program begins running, the C/C++ boot routine copies data from the tables (pointed to by .cinit) into the specified variables in the .ebss section. This allows initialization data to be stored in ROM and copied to RAM each time the program starts.

Figure 7-4 illustrates autoinitialization at run time. Use this method in any system where your application runs from code burned into ROM.

Figure 7-4 Autoinitialization at Run Time runtime_init_c28.png
7.9.2.3 Initialization of Variables at Load Time

Initialization of variables at load time enhances performance by reducing boot time and by saving the memory used by the initialization tables. To use this method, invoke the linker with the --ram_model option.

When you use the --ram_model link option, the linker sets the STYP_COPY bit in the .cinit section's header. This tells the loader not to load the .cinit section into memory. (The .cinit section occupies no space in the memory map.) The linker also sets the cinit symbol to -1 (normally, cinit points to the beginning of the initialization tables). This indicates to the boot routine that the initialization tables are not present in memory; accordingly, no run-time initialization is performed at boot time.

A loader (which is not part of the compiler package) must be able to perform the following tasks to use initialization at load time:

  • Detect the presence of the .cinit section in the object file
  • Determine that STYP_COPY is set in the .cinit section header, so that it knows not to copy the .cinit section into memory
  • Understand the format of the initialization tables

Figure 7-5 illustrates the initialization of variables at load time.

Figure 7-5 Initialization at Load Time init_load_c28.png

Regardless of the use of the --rom_model or --ram_model options, the .pinit section is always loaded and processed at run time.

7.9.2.4 Global Constructors

All global C++ variables that have constructors must have their constructor called before main(). The compiler builds a table in a section called .pinit of global constructor addresses that must be called, in order, before main(). The linker combines the .pinit section form each input file to form a single table in the .pinit section. The boot routine uses this table to execute the constructors.

Submit Documentation Feedback

Copyright© 2015, Texas Instruments Incorporated. An IMPORTANT NOTICE for this document addresses availability, warranty, changes, use in safety-critical applications, intellectual property matters and other important disclaimers.