3 LANGUAGE IMPLEMENTATION

This chapter contains the following sections:

Introduction

Accessing Memory
Storage Specifiers
Memory Models
16 and 24-bit Models for DSP563xx
DSP566xx Memory Model
Static Model for DSP5600x
Mixed Model for DSP5600x
DSP5600x Static and Mixed Model Limitations
Reentrant Model
_MODEL, _DSP, _DEFMEM and _STKMEM
The _at() Attribute

Data Types
The Fractional Type
The Complex Data Type
Unsigned Characters
ANSI C Type Conversions
Memory Mapped Registers

Automatic Variables

Register Variables

Initialized Variables

Type Qualifier volatile

Strings

Pointers

Integer Division and Modulo

Inline C Functions

Inline Assembly
Using the _asm Intrinsic Function
Using the __asm Intrinsic Function
Using Inline Assembly Pragmas
Linking with Separate Assembly Routines

Intrinsic Functions

Interrupts

Circular Buffers

DSP563xx Cache Support
Cache Alignment
Cache Regions
Cache Intrinsic Funcions
Examples

Patriot Bank Switching Support

Packed Strings
Library Funcions
Pragmas
Examples

Structure Tags

Typedef

Switch Statement

Portable C Code

Efficient Use of the DSP56xxx Tool Set
Char and Short Types
Unsigned
Hardware Loops
Speed vs. Size
Assembly Interfacing
Selecting the Most Efficient Model
Memory Mapped I/O from C
Parallel Moves
Shifting Fractional Data
Dynamic Scaling
Reviewing the Optimized Code
Integer and Fractional Types
Interrupt Routines

3.1 Introduction

The TASKING DSP563xxx/DSP566xx Family C cross-compiler (c563) offers a new approach to high-level language programming for the DSP563xx/DSP566xx family. c56 is the DSP5600x Family C cross-compiler. They conform to the ANSI standard, but allow you to control the special functions of the DSP5600x, DSP563xx and DSP566xx in C.

The extensions to the C language in the C compiler are:

additional data types

In addition to the standard data types, c563 supports the fractional type (_fract), long fractional type (long _fract) and complex data type (_complex).

_at

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

_nosat

You can specify a fractional variable to wrap around instead of going into saturation during calculations.

storage specifiers

Apart from a memory category (extern, static, ...) you can specify a storage specifier in each declaration. This way you obtain a memory model-independent addressing of variables in several address ranges (_X, _Y, _L, _P, _near, _internal, _external). The _near, _internal and _external modifiers can also be used on functions to force them in a specific memory region.

reentrant functions

In the mixed model (DSP5600x only) you can selectively define functions as reentrant (_reentrant keyword). Reentrant functions can be invoked recursively. Interrupt programs can also call reentrant functions.

interrupt functions

You can specify interrupt functions directly through interrupt vectors in the C language (_fast_interrupt, _long_interrupt keyword).

inline C functions

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

special calling conventions

With the _compatible keyword you can specify that a function must have the same calling convention as the Motorola C compiler. The _callee_save keyword can be used to indicate that a function must save all registers, instead of leaving this to the caller.

intrinsic functions

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

circular buffers

c563 supports the type modifier _circ for circular data structures and pointers.

bank switching

You can specify a function to be located in a particular bank with the keyword _bank(). A bank is a combination of an address range and a page number.

3.2 Accessing Memory

Members of the DSP56xxx family have separate program memory and data memory, and the data memory of the DSP5600x, DSP563xx and DSP566xx is, in turn, divided into two separate memory spaces, X and Y. The combination of X and Y memory space is called L memory space. Each address range is accessible through 16-bit or 24-bit addresses. Also, the entire address range is bit-addressable.

c563 offers language extensions to deal with the separate memory spaces. You can specify a storage type (and in case of a pointer the storage type of the object it points to) with the declaration of a C variable.

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

Only a small part of the application may in fact require language extensions. These parts often have some of the following properties. They

- access I/O, using the special function registers

- need high execution speed

- need high code density

- access non-default memory

- are used to service interrupts

3.2.1 Storage Specifiers

Static storage specifiers can be used to allocate static objects in a particular memory area of the addressing space of the processor. All objects taking static storage may be declared with an explicit storage specifier. By default static variables will be allocated in X memory.

c56 and c563 recognize the following storage type specifiers:

Storage Specifier Description
_X X memory specifier (default)
_Y Y memory specifier
_L L memory specifier
_P program memory
_near lowest 64 addresses of data memory.
For functions: short addressable memory
_internal internal memory
_external external memory

Table 3-1: Storage type specifiers

Example (mixed DSP56xxx lines):

int _X            Var_in_X;       /* allocate integer variable
                                     in X memory */
int _near _X      Var_in_low_X;   /* fast accessible integer
                                     in low 64 addresses
                                     of X memory */
int _X * _Y       Ptr_in_Y_to_X;  /* allocate
                                     pointer in Y memory,
                                     used to point to integers
                                     in X */
char _P   string[] = "DSP56xxx";  /* string in program memory*/
long _L   Long_in_L;              /* allocate long variable in
                                     L memory */
_internal int _X  Intern_in_X;    /* allocate integer variable
                                     in internal X memory */

The c56 and c563 compilers use the advantage of the fact that two objects allocated in X and Y memory, can be accessed simultaneously in the same instruction.

Using the _near storage qualifier, allows the compiler to generate faster access code for frequently used variables and functions. The 'near' range depends on the core for which the program is compiled.

Using the _L storage qualifier only makes sense for the types of a double word size, which are unsigned long, signed long, float and long _fract. The most significant word is allocated in X memory and the least significant word is allocated in Y memory at the same address (X:Y). When _L is used the compiler can generate faster code for accessing these types. The compiler does not apply _L to any other data type.

For the _internal and _external storage qualifier there is no difference in code generation. The information is passed to the locator which knows which part of the memory is internal and which part is external from the description file. By default, internal memory is filled first, then external memory. With the _internal storage qualifier (high priority) you force an object in internal memory. If there is not enough internal memory the locator will give an error. With the _external storage qualifier (low priority) you give a preference for external memory, but sections declared with _external will still be located in internal memory if it is available. Using internal memory means a higher performance and lower power consumption than using external memory. Sections with none of the two modifiers (medium priority) will be located before sections created with the _external memory modifier. Except for _internal declared sections, sections may cross the border between internal and external memory if there is no gap between them.

Functions are allocated in program memory by default; the storage specifier may be omitted in that case. Also, function return values cannot be assigned to a storage area. The _near, _internal and _external storage qualifiers can be used to express storage preferences.

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

This is useful to interface to object programs using fixed memory schemes. It allows two objects, when declared in two different memory areas, to have the same starting address, for which the compiler can attempt to generate more efficient code when both objects are used as operands of one operation.

Examples using storage specifiers:

Some examples of using storage specifiers:

If a library function declares:

and a data object is declared as:

the linker will flag this as 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 from address 0. An error will be generated if this section exceeds 64 words. Next, the sections with the _internal attribute will be located. The size of the internal memory is known by the locator (it reads the DELFEE description file). If the total size of _near and _internal memory exceeds the internal memory size, an error will be generated. It is always possible to control the location of sections manually.

3.2.2 Memory Models

c563 supports the 24-bit and 16-bit modes of the DSP563xx by three models: the 24-bit model, the 16/24-bit model and the 16-bit model. Furthermore it can generate code for DSP566xx targets (16-bit). By default, the c563 compiles for the 24-bit model.

c56 supports three memory models for the DSP5600x : static, mixed and reentrant. By default, the c56 compiles for the mixed model.

You can select one of these models with the -M option. Programs for the DSP563xx and DSP566xx are always compiled using a reentrant model, since a static model would not improve code performance.

The compiler also enables you to select a different default memory space. The default memory space is where all data without memory space modifiers is placed. This can be important for backward compatibility and special hardware layouts. On the DSP5600x and DSP563xx X, Y, L and P can be selected.

Moreover, if you select X or Y memory as default memory the stack will be placed in L memory by default. This improves the execution speed because on L memory double-word moves are possible.

Separate versions of the C and run-time libraries are supplied for all supported models, avoiding the need for you to re-compile or re-build these when using a particular model.

3.2.2.1 16 and 24-bit Models for DSP563xx

The DSP563xx supports 24 and 16-bit arithmetic modes and 24 and 16-bit addressing modes. The c563 C compiler supports the following models for these modes:

The 24-bit model is the default for c563. c563 can also generate code for the DSP566xx, which is always 16-bit arithmetic with 16-bit addresses.

All DSP563xx models are reentrant.

3.2.2.2 DSP566xx Memory Model

The DSP566xx is supported with a special compiler model for c563. This model creates 16-bit code for the 16-bit address space of the DSP566xx, and takes into account the specific properties of the DSP566xx. The DSP566xx model is reentrant.

3.2.2.3 Static Model for DSP5600x

In the static model, C function parameters and automatics are passed via a static, overlayable area in memory. The linker uses a function call graph of the entire application for this purpose. Data areas of functions which do not call each other can be overlayed, since these functions will never be active simultaneously. However, this cannot be accomplished for functions called through pointers.

The static model approach for the DSP5600x version implies that function parameters and local variables are allocated statically instead of using a stack. The reason for this is that there is no efficient way of addressing objects on a stack. When it is assumed that register R7 is used as the stack pointer, three instructions would be needed to access an arbitrary object on the stack:

Absolute addressing only requires a single instruction:

When the NOP instruction cannot be replaced by something useful, the first sequence requires 3 or 4 instruction words and 7 or 8 cycles, depending on the size of the offset. Using absolute addressing, accessing an object requires only 1 or 2 instruction words and 2 or 7 cycles, depending on the size of the address.

For each function, the compiler creates a separate section for parameters, locals and temporary storage. To minimize the memory requirements of an application, the linker overlays the sections of functions that are not simultaneously active. The compiler generates information about function calls, so that the linker is able to construct a function call graph for the application.

As an example, consider a function main which calls the two functions a and b. The functions a and b are not simultaneously active, so they may use the same memory for local storage. This situation is similar to a stack based approach, where two functions also use the same memory for local storage, but where the memory is allocated on the stack.

Using a static memory model implies that the functions are not reentrant, and recursion is therefore not possible. Because of the limited depth, the hardware stack is only used for function return addresses and for hardware loops. To further limit the hardware stack requirements, a loop is not implemented by a hardware loop instruction when the loop body contains function calls.

To limit the hardware stack usage the return address of non-leaf functions is saved in the separate section for parameters, locals and temporary storage.

Whenever possible, variables and common subexpressions (CSE) are allocated in a register. Using registers increases performance and reduces power consumption.

Local variables that are allocated in a register during the entire function do not need to be allocated in memory.

3.2.2.4 Mixed Model for DSP5600x

Using a mixed memory model implies that the functions are not reentrant, and recursion is therefore not possible, except for functions explicitly declared _reentrant. This is the default memory model for the DSP5600x . The difference with the static model is that in the static model no register is reserved as stack pointer, while in the mixed model register R7 is always reserved as stack pointer. This implies that the compiler cannot use this register for storing automatics or temporary results in the mixed model.

3.2.2.5 DSP5600x Static and Mixed Model Limitations

Function Pointers

When using the static memory model (DSP5600x only), the parameter space and local variable space is allocated in a fixed area in memory. When programming with function pointers, the compiler is not able to find out where to place arguments. Normally the compiler places arguments specified in the function call directly in the variable/argument space of the called function. Doing so, the caller passes the arguments implicitly to the callee. Thus, functions requiring stacked pointers may not be called via function pointers in the static memory model, since the run-time contents of the function pointer is unknown. Consequently, it is unknown which static memory area must be used.

Individual functions may be declared reentrant when compiling for the mixed model:

Reentrant functions use the stack for passing parameters and automatic variable allocation. If the function does not use any static data, does not call any direct or extended function, then the function is both recursive and reentrant. When using the reentrant memory model, all functions are in fact implicitly reentrant.

So, function pointers are only allowed to point to functions compiled using the reentrant model. Parameters are passed to these functions via the stack. In the reentrant memory model a function pointer may point to any function in the application.

Variable Argument Lists

When compiling functions using a static model (DSP5600x only), the compiler allocates a fixed amount of static memory space for all necessary arguments and local variables. When a function has a variable argument list, the required amount of space is defined by the calling function. The actual space needed may vary from call to call. As a result, the compiler does not know how much space should be allocated for passing parameters to the function with a variable argument list.

For non-reentrant functions with variable argument lists the compiler generates eleven words. For long argument lists this may not be enough.

To overcome this problem, functions with variable argument lists are implicitly _reentrant in the mixed memory model.

3.2.2.6 Reentrant Model

If recursion or reentrancy is required, the application must be compiled using the reentrant scheme. It implies that all functions are declared _reentrant. The code generator then uses a software stack to pass parameters and for allocation of automatics. Register R7 is used as stack pointer by default.

3.2.2.7 _MODEL, _DSP, _DEFMEM and _STKMEM

c563 introduces the predefined preprocessor symbols _MODEL, _DSP and _DEFMEM. The value of _MODEL represents the memory model selected (-Mmodel option). The value of _DSP represents the processor family (0, 1, 3 or 6 for the different families). The value of _DEFMEM represents the default memory space selected (-Mmem option). This can be very helpful in making conditional C code in one source module, used for different applications in different memory models. See also the section Portable C Code, explaining the include file c56.h.

The _STKMEM symbol represents the memory space used for the stack, in the same way as the _DEFMEM symbol. The stack may be in a different memory if default memory is X or Y. In this case it is placed in L memory by default, which can be changed with the option -ML.

For c563 the value of _MODEL is:

For c56 the value of _MODEL is:

The value of _DEFMEM and _STKMEM can be x, y, l or p.

Example:

#if _MODEL == 's'   /* static model */
...

#endif

3.2.3 The _at() Attribute

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

Example:

The example above creates a variable with the name Display at address 0x2000 in external memory. In the generated assembly code an absolute section will appear. On this position space is reserved for the variable Display.

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

3.3 Data Types

All ANSI C data types are supported, except double and long double, which both are evaluated as floats. In addition to these types, the _fract, long _fract, enum and _complex types are added. Object size and ranges for all DSP56xxx families are given in tables 3-2 and 3-3:

DSP563xx/6xx 16-bit DSP563xx 24-bit DSP563xx 16/24-bit
Data Type Size
(bit)
Range Size
(bit)
Range Size
(bit)
Range
signed char 8 -128 to +127 8 -128 to +127 8 -128 to +127
unsigned char 8 0 to 255U 8 0 to 255U 8 0 to 255U
signed short 16 -32768 to +32767 16 -32768 to +32767 16 -32768 to +32767
unsigned short 16 0 to 65535U 16 0 to 65535U 16 0 to 65535U
signed int 16 -32768 to +32767 24 -8388608 to +8388607 16 -32768 to +32767
unsigned int 16 0 to 65535U 24 0 to 16777215U 16 0 to 65535U
signed long 32 -2147483648 to
+2147483647
48 -140737488355328 to
+140737488355327
32 -2147483648 to
+2147483647
unsigned long 32 0 to 4294967295UL 48 0 to 281474976710655 32 0 to 4294967295UL
_fract 16 [-1, 1] 24 [-1, 1] 16 [-1, 1]
long _fract 32 [-1, 1] 48 [-1, 1] 32 [-1, 1]
pointer 16 0 to 65535U 24 0 to 16777215U 24 0 to 16777215U
_circ pointer 16+16 0 to 65535U 24+24 0 to 16777215U 24+24 0 to 16777215U
float/double 16+8 +/- 1.1750E-38
+/- 3.4028E+38
24+8 +/- 1.1754940E-38
+/- 3.4028235E+38
16+8 +/- 1.1750E-38
+/- 3.4028E+38
enum 16 -32768 to +32767 24 -8388608 to +8388607 16 -32768 to +32767
_complex 2*16 [-1, 1] for both fields 2*24 [-1, 1] for both fields 2*16 [-1, 1] for both fields

Table 3-2: Data types DSP563xx/6xx

DSP5600x
Data Type Size
(bit)
Range
signed char 8 -128 to +127
unsigned char 8 0 to 255U
signed short 16 -32768 to +32767
unsigned short 16 0 to 65535U
signed int 24 -8388608 to +8388607
unsigned int 24 0 to 16777215U
signed long 48 -140737488355328 to
+140737488355327
unsigned long 48 0 to 281474976710655
_fract 24 [-1, 1]
long _fract 48 [-1, 1]
pointer 24 0 to 16777215U
_circ pointer 24+24 0 to 16777215U
float/double 24+8 +/- 1.1754940E-38
+/- 3.4028235E+38
enum 24 -8388608 to +8388607
_complex 2*24 [-1, 1] for both fields

Table 3-3: Data types DSP5600x

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

- although the char type is 8 bit, each char occupies one memory word, because the DSP56xxx has no instructions to access one byte efficiently.

- char and short are treated as 8-bit and 16-bit int respectively.

- the DSP56xxx convention is used, storing variables with the most significant part at the higher memory address (Little Endian).

- Double is equal to float.

3.3.1 The Fractional Data Type

The compiler supports the additional data type _fract to do fixed point arithmetic without the use of (expensive) special libraries. The implementation of this data type depends on the selected family member. Fractions can have values between -1 and +1 and can be used like integers and floating points, and combine little code overhead with a high dynamic range.

Fixed point arithmetic follows the rules for floating point calculations.

Floating point constants in [-1,1] are interpreted as a fractional type which allows fixed point arithmetic with fractional constants without suffixes or casting. Floating point value +1.0 will be interpreted as the closest fractional value to 1 possible.

Long Fractional Type

The long keyword can be used in combination with the _fract data type. The result is a fractional type with double precision, 32-bit or 48-bit depending on the selected family member.

Example:

Rounding

Rounding can be controlled by the intrinsic function _round() (see section Intrinsic Functions).

Operations

You can use all operations on fractional data which are also allowed on floating point numbers. As an example, you can add two fractional numbers, but you cannot exclusive-or them. In addition, you can use the shift operators on a fractional number; the right side of the shift operator must be an integral type.

Example:

Wrapping and Saturation

The default behavior of C fractional variables for an overflow is going into saturation. (Integer calculations will always truncate on overflows). For some calculations (such as those related to the maintenance of phase angle), it is useful to let an overflow wrap around (e.g., .75 + .5 => 1.25 , wraps to -.75). In the Motorola DSP, it can be done efficiently by storing A1 directly, which bypasses the saturation.

The _nosat keyword allows to define variables which will wrap around instead of going into saturation during calculations.

Example:

The variable Angle in this example will wrap around instead of going into saturation during calculations.

3.3.2 The Complex Data Type

The compiler supports a pre-declared type definition of type complex, which is equivalent to:

This type is supported by means of intrinsic functions. Four standard operations are provided by intrinsic services:

_complex _cadd(_complex,_complex);           /* complex addition */
_complex _csub(_complex,_complex);        /* complex subtraction */
_complex _cmul(_complex,_complex);     /* complex multiplication */
_complex _cdiv(_complex,_complex);           /* complex division */
_complex _cmac(_complex,_complex,_complex);       /* complex mac */

This offers a simple but effective way to use complex arithmetic in C applications, without sacrificing portability or efficiency.

3.3.3 Unsigned Characters

The character type is treated as unsigned char by default. Arithmetic on unsigned characters can be done more efficiently than on signed characters. You can overrule this default with the -u command line option, which sets the default to signed char.

Examples:

3.3.4 ANSI C Type Conversions

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

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

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

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

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

3.3.5 Memory Mapped Registers

Each derivative of the DSP56xxx core can have its own features like timers, ADCs and I/O ports, etc. These features are implemented using memory mapped hardware registers. Most of these registers consist of bitfields. A register and its bitfields can be accessed with a structure defined at the fixed address of the register with all fields defined. The compiler will generate efficient code for accessing structures at absolute addresses and bitfields in these structures. This makes special keywords for defining registers, which is used in other compilers, superfluous.

Example:

typedef union            /* PLL Control Register      */
{
   struct
   {
      unsigned MF  : 12;  /*  0: Multiplication Factor */
      unsigned DF  : 4;   /* 12: Division Factor       */
      int XTLD     : 1;   /* 16: XTAL Disable          */
      int PSTP     : 1;   /* 17: STOP processing State */
      int PEN      : 1;   /* 18: PLL Enable            */
      unsigned COD : 2;   /* 19: Clock Output Disable  */
      int CSRC     : 1;   /* 21: Chip Clcok Source     */
      int CKOS     : 1;   /* 22: CKOUT Clock Source    */
      int rsvd     : 1;   /* 23: Reserved              */
   } B;

   int I;

} pctl_type;
#define PCTL  (*(pctl_type*)  0xFFFD)  /* PLL Control Register */
void
main( void )
{
   PCTL.B.PEN = 1;
   PCTL.I = 0x0010;
}

Generated code:

   bset   #18,x:<<$FFFD
   movep  #>$000010,x:<<$FFFD

To simplify programming for the various different DSP56xxx derivatives, special files are supplied containing the declarations of all memory mapped register names for a specific derivative. For example, reg56002.h for the DSP56002 derivative.

You can also add your own memory mapped register definitions within the C-source. All bits without a special meaning to the chip can be given a unique name with a unique purpose.

3.4 Automatic Variables

The implementation of automatic variables in the static and mixed models of the DSP5600x has some limitations. In non-reentrant functions recursion is not possible. In these functions automatic variables are not allocated on a stack, but in a static area. In a reentrant function automatic variables are treated the conventional way: coming and going with a function on the stack. In static functions automatics are still overlayable with automatics of other functions. Allocation of automatics is subject to the memory model selected. In a static model this means static allocation in one of the RAM memory spaces. In the reentrant model this means dynamic allocation on the stack.

Although automatic variables are allocated in a static area with non-reentrant functions, they are not the same as local variables (within a function) which are declared to be static by means of the static keyword. The difference is:

- it is not guaranteed that an automatic variable still has the same value as the last time the function returned, because it may have been overlaid with another automatic variable of another module.

- it is guaranteed that the value of a static variable is the same as the last time the function returned. Static variables are never overlaid.

3.5 Register Variables

In C the register type qualifier tells the compiler that the variable will be used very often. So the code generator must try to reserve a register for this variable and use this register instead of the stack location of this automatic variable. Whenever possible, the compiler allocates automatic objects and parameter objects within registers. c563 therefore ignores the register keyword.

For every object not placed in registers, the next rules apply.

static: (DSP5600x only) In this model automatic variables are allocated on fixed positions in X or Y memory, which is directly addressable RAM. The code using this memory has a very high execution speed, so in this model there is no need to treat a register variable in a special way, because all automatic variables are accessed with a speed comparable to a real register.

reentrant: In this model automatic variables are allocated on the user stack and are addressed using the indexed addressing mode.

Conclusion: the usage of the register keyword is not necessary for improving code density or speed.

3.6 Initialized Variables

Non automatic initialized variables use the same amount of space in both ROM and RAM (for all possible RAM memory spaces). This is because the initializers are stored in ROM and copied to RAM at start-up. This is completely transparent to the user. The only exception are initialized variables declared with the const specifier.

Examples (static memory model) :

3.7 Type Qualifier volatile

You can use the volatile type qualifier when modifications on the object have undesired side effects when they are performed in the regular way. It may be undesired that the compiler attempts to optimize a memory update by keeping the value in a register (e.g., a hardware register). When a variable is declared with the volatile qualifier, the compiler disables such optimizations. The ANSI standard describes that the updates of volatile objects follow the rules of the abstract machine (the target processor) and thus access to a volatile object becomes implementation defined.

Example:

3.8 Strings

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

Strings have static storage. The ANSI X3.159-1989 standard permits string literals to be put in ROM. c563 offers the possibility to allocate a static initialized variable in ROM only, when you declare it with the const qualifier. This enables the initialization of a (const) character array in ROM (if not enough ROM is available, the string may be located in RAM as well):

Or a pointer array in ROM only, initialized with the addresses of strings, also ROM only:

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

The ANSI Standard states that identical string literals need not be distinct, i.e. may share the same memory. Because memory can be very scarce with DSP applications, c563 overlays identical strings within the same module.

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

c563 accepts this statement when strings are in both ROM and RAM.

3.9 Pointers

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

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

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

If the target memory and storage memory of a pointer are not explicitly declared, c563 uses the default _X storage specifier. You can overrule the default memory with the -M command line option.

In pointer arithmetic c563 checks, besides the type of each pointer, also the target memory of the pointers, which must be the same. For example, it is invalid (and has no use) to assign a pointer to _X to a pointer to _L. Of course, an appropriate cast corrects the error.

3.10 Integer Division and Modulo

If you divide a negative number by a positive one, there are two possible outputs, one smaller than the actual (float) value, one larger than it, and they have different modulo values too. For example, -5 / +3 equals -1.666, so converted to integers we can get:

-5 / +3 = -2,  -5 % +3 = +1   ==>  (-5 / +3) * +3 + (-5 % +3) = -5

OR:

-5 / +3 = -1,  -5 % +3 = -2   ==>  (-5 / +3) * +3 + (-5 % +3) = -5

The ANSI C standard (section 3.3.5) allows both sets of outcomes, it only requires that the calculation sums up correctly as shown after the arrow. In the TASKING C compiler we have chosen to implement the first form, as this gives the most compact code: we can do divisions with shifts, and modulo operations with bitwise-and, if the second operand is a power of two.

3.11 Inline C Functions

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

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

Example (t.c):

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

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

The generated code is:

3.12 Inline Assembly

c563 supports using assembly in the following ways:

1. by using the _asm() intrinsic function;

2. by using the __asm( ) intrinsic function, which allows passing and returning values to the assembly code;

3. by using pragmas;

4. by linking with separate assembly modules.

C modules that contain inline assembly are not portable and harder to prototype in other environments.

3.12.1 Using the _asm Intrinsic Function

c563 supports the intrinsic function _asm() for inline assembly. The argument of this function is a string that contains the assembly code to be inlined.

For example:

This is equal to:

The advantage is that the _asm() intrinsic can be used in pre-processor defines:

3.12.2 Using the __asm Intrinsic Function

To allow access to local variables from inline assembly, the compiler provides the __asm() (two leading underscores) intrinsic function. This function is highly compatibly with the Motorola C compiler function, although differences may exist in compiler-structure dependent areas. Note that other uses of the __asm keyword in the Motorola C compiler (e.g. forcing variables in specific registers) are not supported. With the __asm keyword C expressions can be assigned to a register from a group (e.g. an address register), and result values can be written to local or global C variables.

The general syntax of the __asm statement is:

where,

instruction_template is a string that may contain %[mod_char]parm_nr parameters from the in- or output list.

mod_char is an operand modifier character (see table 3-5)

parm_nr is a parameter number in the range 0..31.

output_param_list [[ "=constraint_char"(c_expression)],...]

input_param_list [[ "constraint_char"(c_expression)],...]

constraint _char is an operand constraint character
(see table 3-4)

regsave [["register_name"],...]

register _name is one of the work register names (see below)

The constraint characters specify a group of registers for the parameter. The modifier characters select a specific part of the register selected for the parameter. For output parameters, the c_expression must be something that can be assigned to (an lvalue). Registers that are used in the assembly instructions must be reserved, either in the parameter lists or in the reserved register list (regsave, above). The compiler takes account of these lists (but does not interpret the instruction template!), and no unnecessary register saves and restores are placed around the inline assembly instructions.

Except for the instruction template, all parts within the braces are optional. The TASKING C compiler accepts but ignores the volatile keyword after the __asm keyword. Also the superfluous '=' equals signs in the output parameter list are accepted but ignored.

Available input/output operand constraints

Char Type Operand Remark
A address register r0..r7 The compiler excludes the user stack pointer and reserved address registers
N offset register n0..n7 The compiler excludes the user stack pointer offset and reserved offset registers
C address + modulo register r0/m0..r7/m7 For circular pointers
D accumulator a, b The complete 56-bit accumulator registers
R input register x0, y0, x, y Selection depends on operand size
r GP register a, b, x0, y0, x1, y1, r0..r7, n0..n7 All general-purpose int-sized registers except user stack pointer and offset
S source register x0, x1, y0, y1, x, y Selection depends on operand size
i immediate value #value
m memory x:Fvar, x:(r7-i) Stack or memory operand
number other operand same as number Used when in- and output operands must be the same

Table 3-4: Available input/output operand constraints

Available operand modifiers

Char Constraint Operand Remark
j A, C n0..n7 Offset register matching with address register. You must save and restore these registers.
v A, C m0..m7 Modulo register matching with address register (this modifier is not supported in Motorola C).
e D a1, b1 High part of accumulator.
h D a0, b0 Low part of accumulator.
k D a2, b2 Extension word of accumulator.
g R, S x1, x0, y1, y0 Other part of input register
i R, S x, y Long input register matching with integer-sized input register.
f m x:,y:,p:,l: Insert memory space for the operand.
p i $0000..$FFFF Force 16-bit sized constant
q i $000000..$FFFFFF Force 24-bit sized constant

Table 3-5: Available operand modifiers

Possible work registers

a, a0, a1, a2, b, b0, b1, b2, x, x0, x1, y, y0, y1, r0..r7, n0..n7 (except user stack pointer r and n register)

m0..m7 cannot be reserved, they must be restored in the template to -1 after use. Of course the associated r-register must be set free, either by making it an output parameter, or by placing it in the reserved register list.

Most of the examples below are very trivial, and could be coded in C just as well with similar results. They are provided just to demonstrate the use of the __asm keyword. Situations where the __asm keyword is used should give a definite improvement of the generated code, to justify the complexity and lack of portability of the __asm statement. Assembly functions larger than ten statements should preferably be written in a separate assembly module, not inline, to improve readability and portability.

It is not allowed to create loops with multiple __asm statements, or generate (conditional) jumps across __asm statements. The compiler cannot detect these 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, independent of it being a hardware loop or a backward jump. The same restriction applies to (conditional) jumps. As a rule of thumb, all references to a label in an __asm statement must be contained in the same statement.

Example 1

A simple example without input or output parameters. The mr register is not a register used by the compiler and therefore can be used without reserving it.

Generated code:

Example 2

The simplest example, it only generates a label. Because the compiler inserts a tab character before emitting the first character of the template, we must provide a newline first (or follow the label with a semicolon).

Generated code (note that the first line is empty):

Example 3

Assign the result of inline assembly to a variable. An accumulator-type register is chosen for the parameter due to the constraint D; the compiler decides which accumulator is best to use. The string %0 in the template is replaced with the name of this accumulator. Finally, the compiler generates code to assign the result to the output variable.

Generated code:

Example 4

Multiply two expressions and assign the result to a variable. Two expressions are multiplied and the result is placed in a variable. An accumulator-type register is necessary for the output parameter (constraint D, %0 in the template); alu registers (X0, Y0, X1 or Y1) must be used for the input registers (constraint S, %1 and %2 in the template). The compiler generates code to move the input expressions into the input registers and to assign the result to the output variable.

Generated code:

Example 5

Execute a block move in Y memory. Two pointer expressions are given and the contents of a memory block in Y memory is copied. This example has no output, and uses a scratch register for the copying action; it also demonstrates the use of C expressions in the parameters. Notice that the address registers have been declared as output registers because they are destroyed in the routine. To get the same registers in the input parameter list, the number of the output parameter is used instead of the constraint character. The instruction template has been split up over several shorter source lines for better readability. The lines can be written on a single line as well (with '\n' sequences!).

Generated code:

Example 6

The previous example can be improved in two ways: the loop count can be made a parameter as well, and we can leave it to the compiler to decide which scratch integer register to use. It is better to reserve a work register through an unassigned output parameter than through the reserved list, because specifying a group gives the compiler more optimization opportunities than handpicking a register.

Generated code:

Example 7

The register used in the assembly template can be changed with a modifier, which is placed between the '%' and the number of the parameter. This way you can use partial registers and tell the compiler to reserve the complete register. The following example swaps the two halves of a long. An accumulator is used as the input to the function, because the function parameter will be passed in A. A work register is chosen from the alu register set.

Generated code:

Example 8

When a modulo register is used in the template, it must be restored to the default value -1 (linear addressing mode) after use. The modifier 'v' can be used to select the modifier register associated with an address register.

Generated code:

3.12.3 Using Inline Assembly Pragmas

c563 supports inline assembly using the following pragmas:

#pragma asm Insert assembly text following this pragma.

#pragma asm_noflush Same as #pragma asm but the peephole optimizer will not flush the code buffer.

#pragma endasm Switch back to the C language.

The peephole optimizer in the compiler maintains a code buffer for optimizing sequences of assembly instructions before they are written in the output file. The compiler does not interpret the text of inline assembly. It passes inline assembly lines directly to the output file.

Example:

The compiler will not save any registers prior to the pragma asm and also does not restore the original contents of the registers after the pragma endasm. This implies that when registers must be used within the pragma asm/endasm you have to save the contents of these registers first.

For using registers within the inline assembly you cannot rely on the registers the compiler allocated for C variables. Changing the C code may change the register allocation. Also the register allocation may change in future versions of the C compiler. You should use the __asm() intrinsic function if your code requires parameter passing.

These pragmas can also be used outside the scope of a function, for instance to create temporary storage for an assembly routine or to specify a vector. Using NOP instructions to place code at specific positions must be avoided, as the assembler will remove these when in optimizing mode. This is a common method in the vector table in combination with short jumps, but it can easily be avoided with multiple ORG statements.

3.12.4 Linking with Separate Assembly Routines

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

Example:

With the _asmfunc function qualifier the parameter interface is identical to the standard calling convention (see section 7.3 Calling Conventions), but the stack is not used for passing arguments to functions. When there are more arguments to be passed than fit in the registers the compiler will issue an error message.

All registers can be used in the assembly function. On return, the M-registers must be reset to -1 (except M0 if the function returns a circular pointer).

3.13 Intrinsic Functions

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

Intrinsic functions appear to the programmer as normal C functions, but the difference is that they are interpreted by the code generator, and as a result more efficient code may be generated. The names of the intrinsic functions all have a leading underscore, because the ANSI specification states that public C names starting with an underscore are implementation defined.

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

The following intrinsic functions are implemented:

Function Description
_abs() Absolute value of int argument
_asm() Generate inline assembly
__asm() Generate inline assembly with parameter passing
_cache_get_start() Get first address of cache region
_cache_get_end() Get last address of cache region
_cadd() complex addition
_cdiv() complex division
_cmac() complex multiply-accumulate
_cmul() complex multiplication
_csub() complex division
_ext() Extend high order part of accumulator for (unsigned) chars and shorts
_fabs() Absolute value of _fract argument
_fract2int() Convert _fract to int
_fsqrt() Generate a JSR to the run-time library function Rfsqrt()
_int2fract() Convert int to _fract
_labs() Absolute value of long argument
_lfabs() Absolute value of long _fract argument
_lfract2long() Convert long _fract to long
_long2lfract() Convert long to long _fract
_memcpy() Copy block of memory. Compact memcpy replacement
_memset() Fill block of memory. Compact memset replacement
_nop() NOP instruction, not optimized away
_pdiv() Calculates a/b inline, when it is known that a and b are both positive
_pflush() Flush cache
_pflushun() Flush unlocked sectors
_pfree() Global unlock
_plock() Lock 1 sector in cache
_punlock() Unock 1 sector in cache
_rol() Rotate left
_ror() Rotate right
_round() Specifies rounding (fractional arithmetic operations)
_sema_clr() Clear semaphore
_sema_set() Set semaphore
_sema_tst() Test semaphore
_stop() STOP, stop mode saves power consumption
_strcmp() String compare. Compact strcmp replacement
_strcpy() String copy. Compact strcpy replacement
_strlen() String length. Compact strlen replacement
_swi() SWI (c56) or TRAP (c563), software interrupt
_vsl() Viterbi shift left (c563 only)
_wait() WAIT, wait mode saves power consumption

Table 3-6: Intrinsic functions

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

_abs

Absolute integer value. Generate ABS instruction.

Returns the result.

Example:

volatile int ia, ib;
ia = _abs( ib );

... Code ...
     move x:ss_main+23,b     ; ib
     abs  b                  ; _abs(ib)
     move b1,x:ss_main+24    ; ia

_asm

This function can be used to generate inline assembly. The asm argument must be a string constant with valid assembly code. The compiler will prefix the string with a tab. For more information, see section 3.12.1, Using the _asm Intrinsic Function.

Returns nothing.

Example:

#define SAVEINTSTAT _asm( "move SR,X:(r2)+" ); \
     _asm( "ori  #$03,MR" )

     SAVEINTSTAT;

... Code ...
     move SR,X:(r2)+
     ori  #$03,MR

__asm

This function can be used to generate inline assembly with parameter passing. The instruction_template argument must be a string constant with valid assembly code and optional register placeholders. The compiler will prefix the string with a tab. For more information, see section 3.12.2, Using the __asm Intrinsic Function.

Returns nothing.

_cache_get_start

This function gets the start address of the cache region belonging to fptr.

Returns the start address of the cache region belonging to fptr.

Example:

See example with _plock function.

This function is only available for the DSP563xx. For more information about instruction cache support, see section 3.16 DSP563xx Cache Support.

_cache_get_end

This function gets the end address of the cache region belonging to fptr.

Returns the end address of the cache region belonging to fptr.

Example:

See example in section 3.16.4 Examples.

This function is only available for the DSP563xx. For more information about instruction cache support, see section 3.16 DSP563xx Cache Support.

_cadd

Complex addition.

Returns the result.

Example:

_complex ca, cb, cc;
cc = _cadd( ca, cb );
... Code ...
     move x:Fca,b
     move x:Fca+1,a
     move x:Fcb,x0
     move x:Fcb+1,x1
     add  x0,b
     add  x1,a
     move a,x:Fcc+1 ; cc
     move b,x:Fcc   ; cc

_cdiv

Complex division. Calculate op1 / op2.

Returns the result.

Example:

_complex ca, cb, cc;
cc = _cdiv( ca, cb );
... Code ...
     move x:Fca,b
     move x:Fca+1,a
     move x:Fcb,x0
     move x:Fcb+1,x1
     jsr  F_cdiv     ; _cdiv(ca, cb)
     move a,x:Fcc+1  ; cc
     move b,x:Fcc    ; cc

_cmac

Complex multiply and accumulate. Calculate op1 + op2 * op3 generating MAC instructions.

Returns the result.

Example:

_complex ca, cb, cc, cd;
cd = _cmac( ca, cb, cc );
... Code ...
     move x:Fca,b
     move x:Fca+1,a
     move x:Fcb,x0
     move x:Fcb+1,x1
     move x:Fcc,y0
     move x:Fcc+1,y1
     mac  x0,y0,b
     macr -x1,y1,b
     mac  x0,y1,a
     macr x1,y0,a
     move a,x:Fcc+1     ; cc
     move b,x:Fcc       ; cc

_cmul

Complex multiplication.

Returns the result.

Example:

_complex ca, cb, cc;
cc = _cmul( ca, cb );
... Code ...
     move x:Fca,x0
     move x:Fca+1,x1
     move x:Fcb,y0
     move x:Fcb+1,y1
     mpy  x0,y0,b
     macr -x1,y1,b
     mpy  x0,y1,a
     macr x1,y0,a
     move a,x:Fcc+1     ; cc
     move b,x:Fcc       ; cc

_csub

Complex subtraction. Subtract operand op2 from op1.

Returns the result.

Example:

_complex ca, cb, cc;
cc = _csub( ca, cb );
... Code ...
     move x:Fca,b
     move x:Fca+1,a
     move x:Fcb,x0
     move x:Fcb+1,x1
     sub  x0,b
     sub  x1,a
     move a,x:Fcc+1     ; cc
     move b,x:Fcc       ; cc

_ext

Generate instructions to extend high order part of accumulator for (unsigned) chars and shorts. Generate MOVE instructions.

Returns the result.

Example:

volatile unsigned long ula, ulb;
ula = _ext( ulb );

... Code ...
     move x:ss_main+14,b
     move x:ss_main+13,b0
     move #0,b2
     move b1,x:ss_main+16     ; ula
     move b0,x:ss_main+15     ; ula

_fabs

Absolute _fract value. Generate ABS instruction.

Returns the result.

Example:

volatile _fract fa, fb;
fb = _fabs( fa );

... Code ...
     move x:ss_main+11,b     ; fb
     abs  b                  ; _fabs(fb)
     move b,x:ss_main+12     ; fa

_fract2int

This intrinsic function changes a fractional value into an integer value without generating any conversion code.

Returns the result.

Example:

int i;
_fract f = 0.5;
i = _fract2int(f);

... Code ...
     move #<$40,y0
     move y0,x:Ff
     move y0,x:Fi

_fsqrt

Calculate the square root of the fractional operand. Calls the run-time library function Rfsqrt.

Returns the result.

Example:

volatile _fract fa, fb;
fa = _fsqrt( fb );

... Code ...
     move x:ss_main+11,b     ; fb
     jsr  Rfsqrt             ; _fsqrt(fb)
     move b,x:ss_main+12     ; fa

_int2fract

This intrinsic function changes an integer value into a fractional value without generating any conversion code.

Returns the result.

Example:

int i = 0x400000;
_fract f;
f = _int2fract(i);

... Code ...
     move #<$40,y0
     move y0,x:Fi
     move y0,x:Ff

_labs

Absolute long value. Generate ABS instruction.

Returns the result.

Example:

volatile long la, lb;
la = _labs( lb );

... Code ...
     move x:ss_main+18,b
     move x:ss_main+17,b0
     abs  b               ; _labs(lb)
     move b1,x:ss_main+20 ; la
     move b0,x:ss_main+19 ; la

_lfabs

Absolute long _fract value. Generate ABS instruction.

Returns the result.

Example:

long _fract lfa, lfb;
lfb = _lfabs( lfa );

... Code ...
     move x:ss_main+8,b
     move x:ss_main+7,b0
     abs  b               ; _lfabs(lfb)
     move b,x:ss_main+10  ; lfa
     move b0,x:ss_main+9  ; lfa

_lfract2long

This intrinsic function changes a long fractional value into a long value without generating any conversion code.

Returns the result.

Example:

long l;
long _fract lf = 0.5;
l = _lfract2long(lf);

... Code ...
     move #<$40,x1
     move #0,x0
     move x1,x:Flf+1
     move x0,x:Flf
     move x1,x:Fl+1
     move x0,x:Fl

_long2lfract

This intrinsic function changes a long value into a long fractional value without generating any conversion code.

Returns the result.

Example:

long l = 0x4000000000L;
long _fract lf;
lf = _long2lfract(l);

... Code ...
     move #$4000,b
     move b1,x:Fl+1
     move b0,x:Fl
     move b,x:Flf+1
     move b0,x:Flf

_memcpy

Copy a block of memory. An optimized inlined version of the library function memcpy, that works on all memory spaces.

The size parameter type is unsigned int, not size_t. This is a small difference on the 16/24-bit model of the DSP563xx between _memcpy and memcpy.

Returns the copy destination

Example:

/* _memcpy for all memory spaces */
_X int a[10];
_Y int b[10];
void main(void)
{
  _memset(a,1,sizeof(a));
  _memcpy(b,a,sizeof(a));
}

_memset

Fill a block of memory. An optimized inlined version of the library function memset, that works on all memory spaces.

The size parameter type is unsigned int, not size_t. This is a small difference on the 16/24-bit model of the DSP563xx between _memset and memset.

Returns the destination

Example:

/* _memset for all memory spaces */
_X int a[10];
_Y int b[10];
_X void * const p = a+5;
_Y void * const q = b+5;
void main(void)
{                         /*   space derived from   */
                          /* ---------------------- */
  _memset(a,1,sizeof(a)); /*  space 'a' resides in  */
  _memset(b,2,sizeof(b)); /*  space 'b' resides in  */
  _memset(p,3,5);         /*  space 'p' points to   */
  _memset(q,4,5);         /*  space 'q' points to   */
}

_nop

Generate NOP instructions.

Returns nothing.

Example:

_nop();
... Code ...
     opt  noopnop
     nop
     opt  opnop

_pdiv

Calculate the division of op1/op2 for positive fractional operands. Generate DIV instruction.

Returns the result.

Example:

volatile _fract fa, fb;
fa = _pdiv( fa, fb );
... Code ...
     move x:ss_main+12,b     ; fa
     move x:ss_main+11,x0    ; fb
     andi #$FE,ccr
     rep  #24
     div  x0,b               ; _pdiv(fa, fb)
     move b,x:ss_main+12     ; fa

_pflush

Use the PFLUSH instruction to flush the instruction cache.

Returns nothing.

Example:

_pflush();

... Code ...
     pflush

This function only generates code for the DSP563xx. For more information about instruction cache support, see section 3.16 DSP563xx Cache Support.

_pflushun

Use the PFLUSHUN instruction to flush unlocked instruction cache sectors.

Returns nothing.

Example:

_pflushun();

... Code ...
     pflushun

This function only generates code for the DSP563xx. For more information about instruction cache support, see section 3.16 DSP563xx Cache Support.

_pfree

Use the PFREE instruction to unlock all the locked cache sectors in the instruction cache.

Returns nothing.

Example:

_pfree();

... Code ...
     pfree

This function only generates code for the DSP563xx. For more information about instruction cache support, see section 3.16 DSP563xx Cache Support.

_plock

This function uses the PLOCK instruction to lock one sector in the instruction cache. The argument of this function is the sector address to be locked.

Returns nothing.

Example:

void foo( void )
{
     void _cache_region creg(void);

     _plock( _cache_get_start( creg ) );
          /* loop fits in one sector */
#pragma cache_align_now
#pragma cache_region_start creg
     for ( ... )
     {
          ...
     }
#pragma cache_region_end creg
     _punlock( _cache_get_start( creg ) )

... Code ...
     move    #Fcreg,r6
     nop
     plock   (r6)
     align   cache
     ...
     move    #Fcreg,r6
     nop
     punlock (r6)

This function only generates code for the DSP563xx. For more information about instruction cache support, see section 3.16 DSP563xx Cache Support.

_punlock

This function uses the PUNLOCK instruction to unlock one sector in cache. The argument of this function is the sector address to be unlocked.

Returns nothing.

Example:

See example with _plock function.

This function only generates code for the DSP563xx. For more information about instruction cache support, see section 3.16 DSP563xx Cache Support.

_rol

Use the ROL instruction to rotate (left) operand count times. The carry bit is reset before rotation.

Returns the result.

Example:

volatile unsigned int uia, uib;
/* rotate left, using int variable */
uia = _rol( uia, uib );
... Code ...
     move x:ss_main+22,b     ; uia
     move x:ss_main+21,a     ; uib
     tst  a                  ; uib
     jeq  L3
     rep  a1
     rol  b                  ; _rol(uia, uib)
L3:  move b1,x:ss_main+22    ; uia

_ror

Use the ROR instruction to rotate (right) operand count times. The carry bit is reset before rotation.

Returns the result.

Example:

volatile unsigned int uia, uib;
/* rotate right, using constant */
uia = _ror( uib, 2 )
uia = _ror( uia, 3 );
... Code ...
     move x:ss_main+21,b     ; uib
     ror  b                  ; _ror(uib, 2)
     ror  b
     move b1,x:ss_main+22    ; uia

     move x:ss_main+22,b     ; uia
     rep  #3
     ror  b                  ; _ror(uia, 3)
     move b1,x:ss_main+22    ; uia

_round

Round the fractional operand. Generate RND instruction.

Returns the result.

Example:

volatile _fract fa, fb;
volatile long _fract lfa, lfb;

void main( void )
{
     fa = _round(fa);
     fb = _round(lfa);
     lfb = _round(lfa);
}
... Code ...
     move x:ss_main+12,b     ; fa
     rnd  b                  ; _round(fa)
     move b,x:ss_main+12     ; fa
;
     move x:ss_main+10,b
     move x:ss_main+9,b0
     rnd  b                  ; _round(lfa)
     move b,x:ss_main+11     ; fb
;
     move x:ss_main+10,b
     move x:ss_main+9,b0
     rnd  b                  ; _round(lfa)
     move b,x:ss_main+8      ; lfb

Semaphore intrinsics

Semaphores are program flags that are used to synchronize processes and to force exclusive access to a resource. Examples are an interrupt function that sets a semaphore when data is read, and a processing loop that clears it when done; or a serial port that is used by several parallel processes and must be line-blocked. To guarantee correctness, semaphore actions must be atomic (non-interruptable). The semaphore intrinsic functions guarantee this without the overhead of blocking and re- enabling interrupts. To do this, the generated code contains a bit clear, bit set or bit test assembly instruction to access the semaphore.

_sema_clr

This function clears a semaphore (a bit in a volatile int field pointed to by p). 'bitnumber' must be an integral constant expression and should not exceed the width of an int in the DSP program. It can either be used to clear a semaphore (ignoring the result), or to test and clear a semaphore (result contains previous state).

Returns the previous state of the semaphore.

Example:

typedef volatile int semaphore_t;

void f(void)
{
        static semaphore_t flag = 1;

        while( !_sema_clr( &flag, 0 ) )
            ; /* wait until we reset flag ourselves */
        /* code to handle device */
        _sema_set( &flag, 0 );
        /* free device for other processes */
}
... Code ...
L3:     dc      $000001
        org     p,".ptext":
L4:     bclr    #0,x:L3       ; _sema_clr
        jcc     L4
        bset    #0,x:L3       ; _sema_set
        rts

_sema_set

This function sets a semaphore (a bit in a volatile int field pointed to by p). 'bitnumber' must be an integral constant expression and should not exceed the width of an int in the DSP program. It can either be used to set a semaphore (ignoring the result), or to test and set a semaphore (result contains previous state).

Returns the previous state of the semaphore.

Example:

typedef volatile int semaphore_t;

void f(void)
{
        static semaphore_t flag = 0;

        while( _sema_set( &flag, 0 ) )
             ; /* wait until we set flag ourselves */
        /* code to handle device */
        _sema_clr( &flag, 0 );
        /* free device for other processes */
}

... Code ...
L3:     dc      $000001
        org     p,".ptext":
L4:     bset    #0,x:L3       ; _sema_set
        jcs     L4
        bclr    #0,x:L3       ; _sema_clr
        rts

_sema_tst

This function tests a semaphore (a bit in a volatile int field pointed to by p). It does not change the state of the semaphore. 'bitnumber' must be an integral constant expression and should not exceed the width of an int in the DSP program.

Returns the current state of the semaphore.

Example:

typedef volatile int semaphore_t;
semaphore_t dev_started;    /* set by other routines */

void f(void)
{
        while( ! _sema_tst( &dev_started, 15 ) )
             ; /* wait until device is operational */
        /* code using device */
}
... Code ...
        org     p,".ptext":
L3:     btst    #15,x:Fdev_started     ; _sema_tst
        jcc     L3
        rts

_stop

Generate STOP instruction.

Returns nothing.

Example:

_stop();

... Code ...
     stop

_strcmp

Perform a string compare. An optimized inlined version of the library function strcmp, that works on all memory spaces.

Returns <0 if op1 < op2
0 if op1 == op2
>0 if op1 > op2

Example:

char * sa="strna";
char * sb="strnb";

main()
{
     int result;
     result = _strcmp( sa, sb );
}
... Code ...
        move    x:Fsa,r6
        move    x:Fsb,r5
        move    x:(r5)+,y0
L5:     move    x:(r6)+,a
        sub     y0,a
        jne     L6
        add     y0,a    x:(r5)+,y0
        jne     L5
L6:

_strcpy

Perform a string copy. An optimized inlined version of the library function strcpy, that works on all memory spaces.

Returns op1

Example:

char * sa="strna";
char * sb="strnb";

main()
{
     _strcpy( sa, sb );
}
... Code ...
        move    x:Fsa,r6
        move    x:Fsb,r5
L5:     move    x:(r5)+,a
        tst     a       a,x:(r6)+
        jne     L5

_strlen

Calculate the length of a string. An optimized inlined version of the library function strlen, that works on all memory spaces.

Returns the length of the string.

The result type is unsigned int, not size_t. This is a small difference on the 16/24-bit model of the DSP563xx between _strlen and strlen.

Example:

char * sa="strna";

main()
{
     int length;
     length = _strlen( sa );
}
... Code ...
        move    x:Fsa,r5
        move    x:(r5)+,b
        lua     (r5)+,n5
L4:     tst     b       x:(r5)+,b
        jne     L4
        lua     (r5)-n5,b

_swi

Software interrupt. Generate SWI (c56) or TRAP (c563) instruction.

Returns nothing.

Example:

_swi();

... Code ...
     swi

_vsl

Perform a Viterbi shift left operation. Generates a VSL assembler instruction.

Returns Nothing. The high part of op1 is stored in _X:op3, the low part of op1 is shifted left one bit and the value of op2 (0 or 1) is added to it. and stored in _Y:op3.

Example:

long _L result;
long input = 0x12345678;

main()
{
     _vsl(input, 0, &result);
     _vsl(input, 1, &result);
}

... Code ...
     vsl  b,#0,l:Fresult
     vsl  b,#1,l:Fresult

This function only generates code for the DSP563xx and DSP566xx. On the DSP563xx a warning is generated because the opcode is not supported on all silicon revisions.

_wait

Generate WAIT instruction.

Returns nothing.

Example:

_wait();

... Code ...
     wait

3.14 Interrupts

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

The compiler will generate an interrupt service frame for long interrupts and no frame in case of fast interrupts. The vector specifies the interrupt number of a two word interrupt vector area. Some interrupts are reserved and handled or used by the compiler (run-time) library, like:

The interrupt vector -1 is reserved for a so-called symbolic interrupt. This means that c563 does not assign an interrupt number to this C function. Symbolic interrupts can only be used with _long_interrupt.

When the _fast_interrupt modifier is used:

Example of _fast_interrupt:

Suppose, you want an interrupt function for a peripheral, and the vector number is 17:

This will result in assembly:

3.15 Circular Buffers

The DSP56xxx family supports circular buffers, for which no representation in C exists. A circular buffer is a linear array that can be accessed using modulo address arithmetic, i.e., a pointer that wraps-around automatically, thus creating a virtual circular buffer. To allow you to use circular buffers in C, c563 supports the data type _circ as an extended data type.

Example:

Here, Circ_Buf is declared as a circular buffer. The compiler will emit alignment directives to ensure circular buffers will start at addresses that are a multiple of the smallest power of two that is equal to or larger than the buffer size. The buffer size is kept by the compiler and will be used to control pointer arithmetic of pointers that are assigned to the buffer later.

In the above example, the circular pointer Ptr_to_circ_Buf will be stored in an R-type register and the proper modulo value will be stored in its corresponding M-type register. Operations on the circular pointers can be done using the usual C pointer arithmetic with the difference that the pointer will wrap.

When the circular buffer is accessed using a circular pointer, it will wrap at the buffer limits.

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.

Example:

the following code will be used:

The moves to and from input_buf will be optimized away mostly. Also setting the modulo register will be done only once, unless register R3 is used for another purpose that requires other or no modulo arithmetic.

3.16 DSP563xx Cache Support

The DSP563xx is equipped with an instruction cache. The compiler supports this cache with the features described in the following sections.

The c56 compiler and the c563 compiler in DSP566xx memory model ignore the cache features, which makes it possible to write portable code.

3.16.1 Cache Alignment

Instructions are cached in sectors of 128 or 256 words, depending on the cache size. The size of the cache varies by DSP563xx derivative.

The following pragma is supported for forcing alignment on a 128 or 256-word boundary:

#pragma cache_align_now
Aligns current address at a cache boundary. This can be done only once per function. The alignment may introduce an unused memory alignment gap at the beginning of the section.

Whether the cache alignment is on a 128 or 256-word boundary depends on the compiler command line option -csize or the #pragma cache_sector_size size. The size is either 128 or 256 words. The compiler has a predefined macro for the cache sector size _CACHE_SECTOR_SIZE that expands to size value.

3.16.2 Cache Regions

Cache regions are a start and end address of memory to be locked in the cache. Such a region can be bound to a function or function pointer. The user can lock and unlock the cache sectors in a region using intrinsic functions. The function or function pointer must have the function qualifier _cache_region. For a function with this function qualifier the compiler will generate a start and an end label. Functions having this qualifier are always aligned on the cache sector size. A cache function definition will look as follows:

When a function pointer with the _cache_region qualifier is defined, the begin and end labels can be set manually. For this purpose two pragmas are available:

#pragma cache_region_start fptr
Mark start position of a cache region.

#pragma cache_region_end fptr
Mark end position of a cache region.

The fptr can be defined as follows:

See section 3.16.4 for examples.

3.16.3 Cache Intrinsic Functions

The following intrinsic functions are available for cache locking:

void _pflush( void ); flush cache

void _pflushun( void ); flush unlocked sectors

void _pfree( void ); global unlock

void _plock( int _P *addr );
lock 1 sector in cache with address addr

void _punlock( int _P *addr );
unlock 1 cache sector with the address addr

int _P * _cache_get_start( void _cache_region * fptr) () );
get first address of cache region belonging to fptr

int _P * _cache_get_end( void _cache_region * fptr) () );
get last address of cache region belonging to fptr

3.16.4 Examples

Example 1:

Locking a loop in the cache:

This example assumes that the loop fits in one cache sector. Therefore, only the start address is locked in the cache.

Example 2:

This example shows how to lock a function from another module. This example does not assume that the whole function fits in one cache sector.

Module A:

Module B:

3.17 Patriot Bank Switching Support

The banked program memory feature of the Patriot chips (DSP56622 / DSP56671 / DSP56679 / DSP56690 / DSP56691 / DSP56694) is supported with the _bank() keyword. A bank is combination of a particular address range and a page number.

range a number from 0 to 3, corresponding to the following available address ranges:

range# address range
0 0X8000-0X9FFF
1 0XA000-0XBFFF
2 0XC000-0XDFFF
3 0XE000-0XEFFF

Table 3-7: Address ranges

page a number of 0 or 7, corresponding to the page a function should be located in.

Use the command line option -ppage to specify the total number of pages (page is 1..8). Default is two pages.

A function cannot call functions in the same range but within a different page number. For function calls to functions in the same range and page or in main memory (address 0x000 - 0x7FFF) no extra instructions for switching pages are generated. When the called function is in a different range, switch instructions will be generated. When a function switches pages it saves the value of the DDM Page Configuration Register (DPCR) at the start of the calling function and restores it at the function end.

When you define a function with the _bank keyword, the compiler generates an special section name. The section name begins with ".ptext_", followed by a letter indicating the range (where A is range 0, B is range 1, etc.) and followed by a number indicating the page.

For the S-record format the banks are coded in the section information. For the other formats the locator places the sections at the appropriate addresses. For page 0 this is as indicated in the table above. Ranges within page 1 will be located at the address plus 0x10000. The CrossView Pro debugger/loader will interpret these addresses and switch to the correct bank when memory in those ranges is accessed. The user can use the same addresses to look at the program memory. Furthermore the PC register the user sees will also reflect the current state of the DPCR register for and if it points to the paged memory.

Restrictions:

Example

The following code calls function foo() from function bar().
Function foo() is located in range 1, page 0 and function bar() is located in range 3, page 1.

3.18 Packed Strings

The c56 and c563 compilers support packed characters and packed strings. The _packed type modifier will be used to define a packed string. The _packed modifier can only be applied to characters, character arrays or pointers to character. Incrementing a packed character pointer means incrementing to the next word. Thus, two (c563 in 16-bit model) or three (c56, c563 in 24-bit model) characters are skipped.

3.18.1 Library Functions

To be able to access packed strings, the C library has been extended with functions to deal with packed strings:

char *_unpackstr( char * unp, const _packed char * p );
Unpack string pointed to by p in the buffer pointed to by unp. Return a pointer to the unpacked string.

_packed char *_packstr( _packed char * p, const char * unp );
Pack string pointed to by unp in the buffer pointed to by p. Return a pointer to the packed string.

size_t _unpstrlen( const _packed char *p );
Return the length in number of characters of the packed string pointed to by p. This is the number of characters when the string would be unpacked.

size_t _packsize( const char * p );
Calculate the size of a string when it is packed.

char _pstr_get( const _packed char *p, size_t idx );
Return character at index idx in packed string p.

void _pstr_put( _packed char *p, size_t idx, char c );
Put character c on index idx in packed string p.

Printf and scanf formatters support the packed string conversion specification %S, which otherwise behaves like the normal string conversion specification %s.

3.18.2 Pragmas

Two pragmas for packed string support are available:

#pragma pack_strings After this pragma all string constants will be packed string constants. Using such a string as a not packed string (e.g., passing to a function with a not packed string argument type) will yield a type conflict error.

#pragma nopack_strings
After this pragma string constants will no longer be packed string constants.

3.18.3 Examples

Example 1:

Example 2:

The sizeof( _packed char ) is one, just like sizeof( char ). This implies that pointer arithmetic on pointers to packed strings goes per three characters.

Example 3:

3.19 Structure Tags

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

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

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

3.20 Typedef

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

Examples:

typedef _P int PINT;         /* storage type _P: OK */
typedef int _X *DATAPTR;     /* logical type _X
                                storage type 'default' */

3.21 Switch Statement

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

A jump chain is comparable with an if/else-if/else-if/else construction. A jump table is a table filled with JMP instructions for each possible switch value. The switch argument is used as an index to jump within this table.

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

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

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

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

The last one is also the default of the compiler. Using an option (or a pragma) cannot overrule the restrictions as described earlier.

See #pragma jumptable_memory in section 4.4, Pragmas.

3.22 Portable C Code

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

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

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

3.23 Efficient Use of the DSP56xxx Tool Set

The following sections give you some guidelines and hints to get the best results from the DSP56xxx tool set.

3.23.1 Char and Short Types

Avoid types smaller than int whenever possible, as they need more instructions for conversions and do not save data space. Types smaller than int are (see table 3-2):

DSP5600x signed and unsigned char and short

DSP563xx in 24-bit arithmetic mode: signed and unsigned char and short

DSP566xx signed and unsigned char

3.23.2 Unsigned

Try to use the unsigned qualifier as little as possible, because unsigned comparisons require more code than signed comparisons. When retrieving unsigned values from memory the accumulator extension word can be set incorrectly. Therefore, the extension word has to be cleared before any comparison.

3.23.3 Hardware Loops

For the DSP5600x the loop counter register LC (16 bits) is smaller than an (unsigned) int (24 bits). The compiler only optimizes to a hardware DO loop when it is absolutely sure that the loop counter fits in the loop counter register. When the start and end value of the loop iteration counter in the C code is fixed and fits within a 16-bit value this is the case. Also when an (unsigned) short is used for loop iteration counter or loop conditions, the compiler is sure the loop counter register will not overflow. Therefore, it is recommended to use (unsigned) short for loops and for loop conditions on the DSP5600x .

When using global variables inside the loop the compiler may not be able to produce a hardware DO loop that can be optimized to a REP loop. For example:

In this example the loop cannot be optimized to a REP loop because the variable output is written in each iteration. You can use an automatic temporary variable to make it possible to optimize the hardware loop to a REP loop:

To generate a hardware DO loop the compiler must recognize the variable used as loop counter in the C code. Incrementing the loop counter as a separate statement is easier to recognize by the compiler than when it is used in some expression or as an index of an array.

For example, in the following code the compiler does not recognize the loop counter i:

When rewriting the do-while loop code to:

the compiler recognizes i as the loop counter and generates a hardware loop.

In for statements containing multiple update statements, the loop count variable must be the last updated variable to allow the compiler to create a hardware loop. Example:

3.23.4 Speed vs. Size

The C compiler optimizations are by default tuned for code size. When execution speed is more important than code size you can use the -O3 or -O4 command line option to let the compiler optimize for speed. The optimization options can also be changed using the #pragma optimize.

For example:

3.23.5 Assembly Interfacing

The #pragma asm can be used to get access to specific DSP56xxx instructions or to write code that cannot be achieved using the C language. For interfacing the C language to assembly it is recommended to use a function call interface with the _asmfunc function qualifier. With #pragma asm it is unsafe to rely on the registers the compiler allocates for the variables. See section 3.12 Inline Assembly and section 3.12.4 Linking with Separate Assembly Routines for more information.

The compiler also features intrinsic functions that can be used to get access to some special DSP56xxx instructions. These functions have the advantage of being portable and they have a neat C interface. See also section 3.13 Intrinsic Functions.

When calling an assembly routine from C in the mixed or reentrant model, R7 must not be used in this function because this register is used as user stack pointer. When no more address registers are available in your assembly routine you can save the contents of R7 to some memory location on entry of the routine and restore it before returning to C. Note that interrupt functions in the reentrant model or interrupt functions declared _reentrant in the mixed model cannot be entered when R7 is not available as user stack pointer because these interrupt functions may want to save registers on the stack.

3.23.6 Selecting the Most Efficient Model

The DSP563xx compiler can generate code for 16-bit precision arithmetic in two models: the 16-bit model and the 16/24-bit model. Use the 16/24-bit model only if the code and/or data require more than 64k words of memory, because pointer arithmetic is much more efficient in the 16-bit model.

The DSP5600x compiler supports three models: Static, Mixed and Reentrant. Select the most efficient model for your application. The compiler generates the best code for the static model. The code density generated for the mixed model is close to the static model because it only cannot use R7 which is reserved for user stack pointer. The code density for the reentrant model is less than the static and mixed model. Use the static model for any function that does not need to be reentrant (i.e., not recursive and not called from (an) interrupt routine(s) and the main program simultaneously).

If you place the stack in L-memory, code becomes faster, but data memory use may slightly increase. If you select default memory to be P, the code will become very inefficient and slow; use this selection only as a last resort.

See also section 3.2.2 Memory Models.

3.23.7 Memory Mapped I/O from C

Use the unions in the supplied header file for memory mapped I/O; optimal bit field operations are generated this way. A C header file is supplied for each DSP56xxx family derivative in the include subdirectory of the installed product.

The registers defined in the header file are created by a union of a structure (field .B) and an integer (field .I). The structure defines all bit fields in the register. The integer can be used to access the register as one word. When copying the full register contents it is recommended to use this one word .I field instead of copying the whole structure. Copying one field yields more efficient code than copying it as a structure.

Example:

The best place to add your own I/O devices in the DSP56xxx memory map is in the high addresses of Y memory. You can then access these I/O devices with the same efficient instructions as the built-in I/O devices. To access them from C code you can create a header file for your I/O system that is similar to the ones provided for the DSP device.

3.23.8 Parallel Moves

For the DSP5600x and DSP563xx it is recommended to allocate data structures in Y memory whenever possible, to take advantage of parallel X/Y moves. Automatics are stored in X memory, so, this gives you a better balance.

3.23.9 Shifting Fractional Data

The DSP56xxx compilers support shifting of fractional data values. You may want to use the method below, however, because this feature is not portable to floating point calculations. You can use multiplication/division with fractional powers of 2 to implement shifting of fractional data. For example:

... Code ...

... Code ...

3.23.10 Dynamic Scaling

To scale dynamically, you can use a negative _fract scaling factor to avoid rounding errors at gain 1:

... Code ...

3.23.11 Reviewing the Optimized Code

The optimizations for a C program are partially done in the C compiler and partially in the assembler. This implies that the fully optimized result is only available after assembly. The assembler will produce a list file that shows how it rearranged the compiler generated source. The final result can also be reviewed using the disassembler option of the object reader pr563 (pr56 for DSP5600x) . For example:

or

3.23.12 Integer and Fractional Types

Converting integer to fractional types follows the rules for integer to float conversion. Due to the limited overlap between integer and fractional types there are only a few possibilities, listed in the following table. As these conversions are almost always inadvertent, a warning message is issued for them.

In combined integer and fractional operations the fractional value is converted to integer, as the integer type has the largest range.

From Value To Result
signed char, short, int, long <= -1 _fract, long _fract -1.0
> -1 0.0
unsigned char, short, int, long >= 0 _fract, long _fract 0.0
_fract, long _fract <= -1.0 signed char, short, int, long -1
> -1.0 0
_fract, long _fract < = -1.0 unsigned char, short, int, long max. value (all ones)
>= -1.0 0

Table 3-8: Type conversions

The fractional value is truncated and not rounded.

Example:

The following code shows the result of a conversion from _fract to int and vice versa:

; 1  |extern int i;
; 2  |extern _fract f;

     . . .

; 6  |     i = f;    // _fract to int

     move x:Ff,a     ; get f into accu a
     neg  a          ; negate to set flags
     move #0,a       ; result in i is 0
     jle  L3         ; if value was >= 0 result in i is 0
     jes  L3         ; or if value was > -1.0 if extension
                     ; not in use  result in i is 0
     move #>-1,a     ; else result in i is -1
L3:  move a,x:Fi     ; store i

; 8  |    f = i;     // int to _fract

     move x:Fi,b     ; get i into accu b
     tst  b          ; check value of i
     move #0,b       ; result in f is 0.0
     jge  L4         ; if i >= 0 result in f is 0.0
     move #<$80,b    ; else result in f is -1.0
L4:  move b,x:Ff     ; store f

It is obvious that when integer and fractional types are mixed frequently, the above conversions also occur frequently. Therefore, it is more efficient to use only fractional types or only integer types in expressions. The above of course also applies to the types char, short, long and long _fract.

When you do not want the conversion as described above there are three ways to achieve this:

1. Use the _int2fract and _fract2int intrinsic functions. See section 3.13 Intrinsic Functions.

2. Use a union of the fractional and integer type. Initialization with hexadecimal constants is also possible, depending on which member is mentioned first.

For example:

3. Use a pointer cast.

For example:

It is recommended to use fractional types whenever possible. The compiler can generate the most efficient code for these types because this is the type which suites the DSP56xxx family the best.

For example the compiler will use the MAC instruction whenever possible, also for integer multiplication. To be able to do this the integer must be scaled first.

When this was done using the fractional type the code would be more efficient:

3.23.13 Interrupt Routines

Avoid using function calls in interrupt routines, as this will force the compiler to stack all registers. All registers are free to use in a function, so there is no way that the compiler can limit this. Instead, an _inline function or a macro can be used for repeated code if necessary. This will cause your code to become larger, but it will also avoid call/return overhead in the interrupt function.


Copyright © 2002 Altium BV