原创 TMS320C54x数字信号处理器

2008-1-4 10:22 1517 0 分类: 工程师职场

  摘要:以G.723.1语音编码算法的优化为例,详细介绍了TMS320C54x DSP的软件优化流程以及具体优化技巧。


  关键词:优化;DSP;G.723.1


  1 引言


  很多人认为, 处理器仍然按照摩尔定律保持着高速发展, 所以如果对软件效率不满意, 只需选择更快更强大的处理器, 而不需要在针对处理器的软件优化上花费太多精力。 对于某些应用,这样的考虑当然正确, 比如仅仅需要一片DSP来实现某个大系统中的一部分数据处理工作, 而且此系统对于效率以及功耗没有太高要求。 但是,对于很多特定应用, 例如实现信源或信道编码, 由于编解码算法的运算量太大, 如果不经过精心优化,很可能采用最新的DSP仍然无法实现,优化后却可能在单片DSP上实现更多路的编解码算法, 从而降低每路的平均成本。 此外,在消费类电子产品中,例如手机与便携式MP3播放器这类电池供电的系统, 本身对功耗相当敏感,而DSP功耗基本上随着MIPS数的增加而线性增长。 如果对此类系统上运行的软件进行优化, 其工作时间将会大大延长。


  还有人寄希望于将优化工作全部交给DSP开发工具自带的C编译器, 但


  以笔者多年的工作经验现在的C编译器对大部分软件并不能达到期望的优化效果, 受DSP片上资源少的约束,将来也不可能达到, 除非DSP本身在硬件资源上有较大的突破。


  本文将以 ITU-T的标准G.723.1语音编解码算法为例, 系统讲述针对TI公司的TMS320C54x芯片的优化流程(参见图1)以及优化技巧。


  20080104091900178.jpg


  


  图1 TMS320C54x DSP的软件优化流程


  2 优化方法


  在使用TI的集成开发环境  Code Composer Studio (CCS)建立工程时,第一步工作是把C源程序由PC平台移植到DSP平台上,保证其运行正确。C54x的数据类型并不完全与ANSI C兼容,它的char型数据为16bit,而不是正常的8bit。如果原始的C程序中有char型数据变量, 需要经过一定处理,转化为short型16bit数据变量。而且,CCS对于fread()、fwrite()等函数支持的并不好, 同样需要进一步处理以满足要求。 在建立工程开始,最好不要使用任何优化编译选项, 以避免产生无法预料的错误。 工程建立并验证正确后, 逐级升高优化编译级别,直到使用-o3选项, 仍能运行正确,就可以开始具体的优化工作了。 如果在此过程中程序出错,必须经过调试确定CCS编译时何处代码产生错误, 并降低这一部分的优化级别, 或对该处C源程序进行结构上的修改, 以避开CCS的错误。


  G.723.1的 basop.c文件中的基本运算函数采用的是欧洲电信标准协会(ETSI)的标准函数。 通过简单地包含intrindefs.h头文件,不改变C程序,就可以在编译时将basop.c中的大部分基本运算函数用单条汇编指令替代,从而避免频繁的函数调用。 C54x的C编译器同时支持大量的 intrinsic指令, 与相应的汇编指令对应,可以适当地加以应用。 C编译器此外还支持asm(“assembler text”);的格式,在C程序中直接插入汇编指令 (inline形式), 编译器将把双引号中的字符串直接拷贝到编译输出的汇编文件中,但这种做法是相当危险的, 很可能产


  生错误, 并降低程序的可读性。这种格式最好仅仅用于向生成的汇编文件中添加注释。


  在以上工作结束后, 使用CCS提供的统计工具对编解码算法中的各个模块进行运算量统计,作为下一步工作的依据。对于G.723.1, 会发现运算量主要集中在固定码本搜索与自适应码本搜索部分, 以及大量的滤波运算部分。 此时的优化策略取决于具体的优化目标, 如果最终目标是实现一个效率最优的编解码器, 那么可以按部就班地对所有函数依次优化。 如果目标仅止于片上实时实现, 就应该从效率最低、 运算量最大的块入手进行优化,达到目标即可结束。 而对于运算量很小主要用于控制的函数, 就不需要进行优化。


  由于C54x本身硬件上的限制,客观上说只有 2个40bit 寄存器可以进行基本运算,所以很难仅在C语言级上进行优化,但这并不意味着优秀的汇编可以解决一切。有时算法本身的优化能够产生惊人的效果。 单纯地将C语言逐行翻译成汇编并不能称之为优化,只算得上是人工的C编译器。 真正的优化需要算法级、C语言级、 汇编语言级配合实现。


  一般说来,算法为了保持程序可读性,有些部分必然存在着运算量上的冗余。例如,对一个很大的数组进行初始化清零,其实只需要将其中的几个元素清零即可。算法级上的优化需要真正懂得算法原理,具体算法具体分析,没有定式可循。


  在C语言级上,可做的工作很多,比较零碎但很有意义,可以为下一步快速写出优秀的汇编打好基础。G.723.1源程序中的10阶FIR、IIR滤波部分,对每一个样点进行滤波后,都对10个滤波器状态进行更新,这就造成效率的极大降低,可以将状态更新部分移到循环外,在滤波结束后更新一次即可。G.723.1中有许多移位操作,移位的位数是未知变量。C54x的移位操作最多可以左移31位,或右移16位。如果可以确定移位不超过此范围,就不需要在将来写汇编程序时用很多指令进行判断,一条简单的移位指令即可实现。判断方法有两种,一种是基于算法的理论分析,如果移位位数直/间接产生于norm_s()或norm_l()函数,那么很容易分析是否超出范 数,很多情况下永远不会发生溢出,可以用上面两种方法进行分析化简。但是在确定不了的地方,还是要采用最保险


  的方法实现。 算法级和C语言级上做的工作越充分, 下一步汇编工作就越顺利,效率也越高。下面详细讨论汇编语言级的优化。


  同样的C程序, 交给不同的人,会写出不同的汇编,但最有效率的汇编只有确定的一种,如何才能写出最优秀的汇编? 那就需要程序员在写每一个函数,甚至每一条指令的时候,都要保证它是最优的,无可替代的。程序员需要清楚地了解C54x的汇编指令集,指令间的流水线冲突。背熟厚厚一本指令集是不现实的,但至少需要知道有哪几条跳转指令,哪些乘法指令,以及如何高效实现多重循环,等等。在每次进行内存读写时,都要考虑是否可以利用相关的并行指令。至于利用跳转或循环指令的延迟,以及在流水线等待期间插入其他运行指令,是比较常见的优化方法。而优化的关键,在于被频繁调用的多重循环中的最内层循环,例如Find_Best()函数中,最内层循环体内每减少一条指令, 都将使整个算法峰值运算量下降0.288MIPS。 如果这样的程序段优化不好,将会产生灾难性后果。对于最内层循环,如果循环次数很少,且循环体非常简单,可以采用内层循环展开的方式进行优化。 例如频繁出现的10阶FIR或IIR滤波运算, 由于只是单条乘累加指令循环10次,所以将内层循环完全展开并未增加多少代码长度,却能够有很好的优化效果。以Synt()函数为例,经过算法以及C语言级的优化,C程序主体已经优化为:


  for ( i = 0 ; i < SubFrLen ; i ++ )


  {


  Acc0 = ((Word32)Dpnt) << 13 ;


  for ( j = 0 ; j < LpcOrder ; j ++ )


  Acc0 = L_mac( Acc0, Lpc[j], Spnt[i-1-j] ) ;


  Acc0 = L_shl ( Acc0, 2 ) ;


  Spnt = round( Acc0 ) ;


  }


  循环体对应的汇编程序如下:


  loop starts


  LD *AR_Dpnt+, 13, A


  MAC *AR_Lpc +, *AR_Spnt_j-, A


  MAC *AR_Lpc +, *AR_Spnt_j-, A


  MAC *AR_Lpc +, *AR_Spnt_j-, A


  MAC *AR_Lpc +, *AR_Spnt_j-, A


  MAC *AR_Lpc +, *AR_Spnt_j-, A


  MAC *AR_Lpc +, *AR_Spnt_j-, A


  MAC *AR_Lpc +, *AR_Spnt_j-, A


  MAC *AR_Lpc +, *AR_Spnt_j-, A


  MAC *AR_Lpc +, *AR_Spnt_j-, A


  MAC *AR_Lpc +0%, *AR_Spnt_j-, A SFTA A, 2


  RND A


  MVMM AR_Spnt, AR_Spnt_j


  STH A, *AR_Spnt+


  loop ends


  程序中AR寄存器采用助记名称表示以增加可读性.10次乘累加已全部展开,以减少循环控制语句消耗的运算量。BK


  设置为0,AR0设置为-9,当运行到最后一条MAC指令时,*ARx+0%模式的循环寻址方式使 Lpc系数指针自动回复到起始地址, 否则,至少需要2周期的指令对其进行指针调整。最终计算每点的10阶IIR滤波仅需要15个周期。


  G.723.1 的程序中有许多的搜索运算。如果对C54x的指令集不熟悉,很难找到较好的解决办法,笔者也是在完成了大部分程序后才发现C54x提供的条件存储指令有很好的效果,并以此改写了已经编好的很多汇编程序。


  以Find_Best()函数中一段程序为例:


  Acc2 = (Word32) 0 ;


  for ( i = 0 ; i < SubFrLen/2 ; i ++ )


  {


  Acc0 = L_abs( ErrBlk )


  if ( Acc0 >= Acc2 )


  {


  Acc2 = Acc0 ;


  indx = (Word16) i


  }


  }


  循环体对应的汇编程序如下:


  STM #(SubFrLen/2-1), BRC


  RPTB Label - 1


  loop starts


  SUB A, B


  SRCCD *AR_indx, BEQ


  DLD *AR_ErrBlk+, B


  ABS B


  MAX A


  Label:loop ends


  由于块循环计数器BRC每次循环都进行递减, 所以可以起到C程序中计数器i的作用。使用SRCCD条件存储指令即可将BRC值存到Xmem中。在循环结束后,经过一定处理,就可以方便地得到要求的indx值。


  在一开始学习优化时写的汇编程序,有经验后就会发现效率很低,甚至今天写的程序,明天就会发现存在很多不足。所以程序写完并调试通过并不等于工作完成,回过头来细细检查一定会有收获,提高优化质量的同时也能迅速提高优化技能。


  6  优化效果


  经过由算法级到汇编语言级的优化,G.723.1的6.3kb/s速率编解码器,包括VAD、高通滤波、 后滤波在内的峰值运算量为15.4MIPS, 并通过了ITU-T的所有测试序列。笔者所了解的最优运算量是16.9MIPS, 而程序代码大小相


  当。 这样我们就实现了可以在100MIPS运算能力的 C54x 上运行6路6.3kb/s G.723.1编解码算法的方案。 在效率改善中,高质量的汇编所起的作用仅占到约50%,其它是算法级和C语言级的作用.如果希望达到最好的优化效果,不要在优化工程一开始就写汇编,而要认真地进行算法级和C语言级的优化, 并贯穿始终,才能起到事半功倍的效果。限于篇幅,无法对其他优化技巧一一列举,希望通过本文, 表达一种优化思想-Thinking in DSP。


  参考文献:


  1. International Telecommunication Union: 'Dual rate speech coder for multimedia communications transmitting at 5.3 and 6.3 kbit/s', ITU-T Recommendation G.723.1.


  2. Texas Instruments: TMS320C54x DSP Datasheets.

PARTNER CONTENT

文章评论0条评论)

登录后参与讨论
我要评论
0
0
关闭 站长推荐上一条 /1 下一条