1. 指令集架构与核心寄存器概览
在深入探讨SPAC、SPH、SPL这些具体指令之前,我们必须先建立起对TMS320C5x系列DSP核心数据通路的基本认知。这就像你要操作一台精密的机床,必须先熟悉它的主轴、导轨和刀库一样。C5x的指令集设计核心是围绕几个关键寄存器展开的高速数据流处理,其中累加器(ACC)和乘积寄存器(PREG)扮演了绝对的主角。
ACC是一个32位的寄存器,它是大多数算术和逻辑运算的最终目的地,可以看作是我们进行复杂数学运算的“工作台”。而PREG则是一个32位的乘积寄存器,专门用于存放乘法器的输出结果。这里有个非常关键的设计:PREG的输出在送入ACC进行后续累加或减法之前,会经过一个称为乘积移位器(Product Shifter)的部件。这个移位器的工作模式由状态寄存器ST1中的PM(Product Mode)位(第0位和第1位)控制。PM位决定了PREG输出是直接使用,还是进行左移1位、左移4位或右移6位的操作。
为什么要有这个设计?这主要服务于两类核心运算:小数运算(Fractional Arithmetic)和防止累加溢出。在数字信号处理中,我们经常使用Q格式(例如Q15)来表示小数。两个Q15格式的数(范围在-1到~+1之间)相乘,结果会是一个Q30格式的数(范围在-1到~+1之间)。为了将结果存回Q15格式,我们需要将乘积右移15位。C5x的硬件设计通过“左移1位+后续处理”或特定的数据定标来高效地完成这个操作。而右移6位模式,则是为了在连续执行多达128次乘加运算时,通过预先对乘积进行缩放,来有效防止累加器溢出,这对于FIR滤波器等需要长累加和的算法至关重要。
除了ACC和PREG,T寄存器(TREG0, TREG1, TREG2)是乘法操作的另一个输入源。通常,TREG0存放一个乘数,而另一个乘数来自数据存储器或指令中的立即数。状态寄存器中的TRM(TREG Mode)位则控制着TREG0的加载行为是否会影响TREG1和TREG2,这主要是为了维持与早期TMS320C2x系列处理器的代码兼容性。
理解这些寄存器之间的协作关系,是看懂SPAC、SPH、SPL等指令如何工作的前提。它们并非孤立存在,而是构成了一条从数据读取(到TREG)、乘法运算(结果存PREG)、移位调整(通过PM控制)、再到最终与ACC进行合并(加/减)的完整流水线。接下来,我们就从这条流水线的“合并”环节开始,深入第一条指令。
2. SPAC指令:减法合并的核心操作
SPAC指令,全称是“Subtract Product from Accumulator with Shift”,即“从累加器中减去移位后的乘积”。这条指令是理解C5x DSP乘积累加(MAC)类操作的关键一环,它完成了乘法-累加流水线中的“减”操作。
2.1 指令功能与执行流程解析
SPAC的语法非常简单:SPAC。它没有显式的操作数,因为它隐含的操作对象是ACC和PREG。其执行过程可以清晰地分为三步:
- 取指与PC递增:程序计数器(PC)加1,指向下一条指令。
- 乘积移位:将PREG中的32位内容,根据当前PM位的设置进行移位。需要特别注意的是,SPAC指令的执行不受SXM(符号扩展模式)位的影响。无论SXM是0还是1,PREG在移位时总是进行符号扩展。这意味着即使你处理的是无符号数,在SPAC操作中,PREG的高位也会根据其最高位(bit 31)进行填充,这是一个容易忽略的细节。
- 减法与存储:将移位后的PREG值从ACC中减去,结果存回ACC。
用公式来表达就是:ACC = ACC - (PREG >> PM)。这里的“>>”代表根据PM值进行的移位操作(可能是左移或右移)。
2.2 状态位影响与实战意义
SPAC指令会直接影响两个关键的状态位:进位位(C)和溢出位(OV)。
- 进位位(C):在减法中,C位表征的是“借位”。如果本次减法产生了借位(即ACC < 移位后的PREG),则C被清0;否则,C被置1。这与加法中的“进位”概念正好相反,在编写多精度减法例程时需要特别注意。
- 溢出位(OV):如果减法结果超出了32位有符号数的表示范围(-2^31 到 2^31-1),则OV位置1。OVM(溢出模式)位会影响ACC在溢出时的行为:若OVM=1,发生正溢出时ACC会被饱和为0x7FFF FFFF,负溢出时饱和为0x8000 0000;若OVM=0,则ACC直接存放溢出后的截断结果。
一个核心的实战要点:SPAC是LTS(Load TREG and Subtract)、MPYS(Multiply and Subtract)和SQRS(Square and Subtract)指令的功能子集。这意味着,当你需要连续执行“加载数据到TREG -> 乘法 -> 从ACC中减去乘积”这一系列操作时,使用LTS一条指令即可完成,它比先执行MPY(乘法)再执行SPAC效率更高,因为节省了指令周期和代码空间。SPAC更适用于乘积已经存在于PREG中,且需要从ACC中减去的场景,例如在实现某些特定系数更新算法或误差计算时。
2.3 寻址模式与执行周期考量
由于SPAC是无操作数指令,它不涉及复杂的数据寻址。其执行周期非常稳定,在片内ROM、DARAM或SARAM中执行都是1个周期。即使在外部存储器中执行,也仅为“1+p”个周期(p代表等待状态)。当被RPT(重复)指令包裹时,它可以在片内存储器中实现单周期循环,这对于需要连续进行多次减法合并的循环体性能提升巨大。
注意:虽然SPAC本身很简单,但在算法中使用它时,务必预先确认PREG中的值是否是你期望的、且已经过正确PM移位的结果。一个常见的错误是,在执行SPAC前忘记了PM位的设置,导致减法操作的对象发生了意料之外的缩放,从而引发整个算法错误。
3. SPH与SPL指令:乘积结果的分解存储
如果说SPAC关注的是32位结果的合并运算,那么SPH(Store Product High)和SPL(Store Product Low)指令则专注于将32位的乘积结果分解并存储到16位的数据存储器中。这是DSP中处理双字长乘积输出的标准方式。
3.1 功能定义与移位逻辑
- SPH dma:将PREG的高16位(bit 31-16)经过PM移位器后,存储到指定的数据存储器地址(dma)。
- SPL dma:将PREG的低16位(bit 15-0)经过PM移位器后,存储到指定的数据存储器地址(dma)。
这里的“经过PM移位器”是精髓所在。它并不是将整个32位PREG移位后再取高/低16位,而是先取PREG的高16位或低16位,再将这16位数据送入PM移位器进行移位。移位规则需要仔细区分:
- 对于SPH:
- 当PM=00(不移位)或PM=01/10(左移1/4位)时:取PREG[31:16]直接或左移后,低位移入的位来自PREG的低字节相应位。例如左移1位,SPH输出的bit 0来自PREG的bit 15。
- 当PM=11(右移6位)时:取PREG[31:16]右移,高位进行符号扩展(基于PREG的bit 31)。
- 对于SPL:
- 当PM=00(不移位)或PM=01/10(左移1/4位)时:取PREG[15:0]直接或左移后,低位移入的位用0填充。
- 当PM=11(右移6位)时:取PREG[15:0]右移,高位填充的位来自PREG的高字节相应位(即PREG[31:16]的低位部分)。
关键点:SPH和SPL指令不影响ACC和PREG本身的内容,它们只是读取PREG的值,移位后写入内存。这允许你在不破坏原始乘积的情况下,将中间结果保存起来。
3.2 寻址模式详解与应用场景
SPH和SPL支持直接寻址和间接寻址。
- 直接寻址:例如
SPH 60h。这里的60h是数据页内的偏移地址,最终地址由DP(数据页指针)和该偏移量共同决定。指令编码中包含了dma字段。 - 间接寻址:例如
SPH *+, AR1。这是更灵活、更常用的方式。*表示使用当前辅助寄存器(AR)的内容作为地址。*+表示操作后当前AR加1(用于数组顺序访问)。, AR1表示操作完成后,将辅助寄存器指针(ARP)修改为1,即下一个操作使用AR1。这在处理数据块时极其高效。
应用场景:SPH/SPL最典型的应用场景是实现双精度(32位)结果的存储。例如,一个32位乘法结果在PREG中,你需要将它存放到两个连续的16位内存单元(High_Word, Low_Word)中。正确的操作顺序通常是先执行SPH,再执行SPL。因为如果先执行SPL,在左移模式下,PREG低16位移出的位会丢失,可能影响随后SPH取高16位进行左移时的低位填充值(该值来自PREG低字节)。在右移模式下,顺序同样重要,因为SPL右移时的高位填充来自PREG高字节。
3.3 执行周期与代码优化
SPH/SPL的执行周期取决于操作数(目标地址)所在的存储器类型:
- 目标在DARAM(双访问RAM):1个周期。这是最优情况,因为DARAM在一个周期内可被访问两次。
- 目标在SARAM(单访问RAM):通常为1个周期,但如果指令代码本身也位于同一个SARAM块中,则可能需要2个周期(因为SARAM一个周期只能进行一次访问)。
- 目标在外部存储器:周期数较多,为“1+d+p”或更多(d为数据等待状态)。优化策略非常明确:尽可能将频繁访问的数据缓冲区安排在片内的DARAM中,尤其是对于在循环内被反复执行的SPH/SPL指令。使用RPT指令重复执行SPH或SPL,可以将其在片内存储器中的开销降低到平均1周期/次,这对于需要存储大量乘积结果的算法(如大规模矩阵运算中间结果保存)至关重要。
4. 乘积移位模式(PM)的精确控制:SPM指令
PM位控制着PREG输出移位器的行为,而SPM(Set Product Shift Mode)指令就是专门用来设置这两个关键位的。它的语法是SPM constant,其中constant是0到3之间的立即数,直接写入ST1寄存器的PM字段。
4.1 PM模式详解与算法对应
PM位的四种设置及其对PREG输出(以及SPAC、SPH、SPL等指令)的影响如下:
| PM值 | 助记符 | 操作 | 低位填充(对SPL影响大) | 高位填充(对SPH影响大) | 主要应用场景 |
|---|---|---|---|---|---|
| 00 | PM=0 | 输出不移位 | - | - | 整数运算,或乘积已为最终格式 |
| 01 | PM=1 | 左移1位 | 填0 | 来自PREG低字节bit 15 | Q15小数乘法:两个Q15数相乘得Q30,左移1位变成Q31,再取高16位(通常用SPH)即得到Q15结果。 |
| 10 | PM=2 | 左移4位 | 填0 | 来自PREG低字节bit [15:12] | 扩展动态范围,或特定定标需求 |
| 11 | PM=3 | 右移6位 | 来自PREG高字节bit [21:16] | 符号扩展(基于bit 31) | 防止累加溢出:在长累加(如128点FIR滤波)前对每个乘积进行1/64缩放,使累加和不易溢出。 |
为什么左移1位用于Q15格式?这是TI DSP的一个经典设计。两个16位有符号小数(Q15,范围[-1, 1))相乘,理论结果是32位的Q30小数(范围[-1, 1))。为了将其存回一个16位的Q15格式存储器,我们需要保留结果中最有效的16位。乘积左移1位,相当于将Q30转换为Q31(此时数值范围不变,但分辨率变化),然后取高16位(相当于右移15位),这正好得到了Q15格式的结果。硬件通过PM=1和SPH指令的配合,高效地完成了这一系列操作。
4.2 SPM指令的使用时机与陷阱
你需要在任何使用PREG输出作为输入的指令(如SPAC, SPH, SPL, MAC, MACD等)之前设置好PM位。一个常见的编程模式是:
SPM 1 ; 设置为左移1位模式,用于小数乘法 MPY *+, A ; 相乘,结果在PREG中(已自动左移1位) SPH *+, B ; 将乘积的高16位(即Q15结果)存储到B指向的地址重要的陷阱:PM位是全局状态位。一旦设置,它将影响所有后续使用PREG移位输出的指令,直到被下一次SPM或LST #1指令修改。如果你在同一个函数或中断服务例程中混合了不同定标需求的运算(例如,一部分代码需要Q15格式,另一部分需要防止溢出的右移模式),就必须非常小心地保存、恢复或重新设置PM位。一个粗心的PM位设置错误可能导致整个信号处理链的增益发生64倍(2^6)的偏差,这种错误在时域上难以察觉,但在频域或能量计算中会暴露无遗。
实操心得:在函数入口处将PM位设置为一个确定的值(通常是你的算法主要需要的模式),并在函数退出前恢复它,是一个好习惯。如果函数内需要切换模式,务必在切换点加注释说明。对于关键算法,可以在初始化代码中显式地执行
SPM 0,确保系统从一个已知的状态开始。
5. 关联指令深度解析:从SPAC到SQRS
孤立地理解SPAC、SPH、SPL是片面的,它们是一个强大指令家族的一部分。这个家族的核心是乘积累加(MAC)操作。让我们看看更复杂的组合指令如何封装了这些基本操作。
5.1 LTS, MPYS, SQRS:功能超集
- LTS(Load TREG and Subtract):这条指令一口气完成了三件事:1) 将数据存储器中的值加载到TREG0;2) 将PREG移位后从ACC中减去;3) 将刚才加载到TREG0的值与另一个操作数相乘,结果存入PREG。它等价于
LT+SPAC+MPY的组合,但只需要一个指令字和一个周期(在片内RAM)。它完美展示了DSP指令的“多功能单周期”设计哲学。 - MPYS(Multiply and Subtract):与LTS类似,但它使用当前TREG0的值与数据存储器中的值相乘,同时将旧的PREG移位后从ACC中减去。
- SQRS(Square and Subtract):这是一条非常特殊的指令,用于计算平方差。它执行:1) 将数据存储器中的值加载到TREG0;2) 将PREG移位后从ACC中减去;3) 计算TREG0中值的平方(即与自身相乘),结果存入PREG。这对于计算误差能量(如
ACC = ACC - x^2)非常有用。
SPAC与它们的关系:SPAC只完成了上述指令的第二步——“从ACC中减去移位后的PREG”。因此,在只需要减法合并操作,且PREG中已经有所需乘积时,用SPAC更直接。但如果你的算法流程是“加载新数据 -> 处理旧乘积 -> 计算新乘积”,那么LTS这类复合指令的效率高得多。
5.2 状态位TRM的兼容性考量
在SQRA和SQRS指令的描述中,都提到了TRM(TREG Mode)位。当TRM = 0时,任何加载TREG0的指令(如LT, LTA, LTD, LTS等)都会同时将相同的值写入TREG1和TREG2。这是为了与更早的TMS320C2x处理器保持对象代码级别的兼容性。
这对我们意味着什么?
- 兼容性模式:如果你在移植或复用C2x的老代码,需要确保TRM位被正确清零(通常在初始化代码中完成),否则依赖TREG1/TREG2的旧代码可能无法工作。
- 资源冲突:在TRM=0的兼容模式下,你以为只在操作TREG0,实际上TREG1和TREG2也被悄悄修改了。如果你的C5x新代码同时使用了TREG1(例如,用于SUBT指令的动态移位)或TREG2,就会产生难以调试的冲突。因此,对于全新的C5x项目,建议将TRM位设置为1,让TREG0/1/2独立工作,避免隐性副作用。
- 判断方法:查看状态寄存器ST1的TRM位。使用
LST #1指令可以加载ST1,其中就包含TRM位。
5.3 寻址模式对执行效率的影响
所有涉及数据存储器访问的指令(如SPH, SPL, LTS, SQRS),其执行周期都高度依赖于寻址模式和存储器类型。这里总结一个快速参考:
| 寻址模式 | 操作数位置 | 典型周期数 (单次) | 说明 |
|---|---|---|---|
| 直接/间接 | 片内DARAM | 1 | 最优情况,单周期完成。 |
| 间接 | 片内SARAM | 1 或 2 | 如果指令代码和数据在同一SARAM块,需2周期(访存冲突)。 |
| 间接 | 外部存储器 | 1+d+p | d和p为等待状态,周期数大幅增加。 |
| RPT + 间接 | 片内DARAM | n (循环体) | 第一次开销较大,后续每次循环接近1周期,极高效率。 |
| RPT + 间接 | 外部存储器 | n+nd+... | 效率极低,应绝对避免。 |
优化黄金法则:
- 核心算法数据放DARAM:将最内层循环访问的数组、系数表放在DARAM中。
- 积极使用间接寻址和RPT:对于数组遍历,
*ARx+这类间接寻址配合RPT指令是性能利器。 - 避免循环内的外部存储器访问:如果无法避免,考虑使用DMA(直接存储器访问)将数据批量搬运到片内RAM后再处理。
6. 实战应用:构建一个块FIR滤波器
让我们用一个具体的例子——块FIR滤波器——来串联SPAC、SPH及相关指令的应用。假设我们需要实现一个N阶FIR滤波器,每次处理一个数据块。
6.1 算法结构与寄存器规划
FIR滤波器的差分方程是:y[n] = Σ (h[i] * x[n-i]), i=0 to N-1。 我们采用循环缓冲区来管理输入样本x[n]和系数h[i]。
- AR0:指向当前最新输入样本的指针。
- AR1:指向滤波器系数h[0]的指针(系数通常固定在DARAM中)。
- AR2:作为辅助指针,或用于存储输出。
- ACC:用于累加乘积和。
- PREG:存放每个h[i]*x[n-i]的乘积。
- PM:设置为1(左移1位),因为我们将系数和样本都视为Q15小数,期望结果也是Q15。
我们假设输入样本块和输出样本块的首地址已定义,N=64。
6.2 汇编代码实现与逐行解析
; 函数: block_fir ; 输入: AR0 -> 输入块地址, AR1 -> 系数表地址, AR2 -> 输出块地址 ; BK = N (滤波器阶数,这里为64) ; 使用: ACC, PREG, TREG0, AR0, AR1, AR2, ARP ; PM 模式: 1 (左移1位,用于Q15) block_fir: SPM 1 ; 设置乘积模式为左移1位,用于Q15小数运算 LAR AR2, #OutputBuffer ; 加载输出缓冲区地址到AR2 (假设已定义) MAR *, AR0 ; 设置ARP为AR0,准备操作输入样本 RPT #BlockSize-1 ; 对输出块中的每一个样本点进行循环 || ; 内嵌循环:计算一个输出样本y[n] ZAC ; 清空ACC,为累加做准备 RPT #N-1 ; 重复N次,进行N阶卷积和 MAC *0+, *1+, A ; 关键指令!AR0环绕寻址读x,AR1线性寻址读h,乘加至ACC ; MAC指令执行过程: (PREG移位后加到ACC) && (TREG0=(*AR0)) && (PREG=(*AR1)*TREG0) ; 第一次循环时,PREG是旧值,但ACC已清零,所以无影响。 APAC ; 将最后一次循环产生的PREG(移位后)加到ACC上 ; 此时ACC中即为滤波结果,但它是32位Q31格式。 SACH *2+, 1 ; 将ACC的高16位(左移1位后)存入AR2指向的内存,然后AR2+1 ; SACH *,1 操作: 将ACC[31:16](即高16位)左移1位后存储。因为PM=1时乘积已左移1位, ; 且MAC/APAC后ACC是Q31,左移1位再取高16位,正好得到Q15结果。 NOP ; 流水线保护或对齐(视情况需要) ; RPT外层循环结束,处理下一个输出样本 RET代码解析与指令协同:
SPM 1:奠定了整个滤波运算的定标基础。所有后续从PREG到ACC的传输(在MAC和APAC指令中隐含发生)都会自动左移1位。MAC *0+, *1+, A:这是核心。在一个周期内,它完成了:- 通过
*0+以循环缓冲区方式读取样本x(AR0在BK范围内自动环绕)。 - 通过
*1+线性读取系数h(AR1递增)。 - 将两者相乘,结果放入PREG(并自动根据PM=1左移1位)。
- 将上一次循环计算出的、并已左移1位的乘积(在PREG中)加到ACC中。
- 第一个MAC执行时,加的是PREG的初始值(可能是0或垃圾值),但由于前面执行了
ZAC,所以不影响。
- 通过
APAC:在RPT #N-1循环结束后,最后一次乘法结果还在PREG中,尚未加到ACC。APAC指令完成这最后一次加法。SACH *2+, 1:将ACC中的32位Q31结果转换为16位Q15并存储。SACH指令将ACC的高16位(bit 31-16)存储到内存。选项“1”表示在存储前将ACC内容左移1位。为什么?因为此时ACC是Q31,左移1位后,其bit 30-15就变成了有效的Q15格式结果,取高16位存储即可。这与SPM 1+SPH的组合效果是等价的,但SACH更高效。
在这个例子中,我们没有直接使用SPAC或SPH,但理解了MAC和APAC(它们内部包含了SPAC的加法版本和PREG移位逻辑),以及SACH(它完成了类似SPH的存储功能,但针对ACC),就能完全掌握其原理。如果需要减法版本的滤波器(如在自适应滤波的误差计算中),则可以将内层循环的MAC替换为LTS或SQRS,并移除APAC,因为LTS/SQRS已经包含了减法操作。
6.3 性能优化与常见陷阱
- 循环展开:对于阶数N不是特别大的情况,可以手动展开几次内层RPT循环,减少循环开销。但要注意指令缓存的影响。
- 内存对齐:确保系数表和样本缓冲区在内存中正确对齐(特别是DARAM的边界),有时可以避免硬件等待。
- PM位管理:这是最大的陷阱之一。如果这个滤波器函数被一个使用不同PM模式(例如PM=3用于能量计算)的函数调用,必须在入口保存ST1(包含PM),并在退出时恢复。或者,在函数开头强制
SPM 1,但前提是调用者不依赖之前的PM状态。 - ACC溢出:长脉冲响应(大的N)或大输入信号可能导致ACC溢出。虽然PM=3(右移6位)可以缓解,但会损失精度。更好的方法是使用块浮点技术:在累加过程中监测ACC的数值范围,在必要时对整个数据块进行缩放(右移),并记录缩放因子。
7. 调试技巧与状态位监控
在底层DSP编程中,肉眼检查代码和逻辑正确后,调试阶段往往需要深入芯片内部。理解SPAC、SPH等指令对状态位的影响,是设置有效断点和诊断问题的关键。
7.1 关键状态位诊断清单
当算法结果异常时,可以按以下顺序检查状态位:
OV(溢出位):这是第一个要检查的位。如果OV=1,说明ACC在某个点发生了溢出。你需要判断这是算法固有的(需要调整定标或使用保护位),还是由于bug(如错误的系数或数据)导致的。
- 检查方法:在关键计算指令(如MAC, APAC, SPAC)后设置断点,观察ACC的值是否接近32位有符号数的极限(±2^31)。
- 工具使用:在CCS(Code Composer Studio)等IDE中,可以设置当OV位被置位时触发断点,非常有用。
C(进位位):在减法指令(SPAC, SUBS, SUBB等)后,C位表示借位。在多精度减法例程中,需要根据C位决定是否向更高位借位。如果发现多精度计算错误,检查减法序列后的C位状态是必要的。
PM(乘积移位模式):这是最隐蔽的错误源之一。算法可能因为PM位被意外修改而整体增益错误。
- 检查方法:在算法开始、结束以及任何调用子函数前后,打印或观察ST1寄存器的值。确认PM位始终符合预期。
- 防御性编程:在关键函数的入口和出口,显式地保存和恢复ST1寄存器。
SXM(符号扩展模式):虽然SPAC不受SXM影响,但许多其他算术指令(如ADD, SUB, LACC)受其影响。如果处理的数据在无符号数和有符号数之间切换,SXM设置错误会导致数据解释完全错误。
7.2 仿真器与实时调试实战
现代DSP开发环境提供了强大的仿真功能。
- 寄存器窗口:实时监控ACC、PREG、TREG0-2、AR0-AR7以及ST0/ST1的值。单步执行指令,观察每条指令执行后这些寄存器的变化,是理解指令行为最直观的方式。例如,单步执行一条
SPAC指令,观察ACC和PREG的变化,以及C/OV位的跳变。 - 内存查看器:配合SPH/SPL指令,查看数据存储器中目标地址的值是否正确。你可以预先在内存中设置好测试数据,运行代码后验证结果。
- 图形化显示:对于信号处理算法,将输入信号、系数和输出信号在时域或频域用图形显示出来,能快速定位是某个特定指令的问题,还是整体算法逻辑的问题。如果输出信号幅值异常大或小,首先怀疑PM位设置和定标问题。
- 性能计数器(Profiler):对于包含SPH/SPL的循环,使用性能分析工具查看其是否达到了预期的单周期执行效率。如果发现某个循环耗时远超预期,很可能是因为数据或代码被错误地放置在了外部慢速存储器中。
7.3 常见问题速查表
| 现象 | 可能原因 | 排查步骤 |
|---|---|---|
| 滤波结果增益错误(如放大64倍或1/64) | PM位设置错误。例如该用PM=1(Q15)时误设为PM=3(右移6位)。 | 检查算法起始处的SPM指令,并单步跟踪ST1中PM值的变化。 |
| 存储到内存的乘积高/低字节错乱 | SPH和SPL的执行顺序不当,或在左移模式下理解错了填充位来源。 | 根据PM模式,手动计算一次PREG移位后的高16位和低16位,与SPH/SPL结果对比。确保存储顺序符合算法需求。 |
| 在多精度运算中减法结果错误 | 忽略了SPAC或SUBB指令对C(借位)位的影响,后续的带借位减法未正确处理C位。 | 单步执行减法序列,观察每次减法后C位的值,并与手动计算的理论借位对比。 |
| 使用RPT循环执行SPH/SPL时性能不达预期(非单周期) | 目标数据缓冲区或指令代码未放置在片内DARAM,或者放在了同一个SARAM块导致冲突。 | 检查链接器命令文件(.cmd),确认相关数据段和代码段被正确分配到DARAM。 |
| 算法在大部分时间正确,偶尔出现野值 | ACC溢出(OV=1)且OVM=0,导致结果绕回(wrap-around)。或者是AR指针在循环缓冲区中计算错误,导致访问了错误的数据。 | 使能溢出饱和(设置OVM=1),或增加算法保护位(如用40位累加器如果支持)。检查循环缓冲区指针的更新和边界(BK)设置。 |
| 从C2x移植的代码运行异常 | TRM位未正确设置。C2x代码可能依赖TRM=0(TREG0加载同时更新TREG1/2)的特性。 | 在初始化代码中清除TRM位(LST #1, #xxxx将ST1的TRM位清零),或修改代码使其不依赖该特性。 |
理解SPAC、SPH、SPL等指令,不仅仅是记住它们的语法和操作,更是要掌握其背后数据通路的运作、状态位的互动以及在完整算法中的角色。通过将理论分析与像FIR滤波器这样的实战案例相结合,并运用系统的调试方法,你就能真正驾驭这些指令,编写出既高效又可靠的DSP底层代码。最终,所有的优化和调试都服务于一个目标:让信号数据在芯片内的流动如同精心设计的交响乐,每个指令都是一个精准的音符,共同奏出实时处理的乐章。