In this lab, you learn basic C programming using TI’s optimizing C compiler. Then, you learn how to write C-callable assembly functions and linear assembly procedures.
Summary: In this module you will learn basic C programming using TI's optimizing C compiler.
In this lab, you learn basic C programming using TI’s optimizing C compiler. Then, you learn how to write C-callable assembly functions and linear assembly procedures.
Before starting, download the lab7.zip from the course web page and extract in your directory. This program is a C version of the interrupt-based codec in/out program you wrote in (Reference). Compile and test the program before you do the rest of the module. In this module, you will be writing and modifying this program to implement FIR filtering using various methods.
You can write your programs in C and compile it using TI’s optimizing compiler. C programming has the advantage of easy programming and easier debugging compared to the assembly programming. The optimizing compiler optimizes the program to use the CPU resources as much as possible. The optimized C program provides over 70% utilization of the CPU resources with low coding effort.
You can define variables in your C program. The following table lists the various data types the C compiler can handle on C62x platform.
| Type | Size | Representation |
|---|---|---|
| char, signed char | 8 bits | ASCII |
| unsigned char | 8 bits | ASCII |
| short | 16 bits | 2's complement |
| unsigned short | 16 bits | binary |
| int, signed int | 32 bits | 2's complement |
| unsigned int | 32 bits | binary |
| long, signed long | 40 bits | 2's complement |
| unsigned long | 40 bits | binary |
| enum | 32 bits | 2's complement |
| float | 32 bits | IEEE 32-bit |
| double | 64 bits | IEEE 64-bit |
| long double | 64 bits | IEEE 64-bit |
| pointers | 32 bits | binary |
Some of the general rules in using these data types are:
short types for integer multiplication, because C62xx devices use 16 x 16 multipliers. If you use int variables, a software run-time library function will be used, which is slow. int for counters and indexes because these are 32-bit numbers and stored in a single register. long and int.
The C compiler defines several section names in the memory to store the compiled assembly code, initialized data, uninitialized data, arrays, etc. The linker command file needs to assign proper memory blocks for all of these sections. A typical linker command file for use with C6211 DSK is as follows. This is the c6xdsk.cmd you used in (Reference).
1 MEMORY
2 {
3 VECS: org= Oh, len = 0x220
4 I_HS_MEN: org = Ox0000022O, len = 0x00000020
5 IRAM: org = Ox0000024O, len = 0x0000FDC0
6 SDRAM: org = Ox80000000, len = 0x0l000000
7 FLASH: org = Ox90000000, len = 0x00020000
8 }
9
10 SECTIONS
11 {
12 /* Created in vectors.asm */
13 vectors :> VECS
14
15 /* Created by C Compiler
16 .text :> IRAM
17 .bss :> IRAM
18 coeffs :> IRAM
19 .cinit :> TRAM
20 .stack :> IRAM
21 .sysmem :> IRAM
22 .const :> IRAM
23 .switch :> IRAM
24 .far :> SDRAM
25 .cio :> IRAM
26
27 }
It is convenient to define various register names and memory addresses in a header file. The c6xdsk.h header file defines all the physical memory locations so that you don’t need to remember all the physical memory addresses of each control registers of C6211 CPU. For example, the file defines the addresses of control registers for McBSP0 port as follows:
1 /* define McBSPO registers */
2 #define McBSP0_DRR 0x18c0000 /*address of data receive reg.*/
3 #define McBSP0_DXR 0x18c0004 /*address of data transmit reg.*/
4 #define McBSP0_SPCR 0x18c0008 /*address of serial port contl. reg.*/
5 #define McBSP0_RCR 0x18c000C /*address of receive control reg.*/
6 #define McBSP0_XCR 0x18c0010 /*address of transmit control reg.*/
7 #define McBSP0_SRGR 0x18c0014 /*address of sample rate generator.*/
8 #define McBSP0_MCR 0x18c0018 /*address of multichannel reg.*/
9 #define McBSP0_RCER 0x18c001C /*address of receive channel enable.*/
10 #define McBSP0_XCER 0x18c0020 /*address of transmit channel enable.*/
11 #define McBSP0_PCR 0x18c0024 /*address of pin control reg.*/
1 #include “c6xdsk.h”
c6211interrupts.h defines various constants necessary to set up interrupts and several functions to configure interrupts. Setting up interrupts is discussed more in (Reference). To use the functions defined in c6211interrupts.h, you need to include this file at the beginning of your C source file:
1 #include “c6xinterrupts.h”
c6xdskinit.c containing functions to set up the McBSP0 port and AD535 codec of DSK board.
Even when you write C programs, the interrupt and reset vector file is still necessary. The vectors .asm used in (Reference) is as follows:
1 .title “vectors.asm”
2
3 .ref _c_int00
4
5 .sect “vectors”
6 rst: mvkl .s2 _c_int00,b0
7 mvkh .s2 _c_int00,b0
8 b .s2 bO
9 nop
10 nop
11 nop
12 nop
13 nop
main() function is the default function executed in a C program, the C compiler defines an entry point named _c_int00 at the start of the C initialization routine then the control is transferred to the compiled main() function. This is the reason why the CPU should transfer the execution to _c_int00 in the above reset vector.
Setting and enabling interrupts are done exactly same way you do in an assembly program. To write an interrupt service routine, you can use the interrupt keyword. For example, an ISR can be defined as
1 interrupt void myISR (void)
2 {
3 ....
4 }
When accessing memory mapped registers, you have to exercise special caution to let the compiler know that the corresponding C statements should not be optimized. Whenever you access any of the memory mapped registers such as SPCR of McBSP, you should use the following statement:
1 /* Reset McBSP1 */
2 *(unsigned volatile int *)McBSP1_SPCR = 0;
unsigned volatile int *), you can prevent any unwanted optimization by the compiler that may result in undesired operation.
The control registers that are not memory mapped can be accessed directly. For example, you can simply write
1 CSR |= Ox1;
Read the header file, c6xinterrupts.h and the C file c6xdskinit_pcm.h to understand various functions defined in these files. Read the main C program that implements interrupt-based codec IN/OUT function. In your lab report, briefly describe what each function defined in these files does.
Write a C function implementing an FIR filter. Combine this routine with the provided A/D-D/A loop program to implement a real-time FIR filter. Test your filter program.
TI’s linear assembly language enables you to write an assembly-like programs without worrying about register usage, pipelining, delay slots, etc. The assembler optimizer program reads the linear assembly code to figure out the algorithm, and then it produces an optimized list of assembly code to perform the operations. The linear assembly programming lets you:
.sa extensions. When you have a linear assembly file in your CCS project, the assembly optimizer is invoked automatically to generate optimized actual assembly routine. You can consider the linear assembly language as a tool to describe algorithms. To effectively convey the intent of the programmer to the assembly optimizer for proper optimization, there are quite a few extra directives in linear assembly.
The following is an example of C callable linear assembly routine that computes the dot product of two vectors. It implements a C function
1 short dotp(short* a, short* x, int count);
a[] and x[] are two length-40 vectors, the C function call has the form
1 short a[];
2 short x[];
3 short z;
4 ...
5 ...
6 z = dotp(a,x,40);
7 ...
1 _dotp: .cproc ap,xp,cnt
2 .reg a,x,prod,y
3
4 MVK 40,cnt
5 loop: .trip 40
6 LDH *ap++,a
7 LDH *ax++,x
8 MPY a,x,prod
9 ADD y,prod,y
10 SUB cnt,1,cnt
11 [cnt] B loop
12
13 .return y
14 .endproc
.cproc directive starts a C callable procedure. It must be used with .endproc to end a C procedure. _dotp: is the label used to name the procedure. By using .cproc to start the procedure, the assembly optimizer performs some operations automatically in a .cproc region in order to make the function conform to the C calling conventions and to C register usage convention. The following optional variables (ap, xp, cnt above) represent function parameters. The variable entries are very similar to parameters declared in a C function.
The arguments to the .cproc directive can be either machine-register names or symbolic names. When register names are specified, its position in the argument list must correspond to the argument passing conventions for C. For example, the first argument in C function must be register A4. When symbolic names are specified, the assembly optimizer ensures proper allocation and initialization (if necessary) of registers at the beginning of the procedure. To represent a 40-bit argument, a register pair can be specified as an argument. In this module, however, we oniy use 32bit values as arguments.
The .reg directive allows you to use descriptive names for values that will be stored in registers. It is valid only within procedures only.
The .return directive functionality is equivalent to the return statement in C code. It places the optional argument in the appropriate register for a return value as per the C calling conventions. If no argument is specified, no value is returned, similar to a void function in C code. To perform a conditional .return, you can simply put conditional branch around a .return as:
[!cc] B around
.return
around:
.trip directive specifies the value of the trip count. The trip count indicates how many times a loop will iterate. By giving this extra information to the assembler optimizer, a better optimization is achieved for loops. The label preceding .trip directive represents the beginning of the loop. This is a required parameter.
For more information on writing C callable linear assembly procedure, refer to TMS32OC6x Optimizing C Compiler User’s Guide. For C6x assembly instructions, refer to TMS32OC62x/C67x CPU and Instruction Set Reference Guide.
Write a C callable FIR filtering routine in linear assembly. When using different optimization levels, what is the number of clock cycles of each FIR filtering?
For the C program to be able to call an assembly function, the names of the function must be known to the C program. In the C program, the function is declared as an external function:
1 ...
2 int myasmfunction(int, int);
3 ...
1 .global _myasmfunction
2 _myasmfunction: ADD A4,B4,A4
3 ...
4 B B3
5 NOP 5
6 ;end of subroutine
.global directive in the assembly file to let the assembler and compiler know that this is used in another file.
Then, if you have a function call in C program like
1 ...
2 int x,y,z;
3 x = 12;
4 y = 34
5 z = myasmfunction(x,y);
6 ...
_myasmfunction and after finishing the function the asm program returns the control to the calling function by the B B3 instruction. Why B3 then? This is explained in Section 14. Another question is where are the variables
Let’s go to Code 53. When we have a C function call like z = myasmfunction(x,y);, the arguments are stored in the registers in the following order: A4, B4, A6, B6, Al0, Bl0, A12, B12. That is, in Code 53, the values of A4 and B4 respectively. And, before jumping to the assembly function, the return address is stored in B3. This explains why we branched to the address contained in B3 at the end of the assembly function. In fact, the above myasmfunction computes the sum of the two arguments. Then, where do we need to store the function return value that is eventually stored in the C variable
The reason why the arguments are stored only in the even numbered registers is that the even-odd pairs are used for storing values longer than 32 bit. For example, if the first function argument is a long integer, it is stored in the register pair A4:A5.
There is one more thing to keep in mind when you write a C callable assembly function. You cannot modify the values in A10-A15 and B10-B15. These registers are used in C to store valuable information. Once you corrupt any of these, your C program will not function properly upon returning to the C after finishing the assembly routine. For example, you can save and restore these registers in stack.
Modify the FIR filtering assembly routine you wrote in (Reference) so that it can be called from C. Combining this C callable assembly function with the provided A/D-D/A C program, implement another FIR filter program. Test the program.