1 引言
随着DSP技术的不断发展和完善,数字信号处理的应用范围越来越广泛。工控、计算机、通信和消费电子产品中,都会找到它的影子。近年来,随着多媒体通信的蓬勃发展,DSP也越来越多的应用在多媒体通信中,而在多媒体通信中DSP多用于语音压缩和图像处理等方面,而这些都需要巨大的计算量,在实时通信中一些低速DSP难以满足要求,而使用高速DSP会大大提高成本,所以对代码进行优化是现在DSP开发中常用的一种方法。由于DSP的特殊结构,编译器的编译效率都比较低,难以将DSP计算能力全部发挥出来,所以就必须根据DSP的特殊结构和指令集代码进行手工的汇编优化。
本文结合笔者在TI公司的TMS320VC5402 DSP上的对G.729算法的优化经验,提出一些优化的方法和建议,而这些方法也适用其他54系列的DSP。
2 芯片介绍
TMS320C54X是TI公司于1996年推出的新一代定点数字处理器,它具有功耗小、高度并行等优点,可以满足电信等众多领域的实时处理要求。54系列有很多不同型号的芯片,它们的结构都是一样的,只是在接口和存储器空间上有些不同。在54系列众多DSP芯片中 TMS320VC5402是使用最广泛的一种芯片,接下来将以TMS320VC5402为例介绍54系列DSP的性能特点:
● 运算速度最高达100MIPS
● 具有先进的多总线结构,三条16 位数据存储器总线和一条程序存储器总线
● 40位算术逻辑单元(ALU),包括一个40位桶形移位器和两个40位累加器
● 一个17bit×17bit乘法器和40位专用加法器,允许16位带/不带符号乘法
● 8个辅助寄存器和一个软件栈
● 内部采用改进的哈佛结构,程序空间和数据空间分开,允许同时取指令和取操作数,并且允许在程序和数据空间相互传送数据
● 最大64K×16bit外部数据空间,最大1M×16bit外部程序空间,4K×16bit片内ROM,16K×16bit片内RAM
● 内置可编程等待状态发生器、锁相环(PLL)时钟发生器、两个多通道缓冲串口、一个8位并行与外部处理器通信的HPI口、两个16位定时器以及6通道DMA控制器
● 支持单指令循环和块循环,采用六级流水线,将一条指令执行所需要的取指、译码、取操作数并执行等几个步骤同时完成,是指令周期降到最小适合算法的优化
3 代码优化
对C代码进行手工汇编优化有三种方法:1.对照C代码写出汇编代码,这种方法优化的效率很高,但是开发难度很大特别是当代码量很大,结构很复杂时优化很容易出错;2.先用编译器产生汇编代码,然后改写汇编代码,这种方法优化的效率较低,因为框架被限定了,但是开发难度降低了,不容易出错。
由于现在常用的一些音频、图像处理算法都是结构很复杂的程序,所以建议使用第二种优化方法。
3.1 产生汇编代码
TI公司为DSP开发者提供一套编译开发平台叫CCS (Code Composer Studio),该工具提供了编译器可以将C语言的程序编译为DSP的汇编语言程序,然后链接生成可以在DSP上执行的COFF格式的out文件。
而CCS自身也提供优化器可对C代码进行优化,并产生汇编语言程序,具体过程如图1所示。
CCS提供了4级的文件优化方案,分别是O0、O1、O2、O3,以下具体说明。
(1) O0 寄存器级别
● 执行控制流程简化
● 用寄存器分配变量
● 执行交替循环
● 排除未用的代码
● 简化公式和表述
● 扩大对内连函数的调用
(2) O1 局部级别
执行所有O0级别的优化,并且:
● 执行局部常量的传播
● 排除未用的赋值
● 排除局部共用表达式
(3) O2 函数级别
执行所有O1级别的优化,并且:
● 执行循环优化
● 排除全局共用子表达式
● 排除全局不用的赋值
● 执行打开循环
(4) O3 文件级别
执行所有O1级别的优化,并且:
● 排除未被调用的函数
● 简化返回值没被使用的函数
● 让小函数变成内联调用
● 保存函数说明,以便主函数被优化时知道被调用函数的属性
● 识别文件级别的变量的特性
在使用O3级别的优化时,还可以使用别的选项执行更细致的优化
● OLN 得到标准库函数的文件
● ONN 创造优化信息文件
● PM 执行程序级别优化,编译多个源文件
而我们在做优化时,选的是O2级别的优化,因为使用O2级别优化后产生的汇编文件带有比较多的注释信息,比较容易看懂程序,建议对程序不太熟和对汇编语言不太熟练的人使用。
3.2 手工汇编优化
因为汇编语言可读性很差,并且代码量很大,所以手工优化工作量很大,并且容易出错。为了确保优化不出错,我们就先制作一段测试序列即程序的输入,然后运行程序对其进行处理,生成一段正确的结果序列,检验手工优化是否正确就是用优化过的程序对相同的测试序列进行处理,比较生成的结果序列和正确的结果序列是否一样,一样的话就代表优化无误。不过测试序列要比较长,因为有的错误开始不会显现出来,只是慢慢累积,运行一段时间才会出现。
接下来,就开始手工优化的工作。下面就是我对手工优化的一些经验。
(1) 尽量少进行函数调用。因为进行函数调用的时候,要将PC压栈,还要将一些寄存器压栈,函数调用完后,还要出栈,这都是一些不必要的操作,所以一些小的函数,就不调用,而是直接写入主函数里,这样可以就可以减少那些压栈出栈的操作,提高速度。
(2) 优化循环时,尽量将一些操作放到循环外面去,减少操作的次数。例如一些赋值和初始化操作,可以提到循环外面去做,来提高速度。
(3) 去除一些冗余的赋值。编译器产生的代码有很多赋值,经常将一个值赋给寄存器,再赋给变量,这样就产生了冗余。
(4) 尽量使用RPT和RPTB来执行循环操作。在编译器产生的代码里很多循环操作是通过条件判别来实现的,这样就多了很多无用的判别代码,而54x的DSP芯片就提供专门的循环指令:RPT和RPTB。RPT的功能就是循环执行下一条指令,循环次数由RC寄存器的值决定,循环次数是RC寄存器的值加1,所以执行循环前要将循环次数减1赋给RC寄存器;RPTB是块循环指令,它的功能是循环执行一段指令,它的循环次数由BRC寄存器决定,循环次数是BRC的寄存器的值加1,所以使用前需将循环次数减1赋给BRC寄存器。
(5) 使用比较快的寻址方式。在数字信号处理里面,会对大量的数据进行大量的运算,如果使用比较快的寻址方式会大大减少指令周期。因为数据大多是顺序存放,所以我们用寄存器去寻址,操作完后自加1而指向下个数据,这样寻址会减少很多指令周期。
(6) 使用循环缓冲区。因为FFT,FIR等常用运算中都需要对数据进行移位操作,如果数据量大的话,程序花在数据移位上的开销就很大了,如果使用循环缓冲区就可以不进行这些操作从而提高速度。
(7) 使用一些专用指令。在54的指令系统里,有一些专用指令执行一些特殊的操作,例如平方,FIR等,如果用其他指令代替需要多个指令周期,而使用专用指令值需要一个指令周期。
(8) 使用并行指令。因为DSP的流水线结构,可以让一些指令同时运行,就产生了并行指令,使用并行指令会大大减少指令周期。
(9) 将一些常用的程序和数据,放在片内RAM运行。DSP芯片上一般都带有RAM,而片内RAM的寻址速度比片外RAM快一至两倍,所以将常用程序和数据放在片内,会大大提高运行速度。
3.3 优化中常遇见的问题
在手工优化过程时会遇到很多问题,以下几点比较常见。
(1) 对一些寄存器的设置。因为是手工优化,所以对一些寄存器都要自己赋值,例如ST0、ST1和PMST等,不同的设置会导致运算结果的不一样。其中一些用的比较多的位有SXM、OVM和FRCT。SXM是符号扩展位,如果SXM=0就不进行符号扩展,如果SXM=1就进行符号扩展(见图2a)。OVM是溢出模式位,当发生溢出时,如果OVM=0溢出的结果就被送往目的寄存器,如果OVM=1就往目的寄存器送最大的正数(007FFFFFFFh)或最小的负数(FF80000000h)。FRCT是小数模式位,当FRCT=1时乘法的结果会左移一位(见图2b)。以上3个标志位的置位和复位是由SSBX和 RSBX指令来完成的。
(2) 注意流水线冲突。5402的芯片有一个6级深度的指令流水线,这6级流水线彼此是独立的,在任何一个机器周期内,可以有1至6条不同的指令在工作。这6级流水线的功能分别是预取指、取指、译码、寻址、读数和执行。C5402多级流水线操作可以让多条指令同时指令访问CPU资源,如果多个流水线同时访问到相同的资源,就可能发生流水线冲突,有些冲突可以由CPU通过延迟寻址的方法自动缓解,而有的冲突是不能防止的,需要由程序重新安排指令或插入空操作来解决。当用CCS编译器对C程序进行编译的时候,编译器会自动加入NOP指令来解决流水线冲突,而进行手工优化的时候,就要特别注意这个问题,大部分流水线冲突都是因为同时访问到某些寄存器,只要根据等待周期表加入相应的NOP指令就可以解决。
(3) 对一些参数的保存。在手工优化的过程中,我们会用某些寄存器来传递数据,而在此过程中,如果调用了别的函数,这些寄存器的值就有可能被改变,所以在调用这些函数的时候,要先将这些参数压栈保存,调用完后再将其出栈恢复。还有就是某些标志位的保存,因为在调用函数的过程会改变这些状态标志位,所以在调用完后要将其恢复。
(4) 循环缓冲区地址分配问题。循环缓冲区的地址分配必须对齐,长度为R的缓冲区必须从N位地址的边界开始(即循环缓冲区基地址的N个最低有效位必须为0),N 是满足2N>R的最小的整数。例如,长度R=31的循环缓冲区必须从地址XXXX XXXX XXX0 00002(N=5,25>>31,该地址的最低5位为0)。
(5) 内存泄漏问题。因为DSP使用的是哈佛结构,数据空间和程序空间是分开的,一般数据的操作不会影响到程序。但是DSP芯片上都带有RAM,而这些空间数据和程序是共享的,所以对该部分的数据进行操作,如果有泄漏的话会改写程序,导致程序跑飞。因此程序跑飞的话,就要考虑是否有内存泄漏。
4 结论
以上经验和技巧均是笔者在实际的DSP工程中总结得出,实践证明对实际开发非常有帮助。以笔者对G.729算法优化为例,在优化之前,G.729的运算量为1000MIPS,优化后的运算量为30MIPS,提高了30多倍,可见优化的效果很明显。以上这些经验主要是针对TI公司的 54系列,但对于别的型号的DSP也有借鉴作用。
1 引言
随着DSP技术的不断发展和完善,数字信号处理的应用范围越来越广泛。工控、计算机、通信和消费电子产品中,都会找到它的影子。近年来,随着多媒体通信的蓬勃发展,DSP也越来越多的应用在多媒体通信中,而在多媒体通信中DSP多用于语音压缩和图像处理等方面,而这些都需要巨大的计算量,在实时通信中一些低速DSP难以满足要求,而使用高速DSP会大大提高成本,所以对代码进行优化是现在DSP开发中常用的一种方法。由于DSP的特殊结构,编译器的编译效率都比较低,难以将DSP计算能力全部发挥出来,所以就必须根据DSP的特殊结构和指令集代码进行手工的汇编优化。
本文结合笔者在TI公司的TMS320VC5402 DSP上的对G.729算法的优化经验,提出一些优化的方法和建议,而这些方法也适用其他54系列的DSP。
2 芯片介绍
TMS320C54X是TI公司于1996年推出的新一代定点数字处理器,它具有功耗小、高度并行等优点,可以满足电信等众多领域的实时处理要求。54系列有很多不同型号的芯片,它们的结构都是一样的,只是在接口和存储器空间上有些不同。在54系列众多DSP芯片中 TMS320VC5402是使用最广泛的一种芯片,接下来将以TMS320VC5402为例介绍54系列DSP的性能特点:
● 运算速度最高达100MIPS
● 具有先进的多总线结构,三条16 位数据存储器总线和一条程序存储器总线
● 40位算术逻辑单元(ALU),包括一个40位桶形移位器和两个40位累加器
● 一个17bit×17bit乘法器和40位专用加法器,允许16位带/不带符号乘法
● 8个辅助寄存器和一个软件栈
● 内部采用改进的哈佛结构,程序空间和数据空间分开,允许同时取指令和取操作数,并且允许在程序和数据空间相互传送数据
● 最大64K×16bit外部数据空间,最大1M×16bit外部程序空间,4K×16bit片内ROM,16K×16bit片内RAM
● 内置可编程等待状态发生器、锁相环(PLL)时钟发生器、两个多通道缓冲串口、一个8位并行与外部处理器通信的HPI口、两个16位定时器以及6通道DMA控制器
● 支持单指令循环和块循环,采用六级流水线,将一条指令执行所需要的取指、译码、取操作数并执行等几个步骤同时完成,是指令周期降到最小适合算法的优化
3 代码优化
对C代码进行手工汇编优化有三种方法:1.对照C代码写出汇编代码,这种方法优化的效率很高,但是开发难度很大特别是当代码量很大,结构很复杂时优化很容易出错;2.先用编译器产生汇编代码,然后改写汇编代码,这种方法优化的效率较低,因为框架被限定了,但是开发难度降低了,不容易出错。
由于现在常用的一些音频、图像处理算法都是结构很复杂的程序,所以建议使用第二种优化方法。
3.1 产生汇编代码
TI公司为DSP开发者提供一套编译开发平台叫CCS (Code Composer Studio),该工具提供了编译器可以将C语言的程序编译为DSP的汇编语言程序,然后链接生成可以在DSP上执行的COFF格式的out文件。
而CCS自身也提供优化器可对C代码进行优化,并产生汇编语言程序,具体过程如图1所示。
CCS提供了4级的文件优化方案,分别是O0、O1、O2、O3,以下具体说明。
(1) O0 寄存器级别
● 执行控制流程简化
● 用寄存器分配变量
● 执行交替循环
● 排除未用的代码
● 简化公式和表述
● 扩大对内连函数的调用
(2) O1 局部级别
执行所有O0级别的优化,并且:
● 执行局部常量的传播
● 排除未用的赋值
● 排除局部共用表达式
(3) O2 函数级别
执行所有O1级别的优化,并且:
● 执行循环优化
● 排除全局共用子表达式
● 排除全局不用的赋值
● 执行打开循环
(4) O3 文件级别
执行所有O1级别的优化,并且:
● 排除未被调用的函数
● 简化返回值没被使用的函数
● 让小函数变成内联调用
● 保存函数说明,以便主函数被优化时知道被调用函数的属性
● 识别文件级别的变量的特性
在使用O3级别的优化时,还可以使用别的选项执行更细致的优化
● OLN 得到标准库函数的文件
● ONN 创造优化信息文件
● PM 执行程序级别优化,编译多个源文件
而我们在做优化时,选的是O2级别的优化,因为使用O2级别优化后产生的汇编文件带有比较多的注释信息,比较容易看懂程序,建议对程序不太熟和对汇编语言不太熟练的人使用。
3.2 手工汇编优化
因为汇编语言可读性很差,并且代码量很大,所以手工优化工作量很大,并且容易出错。为了确保优化不出错,我们就先制作一段测试序列即程序的输入,然后运行程序对其进行处理,生成一段正确的结果序列,检验手工优化是否正确就是用优化过的程序对相同的测试序列进行处理,比较生成的结果序列和正确的结果序列是否一样,一样的话就代表优化无误。不过测试序列要比较长,因为有的错误开始不会显现出来,只是慢慢累积,运行一段时间才会出现。
接下来,就开始手工优化的工作。下面就是我对手工优化的一些经验。
(1) 尽量少进行函数调用。因为进行函数调用的时候,要将PC压栈,还要将一些寄存器压栈,函数调用完后,还要出栈,这都是一些不必要的操作,所以一些小的函数,就不调用,而是直接写入主函数里,这样可以就可以减少那些压栈出栈的操作,提高速度。
(2) 优化循环时,尽量将一些操作放到循环外面去,减少操作的次数。例如一些赋值和初始化操作,可以提到循环外面去做,来提高速度。
(3) 去除一些冗余的赋值。编译器产生的代码有很多赋值,经常将一个值赋给寄存器,再赋给变量,这样就产生了冗余。
(4) 尽量使用RPT和RPTB来执行循环操作。在编译器产生的代码里很多循环操作是通过条件判别来实现的,这样就多了很多无用的判别代码,而54x的DSP芯片就提供专门的循环指令:RPT和RPTB。RPT的功能就是循环执行下一条指令,循环次数由RC寄存器的值决定,循环次数是RC寄存器的值加1,所以执行循环前要将循环次数减1赋给RC寄存器;RPTB是块循环指令,它的功能是循环执行一段指令,它的循环次数由BRC寄存器决定,循环次数是BRC的寄存器的值加1,所以使用前需将循环次数减1赋给BRC寄存器。
(5) 使用比较快的寻址方式。在数字信号处理里面,会对大量的数据进行大量的运算,如果使用比较快的寻址方式会大大减少指令周期。因为数据大多是顺序存放,所以我们用寄存器去寻址,操作完后自加1而指向下个数据,这样寻址会减少很多指令周期。
(6) 使用循环缓冲区。因为FFT,FIR等常用运算中都需要对数据进行移位操作,如果数据量大的话,程序花在数据移位上的开销就很大了,如果使用循环缓冲区就可以不进行这些操作从而提高速度。
(7) 使用一些专用指令。在54的指令系统里,有一些专用指令执行一些特殊的操作,例如平方,FIR等,如果用其他指令代替需要多个指令周期,而使用专用指令值需要一个指令周期。
(8) 使用并行指令。因为DSP的流水线结构,可以让一些指令同时运行,就产生了并行指令,使用并行指令会大大减少指令周期。
(9) 将一些常用的程序和数据,放在片内RAM运行。DSP芯片上一般都带有RAM,而片内RAM的寻址速度比片外RAM快一至两倍,所以将常用程序和数据放在片内,会大大提高运行速度。
3.3 优化中常遇见的问题
在手工优化过程时会遇到很多问题,以下几点比较常见。
(1) 对一些寄存器的设置。因为是手工优化,所以对一些寄存器都要自己赋值,例如ST0、ST1和PMST等,不同的设置会导致运算结果的不一样。其中一些用的比较多的位有SXM、OVM和FRCT。SXM是符号扩展位,如果SXM=0就不进行符号扩展,如果SXM=1就进行符号扩展(见图2a)。OVM是溢出模式位,当发生溢出时,如果OVM=0溢出的结果就被送往目的寄存器,如果OVM=1就往目的寄存器送最大的正数(007FFFFFFFh)或最小的负数(FF80000000h)。FRCT是小数模式位,当FRCT=1时乘法的结果会左移一位(见图2b)。以上3个标志位的置位和复位是由SSBX和 RSBX指令来完成的。
(2) 注意流水线冲突。5402的芯片有一个6级深度的指令流水线,这6级流水线彼此是独立的,在任何一个机器周期内,可以有1至6条不同的指令在工作。这6级流水线的功能分别是预取指、取指、译码、寻址、读数和执行。C5402多级流水线操作可以让多条指令同时指令访问CPU资源,如果多个流水线同时访问到相同的资源,就可能发生流水线冲突,有些冲突可以由CPU通过延迟寻址的方法自动缓解,而有的冲突是不能防止的,需要由程序重新安排指令或插入空操作来解决。当用CCS编译器对C程序进行编译的时候,编译器会自动加入NOP指令来解决流水线冲突,而进行手工优化的时候,就要特别注意这个问题,大部分流水线冲突都是因为同时访问到某些寄存器,只要根据等待周期表加入相应的NOP指令就可以解决。
(3) 对一些参数的保存。在手工优化的过程中,我们会用某些寄存器来传递数据,而在此过程中,如果调用了别的函数,这些寄存器的值就有可能被改变,所以在调用这些函数的时候,要先将这些参数压栈保存,调用完后再将其出栈恢复。还有就是某些标志位的保存,因为在调用函数的过程会改变这些状态标志位,所以在调用完后要将其恢复。
(4) 循环缓冲区地址分配问题。循环缓冲区的地址分配必须对齐,长度为R的缓冲区必须从N位地址的边界开始(即循环缓冲区基地址的N个最低有效位必须为0),N 是满足2N>R的最小的整数。例如,长度R=31的循环缓冲区必须从地址XXXX XXXX XXX0 00002(N=5,25>>31,该地址的最低5位为0)。
(5) 内存泄漏问题。因为DSP使用的是哈佛结构,数据空间和程序空间是分开的,一般数据的操作不会影响到程序。但是DSP芯片上都带有RAM,而这些空间数据和程序是共享的,所以对该部分的数据进行操作,如果有泄漏的话会改写程序,导致程序跑飞。因此程序跑飞的话,就要考虑是否有内存泄漏。
4 结论
以上经验和技巧均是笔者在实际的DSP工程中总结得出,实践证明对实际开发非常有帮助。以笔者对G.729算法优化为例,在优化之前,G.729的运算量为1000MIPS,优化后的运算量为30MIPS,提高了30多倍,可见优化的效果很明显。以上这些经验主要是针对TI公司的 54系列,但对于别的型号的DSP也有借鉴作用。
举报