Skip to content Skip to navigation

OpenStax-CNX

You are here: Home » Content » Using C and Linear ASM

Navigation

Recently Viewed

This feature requires Javascript to be enabled.
 

Using C and Linear ASM

Module by: Hyeokho Choi. E-mail the author

Summary: In this module you will learn basic C programming using TI's optimizing C compiler.

Introduction

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.

C-Based Codec IN/OUT Program

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.

C Programming

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.

C Data Types

You can define variables in your C program. The following table lists the various data types the C compiler can handle on C62x platform.

Table 1
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:

  1. Use 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.
  2. Use int for counters and indexes because these are 32-bit numbers and stored in a single register.
  3. Avoid accidentally mixing long and int.
  4. Pointers, 32-bits wide, can point to the entire C62x memory map.

Linker Command File For C Programs

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    }
          

Include Files and Run-Time Library

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.*/
          
Therefore, whenever you need to access the data receive register of McBSP0 port, instead of using the physical address 0x18c0000, you can replace it with McBSP0_DRR, which is easier to remember and understand. To use the definitions in this file, you only need to include the header file in your C source file before using the definitions:

1     #include “c6xdsk.h”
          
The header file 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”
          
There is another file named c6xdskinit.c containing functions to set up the McBSP0 port and AD535 codec of DSK board.

Interrupt Vector File and Writing ISR In C

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
        
Because the 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     }
        
For a function used as an ISR, you cannot have any argument ((void) above or any return values (void data type for the function. Once the function is defined using the interrupt keyword, upon calling the function, the function automatically saves all the registers used inside the function and then restores them before returning. The return from a function defined with interrupt is a branch to IRP.

Accessing Memory Mapped Registers

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;
          
The McBSP1_SPCR was defined to have the physical memory address of the register in one of the included header file. By having the pointer type cast (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;
          
to set the GIE bit of CSR for global interrupt enable.

Exercise 1: A/D, D/A in C

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.

Exercise 2: FIR Filtering in C

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.

Linear Assembly

What Is It?

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:

  1. use symbolic names,
  2. forget pipeline issues,
  3. ignore putting NOPs, parallel bars, functional units, register names,
  4. more efficiently use CPU resources than C.
The linear assembly files have .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.

C Callable Linear Assembly Procedure

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);
          
If 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     ...
          
(see below how the arguments are passed and the pointers are used.) In the following, you learn various assembler directives and how the optimized assembly code is generated by the assembler optimizer.

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
          
The .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:
          
The .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.

Exercise 3: FIR In Linear Asm

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?

Calling Assembly From C (Optional, but extra credit)

Function Name Declaration

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     ...
          
In the assembly file, you use the same function name as the label at the jump address to which the function call branches. But, all the names defined in C are used with a _ prepended. That is, we can define the function in assembly as follows.

1                     .global _myasmfunction
2     _myasmfunction: ADD	A4,B4,A4
3                     ...
4                     B 	B3
5                     NOP	5
6     ;end of subroutine
          
The function name must be declared using the .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     ...
          
the program branches to the address _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 xx and yy stored when the program jumps to the assembly function. This is also explained in Section 14.

Function Argument Passing

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 xx and yy are stored in 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 zz? The A4 register is used for the return value. Therefore, the above assembly function indeed correctly computes and returns the sum of the two arguments.

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.

Preserving Register Values

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.

Exercise 4: C Callable FIR Routine

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.

Content actions

Download module as:

Add module to:

My Favorites (?)

'My Favorites' is a special kind of lens which you can use to bookmark modules and collections. 'My Favorites' can only be seen by you, and collections saved in 'My Favorites' can remember the last module you were on. You need an account to use 'My Favorites'.

| A lens I own (?)

Definition of a lens

Lenses

A lens is a custom view of the content in the repository. You can think of it as a fancy kind of list that will let you see content through the eyes of organizations and people you trust.

What is in a lens?

Lens makers point to materials (modules and collections), creating a guide that includes their own comments and descriptive tags about the content.

Who can create a lens?

Any individual member, a community, or a respected organization.

What are tags? tag icon

Tags are descriptors added by lens makers to help label content, attaching a vocabulary that is meaningful in the context of the lens.

| External bookmarks