1、4.14.24.34.44.54.6TMS320C6000系列系列DSP的的C/C+语言特点语言特点TMS320C6000系列系列DSP的的C/C+语言关键字语言关键字pragma伪指令伪指令初始化静态变量和全局变量初始化静态变量和全局变量TMS320C6000系列系列DSP的的C/C+代码优化代码优化C/C+语言和汇编语言的混合编程语言和汇编语言的混合编程4.1.1 TMS320C6000系列系列DSP的的C语言特点语言特点1标识符和常量标识符和常量l标识符的所有字符都是有意义的并且区分大小写,此特征适用于内部和外部的所有标识符;l源(主机)和执行(目标)字符集为ASCII码,不存在多字节字
2、符;l字符常量或者字符串常量中的十六进制或者八进制转义序列或者字符串常量具有高达32位的值;l具有多个字符的字符常量按序列中的最后一个字符编码,例如:abc=c。2数据类型数据类型表4-1列出了TMS320C6000编译器中各种标量数据类型、位数表示方式及取值范围,许多取值范围的值可以作为头文件limits.h中的标准宏使用。类 型位 数表示方式取值范围最小值最大值char,signed charunsigned charshortunsigned shortint,signed intunsigned intlong,signed longunsigned longenumfloatdoub
3、lelong doublepointers,references,pointer to data members8 bits8 bits16 bits16 bits32 bits32 bits40 bits40 bits32 bits32 bits64 bits64 bits32 bitsASCIIASCII2s complementBinary2s complementBinary2s complementBinary2s complementIEEE 32-bitIEEE 64-biiIEEE 64-bitBinary-1280-32 7680-2 147 483 6480-549 755
4、 813 8880-2 147 483 6481.175 494e-382.22 507 385e-3082.22 507 385e-308012725532 76765 5352 147 483 6474 294 967 295549 755 813 8871 099 511 627 7752 147 483 6473.40 282 346e+381.79 769 313e+3081.79 769 313e+3080 xFFFFFFFF3数据转换数据转换(1)浮点类型到整型的转换,截取0前面的整数部分。(2)指针类型和整数类型之间可以自由转换。4表达式表达式(1)当两个带符号的整数相除时,如
5、果其中有一个为负,则商为负,余数的符号与分子的符号相同。斜杠(/)用来求商,百分号(%)用来求余数。例如:10/-3=-3,-10/3=-310%-3=1,-10%3=-1(2)有符号数的右移为算术移位,即保留符号。5声明声明(1)寄存器存储类对所有的charsshortinteger和pointer类型有效。(2)结构体成员被打包为字。(3)整数类型的位段带有符号,位段被打包为从高位开始的字,并且不能超越字的边界。(4)中断关键字interrupt只能用于没有参数的void型函数。6预处理器预处理器预处理器忽略任何不支持的#pragma伪指令。TMS320C6000系列DSP编译器支持ISO
6、标准的C+语言,但与标准的C+又存在不同的特点:(1)并不包括完整的C+标准库支持,但是包括C子集和基本的语言支持。(2)支持C的库工具(C library facilities)的头文件不包括:,。(3)所包括的C+标准库头文件为和。(4)对bad_cast和bad_type_id的支持并不包括在typeinfo文件中。(5)不支持异常事件的处理。(6)默认情况下,禁止运行时类型的信息(RTTI)。RTTI允许在运行时确定各种类型的对象。它可以使用-rtti编译选项来使能。(7)如果两个类不相关,reinterpret_cast类型指向其中一个类成员的指针,不允许这个指针再指向另一个类的成员
7、。(8)不支持标准中tesp.res和temp.dep里描述的“在模板中绑定的二相名”。(9)不能实现模板参数。(10)不能实现模板的export关键字。(11)用typedef定义的函数类型不包括成员函数cv-qualifiers。(12)类成员模板的部分说明不能放在类定义的外部。1const关键字关键字(1)如果在一个对象定义的同时也指定了关键字volatile(如:volatile const int x),volatile关键字被分配到RAM(程序不会修改一个const volatile 的对象,但是程序外部的对象可能会被修改);(2)对象是auto存储类型(在堆栈中分配)。在以上的两
8、种情况下,为对象分配存储空间与不使用const关键字时是相同的。在一个定义中使用const关键字很重要,例如,下面代码的第一句定义了常量指针p为一个整型的变量,第二句定义了一个变量指针q为一个整型常量:int*const p&x;const int*q&x;使用const关键字,用户可以定义大常量表并将他们分配到系统ROM中。例如,分配一个ROM表,可以使用如下的定义:far const int digits=0,1,2,3,4,5,6,7,8,9;2cregister关键字关键字当对一个对象使用cregister关键字时,编译器将比较对象名和TMS320C6000的标准控制寄存器列表,如果名
9、字匹配,编译器将参照控制寄存器产生相应的代码。如果不匹配,编译器将产生一个错误。控制寄存器列表见表4-2。寄 存 器描 述寄 存 器描 述AMRCSRFADCRFAUCRFMCRGFPGFRICR寻址模式寄存器控制状态寄存器(仅C6700)浮点加法器配置寄存器(仅C6700)浮点辅助配置寄存器(仅C6700)浮点乘法器配置寄存器(仅C6400)Galois域多项式产生函数寄存器中断清除寄存器IERIFRIRPISRISTPNRP中断使能寄存器中断标记寄存器中断返回指针中断设置寄存器中断服务表指针不可屏蔽中断返回指针一旦声明该寄存器,用户就能够直接使用该寄存器名。下例为控制寄存器的声明和使用:【
10、例4.1】定义和使用控制寄存器。extern cregister volatile unsigned int AMR;extern cregister volatile unsigned int CSR;extern cregister volatile unsigned int IFR;extern cregister volatile unsigned int ISR;extern cregister volatile unsigned int ICR;extern cregister volatile unsigned int IER;extern cregister volatile
11、unsigned int FADCR;extern cregister volatile unsigned int FAUCR;extern cregister volatile unsigned int FMCR;main()printf(”AMR=%xn”,AMR);3interrupt关键字关键字当用户将interrupt关键字使用到函数的定义上时,编译器会按照中断函数要求的寄存器保存规则和中断返回的特殊顺序去保存寄存器,然后生成特殊的返回代码序列。用户可以将interrupt关键字和定义为void但没有参数的函数一起使用。中断函数体可以具有局部变量和自由的使用堆栈或者全局变量。如:in
12、terrupt void int_handler()unsighed int flags;.4near和和far关键字关键字语法上,near和far关键字被看做存储类别的变址数。它们出现在存储类别说明符和类型的前、后和中间。这两个存储器类别的变址数不能用于一个定义中。正确的使用实例代码如下:far static int x;static near int x;static int far x;far int foo();static far int foo();5restrict关键字关键字【例4.2】对指针使用关键字restrict。void func1(int*restrict a,int
13、*restrict b)/*此处为函数func1()的代码*/该例代码中关键字restrict的使用告诉编译器func1中的指针a和b指向的存储器范围不会交迭,即指针变量a和b对存储器的访问不会冲突,对一个指针变量的写操作不会影响另一个指针变量的读操作。【例4.3】对数组使用关键字restrict。void func2(int crestrict,int drestrict)int i;for(i=0;i 64;i+)/计算数组的累加和以及数组di的加1操作 ci+=di;di+=1;6volatile关键字关键字优化器分析数据流,尽可能地避免存储器的访问。如果用户将依赖于存储器访问的代码写在
14、C/C+程序中,则必须使用volatile关键字以识别这种访问。编译器不会优化任何对volatile变量的引用。下面的代码中,循环等待一个读为oxFF的单元:unsigned int*ctrl;while(*ctrl!=oxFF);该代码中,*ctrl是一个循环不变的表达式,因此该循环被优化为一个单存储器读。为了改正这些优化,可以定义*ctrl为:volatile unsigned int*ctrl7asm语句语句TMS320C6000的C/C+编译器可以将TMS320C6000汇编指令或者伪指令直接嵌入编译器输出的汇编语言文件。该功能是对C/C+语言的扩展,即asm语句。asm语句提供了C/
15、C+语言所不能提供的对硬件的访问。asm语句类似于调用一个名为asm的函数,该语句以一个字符串常数为参数,具体语法格式:asm(“assembler text”);编译器将参数直接复制到编译器的输出文件,汇编正文必须包含在双引号内。所有通常的字符串都保持它们原来的定义。例如,可插入一个包含引号的.string伪指令:asm(“str:.string”abc“”);pragma伪指令告诉编译器如何处理特定的函数、对象或者代码段。TMS320C6000的C/C+编译器支持下面的伪指令:CODE_SECTIONDATA_ALIGNDATA_MEM_BANKDATA_SECTIONFUNC_CANNO
16、T_INLINEFUNC_EXT_CALLEDFUNC_INTERRUPT_THRESHOLDFUNC_IS_PUREFUNC_IS_SYSTEMFUNC_NEVER_RETURNSFUNC_NO_GLOBAL_ASGFUNC_NO_IND_ASGINTERRUPTMUST_ITERATENMI_INTERRUPTPROB_ITERATESTRUCT_ALIGNUNROLL1CODE_SECTION指令指令CODE_SECTION指令用于为命名段中的符号指定空间。该指令在C语言中的语法格式为:#pragma CODE_SECTION(symbol,”section name”);该指令在C+语
17、言中的语法格式为:#pragma CODE_SECTION(”section name”);【例4.4】CODE_SECTION指令使用。C源文件:#pragma CODE_SECTION(fn,”my_sect”)int fn(int x)return x;此例使用#pragma CODE_SECTION(fn,my_sect),产生my_sect段,并把fn函数指定到my_sect段。汇编源文件:.sect”my_sect”.global _fn;*;*FUNCTION NAME:_fn*;*;*Regs Modified:SP*;*Regs Used:A4,B3,SP*;*Local F
18、rame Size:0 Args+4 Auto+0 Save=4 byte*;*_fn:;*RET.S2 B3;|6|SUB.D2 SP,8,SP;|4|STW.D2T1 A4,*+SP(4);|4|ADD.S2 8,SP,SP;|6|NOP 2;BRANCH OCCURS;|6|2DATA_SECTION指令指令DATA_SECTION指令为命名的段中符号指定空间。该指令在C语言中的语法格式为:#pragma DATA_SECTION(symbol,“section name”);该指令在C+语言中的语法格式为:#pragma DATA_SECTION(“section name”);【例4
19、.5】DATA_SECTION指令的使用。C源文件:#pragma DATA_SECTION(bufferB,”my_sect”)char bufferA512;char bufferB512;C+源文件:char bufferA512;#pragma DATA_SECTION(”my_sect”)char bufferB512;汇编源文件:.global _bufferA.bss _bufferA,512,4.global _bufferB_bufferB:.usect”my_sect”,512,43DATA_ALIGN指令指令DATA_ALIGN指令把符号对齐到边界。对齐的边界是符号默认的
20、最大界值或常量,常量是2的整数次幂。该指令在C语言中的语法如下:#pragma DATA_ALIGN(symbol,constant);该指令在C+中的语法如下:#pragma DATA_ALIGN(constant);4FUNC_CANNOT_INLINE指令指令FUNC_CANNOT_INLINE指令通知编译器,该命名的函数不能扩展为直接插入。任何使用pragma命令的函数会忽略由其他方式指定的直接插入。该指令必须出现在对函数的任何声明和引用之前。该指令在C语言中的语法格式为:#pragma FUNC_CANNOT_INLINE(func);该指令在C+语言中的语法格式为:#pragma
21、FUNC_CANNOT_INLINE;5FUNC_EXT_CALLED指令指令FUNC_EXT_CALLED指令指定优化器保持这些C/C+函数或任何由这些C/C+函数调用的函数。这些函数充当C/C+的入口点。该指令必须出现在对函数的任何声明和引用之前。在C中,FUNC_EXT_CALLED指令的语法格式如下:#pragma FUNC_EXT_CALLED(func);在C+中,FUNC_EXT_CALLED指令的语法格式如下:#pragma FUNC_EXT_CALLED;6FUNC_IS_PURE指令指令FUNC_IS_PURE指令通知优化器,该指令命名的函数没有负面效果,允许优化做以下的工
22、作:(1)如果函数的值不需要的话,删除对函数的调用。(2)删除重复的函数。该指令必须出现在对函数的任何声明和应用之前。在C中,该指令的语法格式为:#pragma FUNC_IS_PURE(func);在C+中,该指令的语法格式为:#pragma FUNC_IS_PURE;7FUNC_IS_SYSTEM指令指令例如,它可以对多函数所使用的寄存器做出假定。不能在已经修改过的ISO函数中使用该指令。该指令必须出现在对函数的任何声明和引用之前。在C语言中,该指令的语法格式为:#pragma FUNC_IS_SYSTEM(func);在C+语言中,该指令的语法格式为:#pragma FUNC_IS_SY
23、STEM;8FUNC_NEVER_RETURNS指令指令FUNC_NEVER_RETURNS指令通知优化器,在所有的情况下,函数不会返回到它的调用处。例如,一个无限循环的函数调用exit(),将不会返回到调用处。当一个函数被该指令标记后,编译器不会产生一个函数的结束。该指令必须出现在对函数的任何声明和引用之前。在C语言中,该指令的语法格式为:#pragma FUNC_NEVER_RETURNS(func);在C+语言中,该指令的语法格式为:#pragma FUNC_NEVER_RETURNS;9FUNC_NO_GLOBAL_ASG指令指令FUNC_NO_GLOBAL_ASG指令通知优化器,该函
24、数不会给已经定义的全局变量赋值并且不会包含任何asm语句。该指令必须出现在对函数的任何声明和引用之前。在C中该指令的语法格式如下:#pragma FUNC_NO_GLOBAL_ASG(func);在C中该指令的语法格式如下:#pragma FUNC_NO_GLOBAL_ASG;10FUNC_NO_IND_ASG指令指令FUNC_NO_IND_ASG指令通知优化器函数不会通过指针进行赋值且不包含任何asm语句。该指令必须出现在对函数的任何声明和引用之前。在C语言中,该指令的语法格式如下:#pragma FUNC_NO_IND_ASG(func);在C+语言中,该指令的语法格式如下:#pragma
25、 FUNC_NO_IND_ASG;11INTERRUPT指令指令INTERRUPT指令允许用户直接在C代码中处理中断。在C语言中,该指令的语法格式如下:#pragma INTERRUPT(func);在C+语言中,该指令的语法格式如下:#pragma INTERRUPT;如果加载器不预初始化变量,则可以使用连接器在目标文件中将变量预初始化为0。例如,在链接命令文件中,在.bss段中填充0值,代码如下:SECTIONS.bss:fill=0 x00;带有常数类型限定词const的静态和全局变量的处理方法与其他类型的静态变量和全局变量不同。没有明确初始化const的静态和全局变量与其他静态和全局变
26、量是类似的,因为它们没有被预初始化为0,例如:const int zero;/*不一定初始化为0*/然而,由于常量是在名为.const的段中进行声明和初始化的,因此常数、全局和静态变量的初始化是不同的,例如:const int zero=0;/*保证初始化为0*/对应于.const段的入口:.sect .const_zero.word 04.5.1 C/C+代码的编写代码的编写1数据类型数据类型当编写C代码时,需要对数据类型的尺寸仔细的考虑。TMS320C6000编译器的每种数据类型尺寸如下(包括有符号和无符号类型):(1)char(字符型):bit(2)short(短整型):16bit(3)
27、int(整型):32bit(4)long(长整型):40bit(5)float(浮点型):32bit(6)double(双精度型):64bit基于每种数据类型的尺寸,在编写C代码时应遵循以下的规则:(1)避免在代码中将int和long类型作为相同的尺寸来处理,因为TMS320C6000编译器对long类型的数据使用40位操作。(2)对于定点乘法输入,应尽可能使用short类型的数据,因为该数据类型为TMS320C6000的16位乘法器提供最有效的使用。(3)对循环计数器使用int或者unsigned int数据类型,而不使用short或者unsigned short类型,避免不必要的符号扩展指
28、令。2分析分析C代码的性能代码的性能使用以下手段可以分析特定代码段的性能:(1)代码性能的主要衡量方法之一是代码运行所占用的时间。使用C语言中clock()和printf()函数具有计时和显示特定代码的功能,为了达到这一目的,利用独立的软件模拟器运行这段代码。(2)利用动态调试器(debugger)中的profile模式,可以得到一个关于代码中特定代码段执行情况的统计表。(3)使用动态调试器中的中断clk寄存器和RUNB命令可以跟踪特定代码段所占用的CPU时钟周期数。(4)在代码中影响性能的主要代码段通常是循环。优化一个循环,最容易的方法是抽出此循环,使之成为一个单独的可重新编写编译和运行的文
29、件。编译工具包括一个外壳程序(cl6x),用于编译汇编优化汇编和程序连接。要激活编译外壳程序,输入如下:cl6x options filenames z linker options object files(1)如果编译器不能确定两条指令是否独立,假设它们相关并且顺序安排这两条指令。(2)如果编译器能确定两条指令是独立的,将安排它们并行执行。通常编译器很难确定访问存储器的指令是否独立,以下的方法可以帮助编译器确定指令是否独立。(3)使用关键字const标识变量的存储单元不会被函数改变。const表示一个变量或者变量的存储单元保持不变。尽可能使用const是编写代码的较好方法,因为它使用简单且
30、可以提高代码的性能。(4)联合使用-pm选项和-o3选项可确定程序级优化,所有的源文件都被编译成称为模块的中间文件。由于编译器访问到整个程序,因此它可以执行几个在文件级优化中很少用的优化手段。(5)使用-mt选项是向编译器说明,在代码中不存在存储器相关性,即允许编译器在无存储器相关性的假设下改进优化。下面通过举例说明存储器相关性的概念。例4.6给出了基本矢量和C代码,图4-1给出了其相关图。1使用内嵌函数使用内嵌函数TMS320C6000提供的内嵌函数是一种直接映射为内嵌TMS320C6000汇编指令的特殊函数,可以快速优化C/C+代码。内嵌函数用下划线(_)开头,使用方法同调用普通函数一样。
31、【例4.7】没有内嵌函数的饱和加法。int sadd(int a,int b)int result;result=a+b;if(a b)&0 x80000000)=0)if(result a)&0 x80000000)result=(a 0)?0 x80000000:0 x7fffffff;return(result);【例4.8】使用内嵌函数的饱和加法。result=_sadd(a,b);2软件流水软件流水软件用于安排循环指令,使循环的多次迭代以并行方式执行。当使用编译器的-o2和-o3选项时,编译器使用软件流水优化源代码并且从程序中收集相关的优化信息。图4-2为循环的软件流水示意图。(1)
32、循环计数下例分别为优化前后的代码:原始代码:for(i=0;i N;i+)/*i=循环计数变量,N=循环次数*/优化后代码:for(i=N;i!=0;i)/*逆序计数*/(2)消除冗余循环有时编译器不能够确定循环是否总是执行大于最小循环次数,因此,编译器将会产出两种版本的循环:如果循环计数值小于最小循环次数,则执行非软件流水循环版本;如果循环计数值等于或大于最小循环次数,则执行软件流水的版本。激活编译器时可以使用以下的选项将循环次数传给编译器:l用-o3和-pm选项允许优化器访问整个程序或者部分,了解循环次数信息;l使用-nassert内核防止冗余循环的产生,或者允许编译器(使用或者未使用-m
33、s选项)软件流水最内层循环以减少代码量。(3)循环展开有3种循环展开的方式:编译器可以自动进行循环展开;可以通过UNROLL伪指令建议编译器进行循环展开;可以自己循环展开C/C+代码。【例4.9】三个存储器操作的矢量加运算。void vecsum2(short*restrict sum,const short*restrict in1,const short*restrictin2,unsigned int N)int i;for(i=0;i N;i+)sumi=in1i+in2i;/循环计算in1i 与 in2i的矢量和【例4.10】字对齐矢量和。void vecsum4(short*res
34、trict sum,const short*restrict in1,const short*restrict in2,unsigned int N)int i;#pragma MUST_ITERATE(10);for(i=0;i (N/2);i+)_amem4(&sumi)=_add2(_amem4_const(&in1),_amem4_const(&in2i);(4)投机执行(-mh选项)在循环嵌套中,只对内层的循环进行软件流水。软件流水的限制如下:l软件流水可以包含内核函数,不能包含函数的调用;l循环中不能有条件中断;l循环中不能有递增循环计数器;l循环体内不能修改循环计数器,因为在循环
35、中修改循环计数器不能将其转换为递减计数的循环;l循环代码量不能过大,因为代码量过大,需要的寄存器超过TMS320C6000的规定个数不能进行软件流水;l寄存器的值生命周期不能太长,否则不能进行软件流水;l循环中不能有复杂的条件代码,如果过于复杂则循环不能进行软件流水。4.6.1 在在C/C+代码中调用汇编语言模块代码中调用汇编语言模块C/C+代码可以访问定义在汇编语言中的变量和调用函数,并且汇编代码可以访问C/C+的变量和调用C/C+的函数。汇编语言和C/C+语言接口需遵循如下的规则:l所有的函数,无论是使用C/C+语言编写还是汇编语言编写,都必须遵循寄存器的规定。l必须保存寄存器A10A15
36、、B3和B10B15,同时还要保存A3。如果使用常规的堆栈,则不需要明确保存堆栈。换句话说,只要任何被压入堆栈的值在函数返回之前被弹回,汇编函数就可以自由使用堆栈。任何其他寄存器都可以自由使用而无需首先保存它们。l中断程序必须保存它们使用的所有寄存器。l当从汇编语言中调用一个C/C+函数时,第一个参数必须保存到指定的寄存器,其他的参数置于堆栈中。记住,只有A10A15和B10B15被编译器保存。C/C+函数能修改任何其他寄存器的内容。l函数必须根据C/C+的声明返回正确的值。整型和32位的浮点值返回到A4中。双精度、长双精度、长整型返回到A5:A4中。结构体的返回是将它们复制到A3的地址。l除
37、了全局变量的自动初始化外,汇编模块不能使用.cinit段。在C/C+启动程序假定.cinit段完全由初始化表组成。将其他的信息放入.cinit中将破坏表,并产生不可预料的结果。l编译器将连接名分配到所有的扩展对象。因此,当编写汇编代码时,必须使用编译器分配的相同的连接名。l任何在汇编语言中定义的在C/C+语言中访问或者调用的对象或者函数,都必须以.def或者.global伪指令声明。这样可以将符号定义为外部符号并允许连接器对它识别引用。【例4.11】C/C+语言调用汇编函数。C程序:extern”C”extern int asmfunc(int a);/*声明外部函数*/int gvar=4;
38、/*定义全局变量*/void main()int i=5;i=asmfunc(i);/*调用函数*/汇编程序:.global _asmfunc.global _gvar_asmfunc:LDW*+b14(_gvar),A3NOP 4ADD a3,a4,a3STW a3,*b14(_gvar)MV a3,a4B b3NOP 5TMS320C6000编译器识别若干的内嵌操作。内嵌函数可以表达C/C+中较难处理且不易表达的汇编语句的含义。内嵌操作的使用类似于函数;可以像普通函数一样使用C/C+变量。内嵌操作以下划线开头,访问的方式类似于函数。例如:int x1,x2,y;y=_sadd(x1,x2)
39、;其参数为一个字符串常量。语法格式为:asm(“汇编正文”);编译器将参数串直接复制到编译器的输出文件,汇编正文必须包含在双引号内。所有的字符串都保持它们原来的定义。使用asm语句需要注意以下的事项:l特别小心不要破坏C/C+环境,编译器不会对插入的指令进行检查。l避免在C/C+代码中插入跳转或者标号,因为这样可能会对插入代码或周围的变量产生不可预测的后果。l当使用汇编语句时不要改变C/C+代码变量的值,因为编译器不检查此类语句。l不要用asm语句插入到改变汇编环境的汇编伪指令中。l避免在C代码中创建汇编宏指令和用-g选项编译。C环境调试信息和汇编宏扩展并不兼容。1访问汇编语言的全局变量访问汇
40、编语言的全局变量在.bss段或者以.usect命名的段访问未初始化的变量很直接:(1)使用.bss或者.usect伪指令定义变量;(2)当使用.usect时,变量定义在一个非.bss的段内,必须在C中声明为far;(3)使用.def或者.global伪指令定义为外部变量;(4)在汇编语言中名字之前以下划线开头;(5)在C/C+中,将变量声明为外部的,然后正常访问。【例4.12】C中访问汇编语言。C程序:extern int var1;/*外部变量*/far extern int var2;/*外部变量*/var1=1;/*使用变量*/var2=1;/*使用变量*/汇编语言程序:.bss _var1,4,4;定义变量.global var1;声明为外部变量_var2 .usect ”mysect”,4,4;定义变量.global _var2;声明为外部变量