TMS320C54x C编译器的使用
1 前言 TMS320C54x 是 TI 公司针对通信应用推出的中高档 16 位定点 DSP 系列器件。该系列器件功能强大、灵活,较之 TMS320C2xx,具有以下突出优点:(1)速度更快:40~100MIPS;(2)指令集更为丰富;(3)更多的寻址方式选择;(4)两个 40 位的累加器;(5)硬件堆栈指针;
(6)支持块重复和环型缓冲区管理。 TMS320C54x 的应用一般较为复杂,因此,完全采用汇编语言进行编程将是一件很困难、也很低效的事情。而 C 语言以其灵活性、易移植性,已成为单片机开发的一种趋势,对于更为复杂的 DSP 应用来说,更是如此。 TI 公司为 TMS320C54x 提供了功能强大的 C 编译器,支持优化功能。下面就对使用 TMS320C54x C 编译器中的一些体会介绍给读者。作者在实践中使用的 TMS320C54x C 编译器的版本为 1.10 版,以下的讨论都基于该版本。
2 C 语言的移植在进行复杂算法开发时,一般的做法是先在 PC 上用高级语言(如 C 语言)进行仿真,然后再移植到 DSP 平台中。考虑到效率问题,可进一步进行手工汇编的调整。编译器的工作方式大致可分为两类:一类直接由高级语言产生目标代码;另一类则是先生成中间的汇编代码,再汇编成目标代码。TI 公司提供的 C 编译器属于后者。这样,用户可方便地根据中间的汇编代码进行手工调整和改写。 在进行 C 语言移植时,涉及到两个问题:一是库函数的使用,二是字长问题。
TI 的 TMS320C54x C 编译器提供符合 ANSI C 的函数库。至于字长,要取决于硬件机器字长和操作系统,但一般来说,在不同的系统中,short 型为 16 位,long 型为 32 位,而 int 型却有所不同。如,在 VC 环境中,int 型为 32 位,而对于 TMS320C54x C 编译器来说,int 型则为 16 位。因此,在将 C 源程序由仿真环境向 TMS320C54x 平台移植时,应根据各变量的取值范围,尽可能地将 int 型归类为 short 型或 long 型。
3 段的分配虽然,C 语言是一种相对高效的高级语言,并且 TI 提供的 C 编译器还结合硬件特点支持三级优化功能,但生成的汇编代码效率仍可能会不尽人意。如作者预使用环型缓冲区管理功能,这就要求该缓冲区应被定位到相对特定的位置。因此,用户对 C 编译器究竟是如何进行存储分配的,应有一定的了解。 目标码是以段为单位组织的。至于 TMS320C54x 汇编器是如何具体分配段,而连接器又是如何进行连接的,这些并非本文的要旨,详细内容请参见关于 TMS320C54x 汇编/连接器的使用一书。
这里,仅结合 C 语言的特点,简单介绍一下几种常用段的使用。 总的来说,可将所有的段分为两类:已初始化段和未初始化段。 已初始化段中包含数据表和可执行代码,常用的有 3 个:.text 段、.cinit 段和.const 段。其中,.text 段中包含所有可执行的代码以及常量;.cinit 段中包含未用 const 声明的外部(external)或静态(static)数据表;.const 段中则包含已用 const 声明的外部或静态数据表以及字符串常量。
未初始化段在存储器(通常为 RAM)中保留空间,用于程序运行时创建和存储变量,常用的有两个:.bss 段和.stack 段。其中,.bss 段用于为全局和静态变量保留空间,在程序开始执行时,由 C 引导程序将.cinit 段中的已初始化数据复制到.bss 段中。.stack 段用作 C 的系统堆栈,向被调函数传递参数,并为局部变量分配空间。 在上述五个常用段中,.text 段和.cinit 段被固定连接至程序空间,存储器类型可以是 ROM 或 RAM(一般为 ROM,具体取决于编译时 RAM 或 ROM 方式的选择);.bss 段和.stack 段则被固定连接至数据空间,存储器类型只能是 RAM。
而.const 段的使用则较为灵活。.const 段被固定连接至数据空间,但存储器类型可以是 ROM 或 RAM。这就有别于.cinit 段:.cinit 段被连接至程序空间,程序执行时,再被复制到数据空间中的.bss 段中。这样,一张未用 const 声明的数据表要同时占用程序(.cinit 段)和数据空间(.bss 段)的一部分。与之相比较,如果系统支持数据 ROM,则该数据表改用 const 声明后,只需占用数据空间(.const 段)的一部分。
如果程序较为复杂,由 C 编译器生成的.bss 段会比较庞大,这种情况下对.bss 段进行特定操作就比较困难。如将几个常用的数据表连接至片内 RAM,而非片外 RAM;或需对某个数据表进行特殊寻址。这时,即使目标系统不支持数据 ROM,也有必要使用.const 段。这就需要在连接时对.const 段作如下规定:在程序空间中加载,而在数据空间中运行。C 引导程序对其处理的方法类似与.cinit 段,将其从程序空间复制到数据空间。
具体实现,请参见 TMS320C54x C 编译器的用户指南。但作者在使用 1.10 版的 TMS320C54x C 编译器时发现,该版本的引导程序并不支持.const 段的加载。通过阅读 C 编译器所带的库函数的源代码,参照引导程序对.cinit 段的处理,作者修改了该段引导程序,使之支持对.const 段的加载。 除上述五个常见段外,TMS320C54x C 编译器还可能产生两个段:.switch 段和.sysmem 段。
其中,.switch 段包含用于开关(switch)语句的数据表;而.sysmem 段则用于动态存储分配。如果直接使用了汇编,还可能用到.data 段以及自命名的已初始化(代码)段和未初始化(变量)段。4 C 与汇编语言的混用在 C 语言中使用汇编语言,可采取两种方式:一是调用汇编语言子程序;二是使用嵌入汇编。
4.1 调用汇编语言子程序在 TMS320C54x C 程序中调用汇编语言子程序,必须遵循其函数调用规则和寄存器使用规则。 TMS320C54x C 编译器在处理函数调用时,关于如何传递入口参数以及如何返回出口参数制定了一套严格的规则。详细内容请参见文献[2]。 TMS320C54x 共有 8 个通用寄存器(AR0~AR7),其中 AR1、AR6 和 AR7 可能会被编译器所使用,其中 AR1 一般用作局部变量暂存,而 AR7 常被用作全局变量。
所以,如果所调用的汇编语言子程序中使用了这 3 个寄存器,应作上下文保护。 TMS320C54x C 编译器支持寄存器变量的使用,最多两个,分别为:AR1 和 AR6。通常的用法是创建全局寄存器变量。较之一般存储单元,C54x 对寄存器的存取速度最快。因此,对一些使用频率很高的变量来说,将其声明为寄存器变量,可显著提高处理速度。 如果使用了全局寄存器变量,应采用-r 选项对所有代码(包括库函数)进行重新编译,以禁止 C 编译器对 AR1 或 AR6 的使用。
4.2 嵌入汇编虽然 TMS320C54x C 编译器提供了三级优化功能,但作者实际使用的体会仍不够满意。而在 C 中调用汇编语言子程序,又要求严格遵循其参数传递规则。由于 TMS320C54x C 编译器支持在 C 源代码中直接用 asm 语句嵌入汇编,故在 C 编译器所产生的中间汇编代码的基础上,可局部使用嵌入汇编。这样,一方面可大大提高一些频繁使用的代码段的效率,另一方面,又不用改变原来的函数框架,也便于对照仿真结果进行调试。
在进行嵌入汇编时,要特别注意流水线的冲突。TMS320C54x 采用 6 级流水线结构,在出现流水线冲突的地方,可插入空操作(nop)指令来解决。常见的易产生流水线冲突的指令如 STLM、ADDM 等。 关于 1.10 版,还有一个值得注意的问题,即如何由两个短整数获得 32 位的长整数乘积。如: short m1,m2; long result; result = (long) (m1 * m2);
(1) result = ((long) m1 * (long) m2);
(2) 对于(1),1.10 版的编译器只是将乘积的低 16 位存入 result;而对于(2),得到的是一个 32 位的长整数乘积,但却是调用了两个长整数相乘的库函数(L$$MPY)而得到的,这对于含有大量乘加运算的 DSP 应用来说,效率上难以令人接受。为此,采用嵌入汇编可方便地解决这个问题。5 结语上述讨论中的 1.10 版的 TMS320C54x C 编译器是一个 DOS 下的工具,相应的仿真调试环境也不够友好。目前,TI 公司针对 TMS320C54x 系列已经推出了一个功能强大的集成开发环境。