1 数据类型注意:
避免设int和long有相同大小。
对定点算法(特别是乘法)尽量使用int数据类型,用long会导致调用运行时间库。
使用int和unsigned int 而不用long作为循环计数。C55x的硬件循环计数只有16位宽。
避免设char为8位,long为64位。
当所写代码用于DSP目标系统中时,应定义genetic类型。
一般来说,最好使用int类型作循环计数器和其他对位数要求不高的整型变量。
2 关键字
2.1 ioport
C55x C编译器对标准C语言进行了扩展,增加了ioport关键字来支持I/O寻址模式
ioport类型限定词可以和标准类型(数组、结构体、共用体和枚举)一起使用
可以和const及volatile一起使用。当和数组一起使用时,ioport限制的是数组单元而非数组类型本身。ioport可以单独使用,这种情况下int限定词就是默认的
ioport类型限定词只能用于全局或静态变量。局部变量不能用ioport限制,除非变量是个指针
2.2 interrupt
C55x C编译器对标准C语言进行了扩展,增加了interrupt关键字,来指定某个函数为中断函数。
2.3 onchip
Onchip关键字声明一个特殊指针,该指针所指向的数据可用作双MAC指令的操作数。在链接时这些数据必须被链接到DSP片上存储器,否则会导致总线错误。
2.4 volatile
在任何情况下,优化器会通过分析数据流来避免存储器访问。
如果程序依靠存储器访问,则必须使用volatile关键字来指明这些访问。
编译器将不会优化任何对volatile变量的引用
3 寄存器变量和参数
寄存器变量就是用register关键字声明的变量。
根据是否使用优化器,C编译器对寄存器变量采用不同的处理方式。
编译器会尽量分配好所声明的寄存器变量。
整型、浮点型和指针类型对象都可以声明为寄存器变量,而其它类型对象不行。
当使用优化器进行编译时,编译器忽略任何寄存器声明,通过一种能够最有效地使用寄存器的代价算法,把寄存器分配给变量和临时量。
当不使用优化器进行编译时,编译器将使用register关键字的变量分配到寄存器中
如果编译器运行超出了合适的寄存器,它将通过移动寄存器内容到存储器来释放寄存器。如果定义了太多的寄存器变量,则会限制编译器用来存放临时表达式结果的寄存器数目。这个限制会引起过量的从寄存器到存储器的移动动作。
4. asm指令
lasm指令可以直接将C55x汇编语言指令嵌入到编译器的汇编语言输出中,就好像是对叫做asm的函数的调用。
l指令格式:
asm (“assembler text”);
例,
asm (“nop”); 插入一条汇编指令nop
插入的代码必须是合法的汇编语言指令
像其它汇编语言指令一样,包含引用的代码行必须用标号、空格、星号、分号开头。编译器不检查字符串。如果有错,汇编器会将其检测出来
使用asm指令存在的问题:
它容易破坏C环境,因为C编译器在编译嵌入了汇编语言的C程序时并不检查或分析嵌入的汇编语句
当使用带asm指令的优化器时必须小心。虽然优化器不会移除asm指令,但它可以重新改变周围代码顺序并可能引起不可预知的结果
5.Pragma指令
Pragma指令告诉编译器的预处理器如何处理函数。
必须在函数体外确定pragma,且必须出现在任何声明、定义或对函数和符号引用之前。否则,编译器会输出警告。
6.标准ANSIC语言模式的改变
编译源代码有如下的模式:
Normal ANSI模式 (默认)
K&R C模式 (-pk)
宽松ANSI模式 (-pr)
严格ANSI模式 (-ps)
7.存储器模式
C编译器将存储器当作一个由代码子模块和数据子模块组成的线性模块。每个由C程序生成的代码子模块或数据子模块被放到各自的连续存储空间中。编译器认为目标存储器的全部24位地址都有效。
编译器支持两种存储器模型:
小存储模式和大存储器模式。
两种存储模式的数据在存储器中的放置和访问不同。
1. 小存储器模式(默认模式)
使用小存储器模式将得到比使用大存储模式时更少的代码和数据。
在小存储器模式中,在单页(64KB)存储器内的以下段必须都分配合适:
.bss和.data段(所有静态和全局数据)。
.stack和sysstack段(第一和第二系统堆栈)。
.sysmem段(动态存储空间)。
.const段。
在小存储器模式中,对.text段(代码)、.switch段(switch语句)和.cinit段(变量初始化)的大小和位置没有限制。
小存储器模式下编译器使用16位数据指针来访问数据。XARn寄存器的高7位用来设置指向包含.bss段的存储页。在程序执行过程中它们仍指向原来那些值。
2.大存储器模式
大存储器模式支持不严格的数据放置。用-ml shell选项就可以应用该模式。
在大存储器模式下,数据指针为23位,在存储器中占2字空间。.stack和.sysstack段必须在同一页上。
在大存储器模式下编译代码时,必须和rts55x.lib运行时间库链接。
链接器不允许同时存在大存储器模式和小存储器模式。应用程序中的所有文件都必须使用相同的存储器模式。
8.存储器分配
1. C编译器生成的段
C编译器生成的段有两种基本的类型,即初始化段和未初始化段
初始化段有:
.cinit段,包含初始化数据表格和常数
.pinit段,包含实时运行时调用的数据表格
.const段,包含用const定义(不能同时被volatile定义)的字符串常量和数据
.switch段,包含switch语句所用表
.text段,包含所有可执行代码
¤汇编器生成了.data段,但C编译器并不使用这个段。
未初始化段保留了存储器空间,一段程序可以在运行期间使用这个空间来生成和存储变量:
.bss段,为全局和静态变量保留了空间。在启动和装载的时候,C启动程序或装载程序从.cinit段(通常在ROM中)复制数据并用这些数据来初始化.bss段中的变量
.stack段,为C系统堆栈分配存储地址。这个存储地址用来传递变量和局部存储
.sysstack段,为第二系统堆栈分配存储地址
.sysmem段,为动态存储分配保留空间。这个空间被malloc、calloc和realloc函数调用。如果C程序不使用这些函数,编译器就不会创建.sysmem段
.cio段,支持C I/O。这个空间用来作为标签为_CIOBUF_缓冲区。当任何类型的C I/O被执行(如printf和scanf),就会建立缓冲区。缓冲区包含一个对stream I/O类型的内部C I/O命令(和需要的参数)及从C I/O命令返回的数据。.cio段必须放在链接器命令文件中才能使用C I/O
2.堆栈
在C编译器中,使用堆栈来放置局部变量、传递参数给函数、保存处理器状态,堆栈被放在存储器的一个连续块中,并从高地址到低地址存放数据。
编译器用堆栈指针(SP)来管理堆栈。
代码不会检查是否在运行时间内堆栈出现溢出,必须为堆栈分配合适的存储空间。
C55x支持第二系统堆栈,编译器使用第二堆栈指针SSP来管理第二系统堆栈。
系统堆栈和第二系统堆栈的大小都由链接器设置。链接器会生成全局符号_STACK_SIZE和_SYSSTACK_SIZE,并给它们指定一个等于各自堆栈大小的值。两种默认堆栈大小都是1000字节。在链接时间内,通过链接器命令中的-stack或-sysstack选项可以改变堆栈大小。
3.动态存储器分配
由编译器提供的运行时间支持库包含几个在运行时间内为变量动态分配存储器的函数(malloc、calloc和realloc)
存储器被从一个在.sysmen段定义的全局池(pool)或堆(heap)中分配出来。可以通过-heap size选项和链接器命令来设置.sysmem段的大小。链接器会生成一个全局符号_SYS MEM _SIZE,并为它指定等于heap字节数的值。默认大小为2000字节
动态分配的对象必须用指针寻址。为了在.bss段中保留空间,可以通过从堆中定义大数组来实现,而不是将其定义为全局或静态变量
例如,不用如下定义:
struct big table[100];
而使用指针并调用malloc函数:
struct big *table;
table=(struct big*)malloc(100*sizeof(struct big));
9.中断处理
当C环境被初始化时,启动程序禁止中断。 如果系统使用中断,必须处理有关的中断使能或屏蔽。
1.关于中断的几个要点:
中断程序会执行任何其它函数执行的工作,包括访问全局变量、为局部变量分配地址、调用其它函数。
需要处理任何特殊中断屏蔽(通过IER0寄存器)。通过嵌入汇编语言语句可以使能或禁止中断,也可以修改IER0寄存器而不会破坏C环境或C指针。
中断处理程序不能有参数,即使声明了参数也会被忽略
中断处理程序不能被普通C代码调用。
为了将中断程序和中断联系起来,需要将分支程序放在合适的中断向量中,通过.sect指令创建一个简单的分支指令表就可以实现此操作。
在汇编语言中,需要在中断程序名前加下划线,如_c_int00。
分配堆栈到偶地址。
c_int00是系统复位中断。当进入c_int00中断时,运行时间堆栈并没有被建立起来,因此不能为局部变量分配地址,也不能在运行时间堆栈中保存任何信息。
2. C中断程序的使用
通过interrupt关键字可以用C函数直接处理中断。
interrupt关键字可以和定义为返回void并不含参数的函数一起使用。中断函数体可以有局部变量,可以自由使用堆栈。
c_int00是C程序入口。这个名字被保存为系统重启中断。这个特殊的中断程序初始化系统并调用了主函数。因为没有调用者,所以c_int00不保存任何寄存器。
例,
interrupt void isr()
{
...
}
3. 保存中断入口的现场信息
中断程序所用到的所有寄存器(包括状态寄存器)都必须被保存
如果中断程序还调用其它函数,表6-6列出的所有寄存器都必须被保存
10系统初始化
在运行C程序之前必须先建立C运行环境,该工作由被称为_c_int00的C启动程序来完成,运行时间支持源程序库(rst.src)中叫做boot.asm的模块中包含了启动源程序。
为使系统开始运行,必须由复位硬件调用_c_int00函数,将_c_ int00函数和其它目标模块链接起来。当使用链接器选项-c或-cr并将rts.src作为一个链接输入文件时,这个链接过程能够自动完成。
当C程序被链接时,链接器会在可执行输出模块中给符号_c_int00设置入口点的值。
_c_int00函数执行如下工作来初始化C环境:
建立堆栈和第二系统堆栈。
通过从在 .cinit段中的初始化表中复制数据到.bss段中的变量来初始化全局变量。如果在装载的时候就初始化变量(-cr选项),装载器就会在程序运行之前执行该步骤(而不是由启动程序完成的)。
调用main函数开始执行C程序。
1. 变量的自动初始化
任何被声明预初始化的全局变量必须在C程序开始运行前被分配初始值,检索这些变量数据并用这些数据初始化变量的过程叫做自动初始化(autoinitialization)。
编译器会创建一些表,这些表含有用来初始化.cinit段中的全局和静态变量的数据。每个编译过的模块都包含这些初始化表。链接器会把它们组合到一个单一的.cinit表中。启动程序或装载器利用这个表来初始化所有的系统变量。
在标准ANSI C语言中,没有显式初始化的全局和静态变量必须在程序执行前设置为0。C55x C编译器不对任何未初始化变量进行预初始化,因此程序员必须显式初始化任何初始值为0的变量。
2. 全局构建器(Global Constructors)
所有具有构建器(constructor)的全局变量必须在运行main()函数前使它们的构建器被调用。
编译器会在.pinit段中依次建立一个全局构建器地址表。
链接器则将各输入文件中的.pinit段链接成一个单一的.pinit段,启动程序将使用这个表来运行这些构建器。
3. 初始化表(Initialization Tables)
.cinit段中的表中包含可变大小的初始化记录。每个必须被自动初始化的变量在.cinit段中都有一条记录。
第一个位域(字0)包含了初始化数据的长度(以字为单位),位14和位15为保留位。一条初始化记录长度可达213字。
第二个位域包含初始化数据要复制到的.bss段的存储器首地址。这个域为24位。
第三个位域包含8比特的标志位。位0为存储器空间指示(I/O或数据),其余位为保留位。
第四个位域(字3到n)包含复制到初始化变量的数据。
¤.cinit段必须以上述格式包含初始化表。
如果要把汇编语言模块接入C/C++程序,就不能把.cinit段用作它途。
4. 运行时间变量初始化
在运行时间自动初始化是自动初始化的默认模式。为使用这种模式,可采用链接器的-c选项。采用这种方法,.cinit段随着所有其它初始化段被装载到存储器(通常为ROM)中,全局变量在运行时间被初始化。
链接器定义了一个叫做cinit的特殊符号,用以指向存储器中初始化表的起始地址。当程序开始运行时,C启动程序从cinit指向的表中复制数据到.bss段中的特定变量中。这使得初始化数据能被存储到ROM中,并在每次程序开始执行时复制到RAM中。
¤这种方法适用于应用程序烧入在ROM中的系统。
5. 装载时间变量初始化
在装载时间自动初始化变量会减少启动时间并节省被初始化表使用的存储器,从而改善了系统性能。用-cr链接器选项可以选择这种模式。
当使用-cr选项时,链接器置位在.cinit段头的STYP_COPY位,这样装载器就不会把.cinit段装载到存储器中(.cinit段不占用存储器空间)。链接器置cinit符号为–1(通常cinit指向初始化表的起始地址),告诉启动程序存储器中没有初始化表,因此在启动时不进行初始化。
为在装载时间内实现自动初始化,装载器必须能够执行如下工作:
检查目标文件中.cinit段是否存在
保证STYP_COPY在 .cinit段头中被置位,这样就不会复制.cinit段到存储器中去
理解初始化表格式
11 TMS320C55x的C代码优化
C55x的C/C++编译器中含有一个称为优化器(optimizer)的程序模块。优化器通过执行一些操作(如简化循环,重新安排语句和表达式,把变量用寄存器实现等),可以提高C/C++程序的运行速度,减少其代码长度。
1编译器的优化选项
1.1.基本优化选项
–o0:采取的主要优化措施有:简化控制流程,把变量安排到寄存器,简化循环,忽略未用代码,简化语句和表达式,把调用函数扩展为内嵌函数等。
–o1:在–o0级优化的基础上,进一步采取局部优化措施,如:进行COPY扩展,删除未用分配,忽略局部公共表达式等。
–o2:在–o1级优化的基础上,进一步采取全局部优化措施,如:进行循环优化,删除全局公共子表达式,删除全局未用分配等。
–o3:这是最大可能的优化级别。在–o2级优化的基础上,进一步进行的主要优化措施包括:对于从未调用的函数移除其代码,对于从未使用返回值的函数删除其返回代码,把小函数代码自动嵌入到程序中(参考-oi选项),重新安排函数声明的次序等。
-oi
:当采用–o3级优化时,优化器自动嵌入被调用的小函数。只有小于size的函数才能被嵌入。
1.2.文件级(File-Level)优化选项
–o3选项使编译器进行文件级优化。可以单独使用–o3选项进行普通的文件级优化,也可以与其它选项组合起来进行更专门的优化
要想控制文件级优化,可以使用–ol选项
–ol0:告诉编译器在源程序文件中声明了一个与标准库函数同名的函数,更改相应的库函数
–ol1:告诉编译器在源程序文件中声明一个与标准库函数同名的函数
–ol2:告诉编译器在源程序文件中不声明或改变任何标准库函数。当在命令文件或环境变量中选择了–ol0或–ol1选项时,可以通过–ol2选项取消–ol0或–ol1选项
采用–o3选项时,可以使用–on选项产生一个扩展名为.nfo的优化信息文件
–on0:取消优化信息文件的作用
–on1:产生优化信息文件
–on2:产生详细的优化信息文件
1.3.程序级(Program-Level)优化选项
通过使用-pm选项和-o3选项就可以进行程序级优化。通过程序级优化,所有源文件都被编译到一个中间文件中。这个中间文件提供给编译器在编译过程中完整的程序总览。因为编译器能够访问整个程序,因此它会执行一些很少在文件级优化中应用的优化。
如果一个函数的特定参量的值不变,编译器就会用这个值替换函数中的这个参量。
如果一个函数的返回值从不使用,编译器就会删除该函数的返回代码。
如果一个函数从未被调用,编译器就会删除该函数。
要想察看编译器进行程序级优化的情况,可以使用–on2选项产生一个优化信息文件。
要想控制程序级优化,可以使用–op选项:
–op0:有被其它模块调用的函数和在其它模块中编辑的全局变量。
–op1:没有被其它模块调用的函数,有在其它模块中编辑的全局变量。
–op2:没有被其它模块调用的函数,也没有在其它模块中编辑的全局变量,为缺省值。
–op3:有被其它模块调用的函数,没有在其它模块中编辑的全局变量。
2 嵌入函数(Inline Function)
当程序调用一个嵌入函数时,会把该函数的代码插入到调用处。
嵌入函数有助于提高代码的运行效率,主要有以下两个优点:
省去了函数调用有关的操作;
优化器可以把嵌入函数代码和周围代码放在一起自由地进行优化。
但是,嵌入函数会大幅度地增加程序代码长度,适合于小函数和调用次数较少的场合。
嵌入函数有以下方法:嵌入本征函数、自动嵌入小函数、利用inline关键字嵌入函数。
1. 嵌入本征函数
C55x有很多本征函数。编译时,编译器会用有效代码取代本征函数。
无论是否使用优化器,这种嵌入操作都会自动进行。
2. 自动嵌入小函数
通过–o3选项,优化器将自动地嵌入所调用的小函数。小函数长度的上限由-oi 选项指定,即任何长度超过size的函数不能被自动嵌入。如果选择了-oi 0,则等价于取消自动嵌入。
函数大小以编译器内部的绝对单位为准进行计算,用-onx选项可以看到某函数大小。
3. 利用inline关键字嵌入函数
如果inline关键字对函数进行限定,则该函数内调用时将被嵌入到调用处,而不是采用普通的函数调用操作方式。
为使inline关键字生效,必须采用–o(–o0,–o1,–o2 或–o3)选项。–pi选项用于关闭基于inline关键字的函数嵌入。
优化C代码的主要方法
1.生成高效循环代码
通过改进C循环代码可以极大地提高代码的性能:
避免循环体内的函数调用。这使得编译器可以高效地使用硬件循环结构(repeat、localrepeat和blockrepeat)。
保持小循环代码来使编译器使用localrepeat。
分析往返计数 (trip count) 问题,使用MUST-ITERATE pragma。(The trip count is the number of times a loop iterates.)
使用-o3和-pm编译器选项。
2.高效地使用MAC硬件
C55x有专门的硬件高效执行MAC运算。一个周期中可以执行一个单乘加或一个双乘加(dual-MAC)运算。
用单乘加操作写高效的小循环
产生双乘加操作
3.使用本征(intrinsics)函数
C55x提供了一种特殊函数——本征(intrinsics)函数,使用它可以迅速优化C代码。
本征函数前有个下划线“_”,调用方法和普通函数相同。
使用本征函数可以减少编代码量和系统开销,但会降低代码的可移植性,为此可以用ETSI(European Telecommunications Standards Institute)函数代替本征函数。
对于C55x代码,gsm.h使用编译器本征函数定义了ETSI函数。
ETSI函数可以在主机或其它没有本征函数的目标系统中编译时使用。
4.对16位数使用长整型访问
在某些情况下,把16位数据作为long类型进行访问可以显著提高效率,例如将数据从一个存储器地址快速地传递到另一个存储器地址。由于32位访问也可以在单周期中出现,这样可以减少一半的数据移动时间
唯一的限制在于数据必须排在偶字边界上。如果传递的数据是2的倍数,则代码简单很多,可以用DATA ALIGN pragma来排列数据:
short x[10];
#pragma DATA_ALIGN(x,2)
5.生成高效控制代码
如果程序中“case”个数少于8,编译器在执行套用的if–then–else和switch/case结构时会生成相似的结构。因为第一种为真的情况在执行时的分支最少,所以最好将最常出现的情况写在第一个“case”后。当程序中“case”超过8时,编译器会生成一个.switch标号的段。这种情况下,仍然最好将最常执行的代码放在第一个“case”后
在可能情况下最好测试0,因为通常测试0会得到更高效的代码
12 C55x C和汇编语言混合编程
1 寄存器规则
在C环境下对特殊操作使用特殊寄存器有严格的规定,C程序中嵌入汇编程序需要遵循这些规则,所以DSP程序员必须懂得寄存器规则。
寄存器规则规定了编译器如何使用寄存器和如何在函数调用时保存数值。
寄存器规则规定在函数调用时用到的寄存器要预先保存。这个工作部分由父函数完成,没有被父函数保存而子函数又用到的由子函数保存。
2 函数结构和调用规则
2.1.父函数如何调用其它函数
(1)将所要传递到子函数的参数放入寄存器或堆栈。
如果子函数的参量用省略号声明(表示参量数量可变),则首先把最后一个显式声明的参量传到堆栈,然后再把其它参量传到堆栈。堆栈地址将作为访问其它未声明参量的索引。
最后一个显式声明的参量之前所声明的参量遵循下述规则:
编译器通常先对要传递的参量分类,然后按照类型将参量放进寄存器。编译器使用的参量有3类:
数据指针(int *,long * 等)
16位数据(char,short,int)
32位数据(long,float,double,函数指针)
双字长(32位)或少于双字长的结构体会被当成32位数据参量,并通过寄存器传递。
如果结构体长度大于32位,编译器将该结构体的地址作为一个数据指针传递。
参量按照在函数声明中的排列顺序被分配给寄存器。参量放置的寄存器类型由参数的类型决定。如果参量的数量超过可用寄存器数量,多余的参量会被压入堆栈。
对于放入堆栈的参量按照下述方法处理:首先将堆栈调整到偶边界上;然后,每一个参数按照相应的参数类型被排列在堆栈上。
(2)子函数保存所有的入口保存寄存器(T2、T3、AR5~AR7)。父函数必须通过压入堆栈来保存其它在调用后会用到寄存器的值。
(3)父函数对子函数进行调用。
(4)父函数收集返回值。
短数据、长数据和数据指针分别返回在T0、AC0和(X)AR0中。如果子函数返回的是一个结构,则父函数在本地堆栈中为结构体分配相应大小的空间。
2.2.被调用函数(子函数)的响应
(1)被调用函数为局部变量、临时存储空间及函数可能调用的参数分配足够的存储空间。这些工作在函数调用开始的时候就完成了。
(2)如果子函数修改一些入口保存寄存器(T2、T3、AR5~AR7),必须将这些值压入堆栈或存储到一个没用的寄存器中。被调用函数可以修改其它的寄存器而不用保存其中的值。
(3)如果子函数的参数是一个结构体,则它所接收到的是一个指向该结构体的指针。如果在被调用函数中需要对结构体进行写操作,则需要把这个结构体复制到本地空间中。如果不进行写操作,则可以直接通过指针访问这个结构。
(4)子函数执行代码。
(5)如果子函数返回一个值,它将该值按照以下规则放置:
短整型数据值返回到T0中
长整型数据值返回到AC0中
数据指针值返回到(X)AR0中
如果子函数返回结构体,父函数就会为结构体分配存储空间并传送指向这个空间的指针到(X)AR0中。要返回这个结构体,被调用函数只要将该结构体复制到被这个指针所指的存储模块中。
(6)子函数恢复所有在第2步中保存的寄存器。
(7)子函数恢复存储在堆栈中的值到原始位置。
(8)函数返回。
3 C和汇编语言的接口
混合使用C代码和汇编语言代码的主要方法有:
使用几个独立的汇编代码模块,并将它们与编译了的C模块进行链接,这是最通用的方法。
在C源代码中使用汇编语言变量和常数。
将汇编语言程序直接嵌入C源代码中。
在C源代码中使用本征函数直接调用汇编语言语句。