Cortex-M33处理器的数字信号处理
1. 微控制器上的DSP
数字信号处理(DSP)涵盖了广泛的数学密集型算法,包括音频、视频、测量和工业控制等应用。自2010年引入带有DSP扩展的Arm® Cortex® - M4处理器以来,基于Cortex - M的系统在信号处理应用中的使用显著增加。
Cortex - M处理器在信号处理方面有诸多应用:
-
音频相关
:如便携式音频播放器,2019年Cortex - M4系统用于亚马逊Alexa语音服务(AVS)。
-
传感器融合
:用于手机和可穿戴设备。
-
图像处理
:例如OpenMV项目利用Cortex - M7微控制器处理图像。
-
声音检测与分析
:如Audio Analytic的ai3™声音检测软件运行在入门级Cortex - M0处理器上。
-
振动分析
:用于预测性维护。
不同的Cortex - M处理器家族成员具有不同的信号处理能力,如下表所示:
| 处理器型号 | 乘加(MAC)、饱和调整 | DSP扩展(SIMD、单周期MAC、饱和算术指令) | Helium(M - 配置文件向量扩展) |
| — | — | — | — |
| Cortex - M0、Cortex - M0+、Cortex - M23 | - | - | - |
| Cortex - M3 | 是 | - | - |
| Cortex - M4、Cortex - M7 | 是 | 是 | - |
| Cortex - M33 | 是 | 可选 | - |
| Cortex - M55 | 是 | 是 | 可选 |
Cortex - M33处理器可选支持与Cortex - M4和Cortex - M7相同的DSP指令集,这些指令可加速数值算法,实现直接在Cortex - M处理器上进行实时信号处理,无需外部数字信号处理器。
2. 为何在DSP应用中使用Cortex - M处理器
在具有DSP功能的现代微控制器出现之前,执行数字信号处理应用时,首先想到的是数字信号处理器(DSP)。DSP的架构针对算法中的数学运算进行了优化,但在一些嵌入式应用需求上表现不佳。
而微控制器具有通用性,擅长控制任务,如与外设接口、处理用户界面和实现通用连接。微控制器拥有广泛的外设,便于与其他传感器和IC接口,并且在便携式产品中有悠久的应用历史,更注重降低功耗和提供出色的代码密度。不过,许多传统微控制器在执行复杂数学算法方面能力不足,因为它们缺乏寄存器和支持这些计算的指令集。
近年来,连接设备的兴起使得产品需要同时具备微控制器和DSP的功能。传统上,这类设备要么使用两个独立芯片(通用微控制器和数字信号处理器),要么使用包含两者的单芯片。
现代基于Cortex - M的微控制器,特别是支持DSP指令扩展的,为需要一定DSP处理能力的应用带来了诸多优势:
-
降低成本
:无需在电路板上使用专用DSP芯片,降低产品和设计成本。
-
简化开发
:嵌入式产品开发人员可以使用单一开发工具链开发整个应用。
-
减少软件复杂度
:所有软件任务在单个处理器系统上运行。
-
增强安全性
:Cortex - M处理器的IoT安全管理功能(如TrustZone)对信号处理应用(如处理指纹等生物识别数据)很有用。
-
支持多线程
:可利用RTOS支持功能处理多个信号处理线程,上下文切换开销小。
一些微控制器具有多个Cortex - M处理器,可将一个或多个处理器专门用于DSP处理,其他处理器用于运行通信栈、处理用户界面等任务。并且,软件开发人员可以使用单一工具链进行开发,通过CoreSight调试架构,多核心调试也更加容易。
此外,CMSIS - DSP库的可用性使得基于Cortex - M的系统进行DSP应用开发更加容易,同时Cortex - M开发板广泛可用,是学习数字信号处理的理想起点。
需要注意的是,Cortex - M33处理器的DSP扩展是可选的。如果芯片的目标应用不涉及信号处理且超低功耗是首要要求,硅芯片设计人员可以省略该扩展。即使省略了DSP扩展,仍有一些乘加指令和饱和调整指令可用。而Cortex - M23处理器没有DSP扩展,但仍可用于轻量级信号处理任务,如语音活动检测和低采样率信号分析。
3. 点积示例
点积操作是将两个向量的元素逐对相乘并累加结果。假设输入x[k]和y[k]是32位值的数组,z使用64位表示。以下是用C语言实现的点积代码:
int64_t dot_product (int32_t *x, int32_t *y, int32_t N) {
int32_t xx, yy, k;
int64_t sum = 0;
for(k = 0; k < N; k++) {
xx = *x++;
yy = *y++;
sum += xx * yy;
}
return sum;
}
点积操作由一系列乘法和加法组成,这种乘加(MAC)操作是许多DSP功能的核心。
在Cortex - M33处理器上执行该算法时,从内存中获取数据并递增指针只需1个时钟周期:
xx = *x++; // 1 cycle (LDR with post increment addressing mode)
yy = *y++; // 1 cycle
与Cortex - M4处理器相比,Cortex - M33在内存访问操作上稍快。乘加操作(32×32 + 64)可以用单条指令完成,但如果乘法操作的输入数据(不包括累加器值)是在上一条指令加载到寄存器组中的,会有1个时钟周期的加载 - 使用惩罚。
循环本身会引入额外开销,通常包括递减循环计数器和分支到循环开始处。在Cortex - M33处理器上,点积的内循环需要8个时钟周期,执行时间取决于数据:
xx = *x++; // 1 cycle
yy = *y++; // 1 cycle
sum += xx * yy; // 2 cycles (1 cycle pipeline due to load - use penalty)
(loop overhead) // 4 cycles including loop counter update and compare
整个点积函数可以用汇编代码重写:
int64_t __attribute__((naked)) dot_product (int32_t *x, int32_t *y, int32_t N)
{
__asm(
"PUSH {R4 - R5}\n\t" /* 2 cycle */
"MOVS R4, #0\n\t" /* 1 cycle */
"MOVS R5, #0\n\t" /* 1 cycle */
"loop: \n\t"
"LDR R3 , [R0], #4\n\t" /* 1 cycle */
"LDR R12, [R1], #4\n\t" /* 1 cycle */
"SMLAL R4, R5, R3, R12\n\t" /* 2 cycles - pipeline hazard */
"SUBS R2, R2, #1\n\t" /* 1 cycle */
"BNE loop\n\t" /* 3 cycles */
"MOVS R0, R5\n\t" /* 1 cycle */
"MOVS R1, R4\n\t" /* 1 cycle */
"POP {R4 - R5}\n\t" /* 2 cycle2 */
"BX LR\n\t"); /* 3 cycle2 */
}
通过简单的指令重新调度,可以消除SMLAL的流水线气泡,将内循环减少到7个时钟周期:
int64_t __attribute__((naked)) dot_product (int32_t *x, int32_t *y, int32_t N)
{
__asm(
"PUSH {R4 - R5}\n\t" /* 2 cycle */
"MOVS R4, #0\n\t" /* 1 cycle */
"MOVS R5, #0\n\t" /* 1 cycle */
"loop: \n\t"
"LDR R3 , [R0], #4\n\t" /* 1 cycle */
"LDR R12, [R1], #4\n\t" /* 1 cycle */
"SUBS R2, R2, #1\n\t" /* 1 cycle – placed between LDR and SMLAL*/
"SMLAL R4, R5, R3, R12\n\t" /* 1 cycle */
"BNE loop\n\t" /* 3 cycles */
"MOVS R0, R4\n\t" /* 1 cycle */
"MOVS R1, R5\n\t" /* 1 cycle */
"POP {R4 - R5}\n\t" /* 2 cycles */
"BX LR\n\t"); /* 3 cycles */
}
利用循环展开可以进一步减少循环开销。如果向量长度是4的倍数,可以将循环展开4倍。计算4个样本需要15个时钟周期,即Cortex - M33上每个样本的点积操作仅需3.75个时钟周期,而Cortex - M4处理器需要4.75个时钟周期,Cortex - M3处理器需要7.75 - 11.75个时钟周期。
int64_t __attribute__((naked)) dot_product (int32_t *x, int32_t *y, int32_t N)
{
__asm(
"PUSH {R4 - R11}\n\t" /* 8 cycle */
"MOVS R3, #0\n\t" /* 1 cycle */
"MOVS R4, #0\n\t" /* 1 cycle */
"loop: \n\t"
"LDMIA R0 , {R5 - R8}\n\t" /* 4 cycles */
"LDMIA R1 , {R9 - R12}\n\t" /* 4 cycles */
"SMLAL R3, R4, R5, R9\n\t" /* 1 cycle */
"SMLAL R3, R4, R6, R10\n\t" /* 1 cycle */
"SMLAL R3, R4, R7, R11\n\t" /* 1 cycle */
"SMLAL R3, R4, R8, R12\n\t" /* 1 cycle */
"SUBS R2, R2, #4\n\t" /* 1 cycle */
"BNE loop\n\t" /* 3 cycle */
"MOVS R0, R3\n\t" /* 1 cycle */
"MOVS R1, R4\n\t" /* 1 cycle */
"POP {R4 - R11}\n\t" /* 8 cycles */
"BX LR\n\t"); /* 3 cycles */
}
需要注意的是,汇编代码示例是手动创建的,C编译器生成的代码可能不同,并且处理所需的时钟周期数取决于内存等待状态和指令在内存中的对齐方式。
4. 利用SIMD指令提高性能
在前面的点积示例中,使用了SMLAL指令(一种MAC操作),即使未实现DSP扩展,该指令也可用。当实现了DSP扩展时,可以利用“单指令多数据”(SIMD)指令功能加速16位点积操作。
例如,如果点积操作基于16位数据而不是32位,可以使用双MAC指令SMLALD。当输入数据数组x[]和y[]都是16位数据时,一次加载操作可以加载两个样本,使得双MAC操作(不包括循环处理开销)仅需3个时钟周期,即每个MAC操作仅需1.5个时钟周期。
利用循环展开技术可以进一步降低平均循环开销,此时循环计数器的递减值从4变为8,因为每个双MAC处理两个数据样本。以下是利用SIMD操作重写的点积汇编代码:
int64_t __attribute__((naked)) dot_product (int16_t *x, int16_t *y, int32_t N)
{
__asm(
"PUSH {R4 - R11}\n\t" /* 8 cycle */
"MOVS R3, #0\n\t" /* 1 cycle */
"MOVS R4, #0\n\t" /* 1 cycle */
"loop: \n\t"
"LDMIA R0 , {R5 - R8}\n\t" /* 4 cycles */
"LDMIA R1 , {R9 - R12}\n\t" /* 4 cycles */
"SMLALD R3, R4, R5, R9\n\t" /* 1 cycle */
"SMLALD R3, R4, R6, R10\n\t" /* 1 cycle */
"SMLALD R3, R4, R7, R11\n\t" /* 1 cycle */
"SMLALD R3, R4, R8, R12\n\t" /* 1 cycle */
"SUBS R2, R2, #8\n\t" /* 1 cycle */
"BNE loop\n\t" /* 3 cycles */
"MOVS R0, R3\n\t" /* 1 cycle */
"MOVS R1, R4\n\t" /* 1 cycle */
"POP {R4 - R11}\n\t" /* 8 cycles */
"BX LR\n\t"); /* 3 cycles */
}
除了点积计算,SIMD双MAC还可用于许多其他DSP处理任务,如有限脉冲响应(FIR)滤波器。
5. 处理溢出问题
在前面的点积示例中,输入数据为32位,累加器为64位。如果MAC操作的输入值接近输入值范围的极限,并且数组元素数量较多,累加操作很容易溢出,因为乘法结果为64位。
许多传统DSP使用比乘法结果更宽的专用累加器寄存器来解决这个问题,例如Analog Devices的SHARC处理器有80位宽的专用累加器寄存器。
Cortex - M33处理器没有专用累加器寄存器,为避免溢出问题,应用开发人员可以限制输入数据的值范围。在大多数信号处理应用中,输入源数据(如音频信号)通常为16 - 24位,如果以定点格式进行数据处理,可以相应地缩放输入数据以减少溢出的可能性。
但在某些情况下,缩放输入数据以防止溢出是不实际的。例如,如果要利用SIMD最大化处理性能,输入数据值已经限制在16位(整数数据范围为 - 32768到 + 36767)。为解决这个问题,Cortex - M33处理器的DSP扩展支持饱和算术。饱和算术操作将运算结果限制在最大值(如16位值的 + 32767)或最小值(如16位值的 - 32768),而不是像标准二进制补码算术那样在溢出时回绕。
Cortex - M33处理器的DSP扩展、其他Armv8 - M主线处理器、Cortex - M4和Cortex - M7处理器支持基本的饱和算术操作。处理器提供饱和加法和减法指令,但没有饱和MAC指令。要执行饱和MAC指令,需要分别进行乘法和饱和加法,这会多消耗一个时钟周期。
Cortex-M33处理器的数字信号处理
6. 利用SIMD和循环展开优化点积运算
在之前介绍的点积示例中,我们已经看到了利用SIMD指令和循环展开技术来提高性能的方法。这里我们进一步分析其优化原理和效果。
利用SIMD和循环展开的点积运算流程如下:
1.
初始化寄存器
:将累加器寄存器初始化为0。
2.
循环处理
:
- 从内存中批量加载数据到寄存器。
- 执行SIMD双MAC操作。
- 更新循环计数器。
3.
结束处理
:将累加结果返回。
下面是利用SIMD和循环展开的点积汇编代码:
int64_t __attribute__((naked)) dot_product (int16_t *x, int16_t *y, int32_t N)
{
__asm(
"PUSH {R4 - R11}\n\t" /* 8 cycle */
"MOVS R3, #0\n\t" /* 1 cycle */
"MOVS R4, #0\n\t" /* 1 cycle */
"loop: \n\t"
"LDMIA R0 , {R5 - R8}\n\t" /* 4 cycles */
"LDMIA R1 , {R9 - R12}\n\t" /* 4 cycles */
"SMLALD R3, R4, R5, R9\n\t" /* 1 cycle */
"SMLALD R3, R4, R6, R10\n\t" /* 1 cycle */
"SMLALD R3, R4, R7, R11\n\t" /* 1 cycle */
"SMLALD R3, R4, R8, R12\n\t" /* 1 cycle */
"SUBS R2, R2, #8\n\t" /* 1 cycle */
"BNE loop\n\t" /* 3 cycles */
"MOVS R0, R3\n\t" /* 1 cycle */
"MOVS R1, R4\n\t" /* 1 cycle */
"POP {R4 - R11}\n\t" /* 8 cycles */
"BX LR\n\t"); /* 3 cycles */
}
通过这种方式,每个双MAC操作仅需1.5个时钟周期,大大提高了点积运算的性能。
7. 饱和算术在信号处理中的应用
饱和算术在信号处理中有着重要的应用,特别是在处理可能出现溢出的情况时。以下是饱和算术在信号处理中的应用场景和优势:
-
音频处理
:在音频信号处理中,输入信号通常为16 - 24位。如果不使用饱和算术,当信号幅度超过表示范围时,会出现回绕现象,导致音频失真。使用饱和算术可以将信号幅度限制在有效范围内,减少失真。
-
图像处理
:在图像处理中,像素值的计算也可能会出现溢出。例如,在图像增强算法中,对像素值进行乘法和加法运算时,使用饱和算术可以避免像素值超出范围,保证图像质量。
饱和算术的操作原理如下:
graph TD;
A[输入值] --> B{是否溢出};
B -- 是 --> C[限制为最大值或最小值];
B -- 否 --> D[保持原值];
C --> E[输出结果];
D --> E;
在Cortex - M33处理器中,虽然没有专门的饱和MAC指令,但可以通过分别进行乘法和饱和加法来实现饱和MAC操作。
8. Cortex - M系列处理器的DSP性能对比
不同型号的Cortex - M处理器在DSP性能上存在差异,以下是它们的性能对比表格:
| 处理器型号 | 点积运算时钟周期(每样本) | 支持的DSP指令 | 饱和算术支持 |
| — | — | — | — |
| Cortex - M3 | 7.75 - 11.75 | 基本乘加指令 | 部分支持 |
| Cortex - M4 | 4.75 | SIMD、单周期MAC、饱和算术指令 | 支持 |
| Cortex - M33 | 3.75(循环展开后) | 可选SIMD、单周期MAC、饱和算术指令 | 支持 |
| Cortex - M55 | 未提及(性能接近现代中低端DSP) | 支持Helium扩展(超150条新指令) | 支持 |
从表格中可以看出,Cortex - M33处理器在点积运算性能上优于Cortex - M3和Cortex - M4,特别是在使用循环展开技术后。而Cortex - M55处理器则通过支持Helium扩展,提供了更强大的信号处理性能。
9. 总结与建议
综上所述,Cortex - M33处理器在数字信号处理方面具有一定的优势,特别是在支持DSP扩展的情况下。以下是一些使用Cortex - M33处理器进行数字信号处理的建议:
-
合理使用DSP扩展
:如果应用需要较高的DSP处理能力,建议启用Cortex - M33处理器的DSP扩展,利用SIMD指令和饱和算术提高性能。
-
优化代码
:通过循环展开、指令重调度等技术优化代码,减少时钟周期,提高处理效率。
-
注意溢出问题
:在处理可能出现溢出的情况时,使用饱和算术来避免结果回绕,保证处理结果的正确性。
-
选择合适的处理器
:根据应用的具体需求,选择合适的Cortex - M处理器。如果对性能要求较高,可以考虑Cortex - M55处理器;如果对功耗要求较高且处理任务较轻,可以选择Cortex - M23处理器。
希望以上内容对您在Cortex - M33处理器的数字信号处理应用中有所帮助。
超级会员免费看
39

被折叠的 条评论
为什么被折叠?



