1. 背景
在传统的嵌入式开发中,如果我们需要在一颗小小的 MCU 上跑一些高强度的运算,常规做法是:
- 借助库函数或手写汇编进行手动优化;
- 或是在硬件设计层面,通过额外的外设或协处理器(crypto, DSP) 助力,完成特定功能加速。
但随着对性能需求越来越苛刻,硬件资源又不想失去 Arm 生态的广泛支持,怎么办? 这时候,“自定义指令” (或称自定义加速单元) 就闪亮登场了:
- 具备灵活性: 允许根据应用需求把常用的算法指令“写入”CPU,以极低延迟操作处理器寄存器。
- 兼容生态: 不必魔改编译器、无须大改调试器;保持与现有 Arm 工具的融洽相处。
zidian 就是这样一个在 G32R501 MCU 上基于 ACI 机制打造的“加速引擎”,主要目标是——极速飙升运算能力,让原本需要几十条甚至上百条指令才能搞定的运算,缩短到寥寥几条。
2. 不得不说ACI:一把开启硬件定制的大门
2.1 ACI 的“前世今生”
ACI 全称 Arm Custom Instructions,它让芯片厂商能够在 Cortex-M33、Cortex-M55、Cortex-M52 等处理器上,自定义数据处理指令集。
- 传统做法中,也有借助“Coprocessor Interface (协处理器接口)”实现硬件加速,但它往往需要独立管理寄存器、内存访问,适合批量处理或在后台执行的场景。
- ACI 则进一步贴近 CPU 寄存器流水线,“紧耦合”地完成特定操作,非常适合短周期、高频调用的计算需求。
简单来说,ACI 就是为 MCU 打开了一扇“能深度定制指令的门”,保留了标准 Arm 生态的同时,提供了更细粒度的指令级优化。

2.2 ACI 的实现方式
在硬件层面,Arm 针对协处理器指令编码区,预留了部分空间让厂商编写自己的操作:
- 指令解码阶段: CPU 内部会检测该指令是否隶属某个 coprocessor number;若与 ACI 相关,则交由“定制数据通路”进行计算;
- 数据读写: CPU 读取需要的寄存器 (整数寄存器或浮点寄存器),然后将数据送给 ACI 硬件模块;
- 获取结果: 结果再写回 CPU 寄存器;若支持更新 APSR 的某些标志位 (如NZCV) 也可以在此阶段完成。
2.3 ACI 指令风格
在 Arm v8-M 架构里,ACI 以类似于 “CXn” 或 “VCXn” 的形式呈现:
- “CX” 前缀常对应整数指令;
- “VCX” 前缀常对应浮点 (FPU) 或向量 (MVE);
- n 可取 1, 2, 3 等,表示几个输入寄存器;
- 后面可能有 “A” 表示累加模式 (accumulate),或 “D” 表示双寄存器/双精度。

指令后面依次跟的是
- Coprocessor #,协处理器编号;
- Destination,目的寄存器;
- Source,源寄存器;
- Immediate data value,一个立即数值,可以说是指令的编号。
工程师只需在编译器中,通过类似 “-mcpu=cortex-m52+cdecp0” 的编译选项告诉工具链:
“哥们,我要用 coprocessor 0 做我的自定义指令啦~”
然后我们就能在 C 里写类似内置函数:
// Example in C, with English comments
uint32_t custom_operation(uint32_t input)
{
// Coprocessor number=0, immediate=0, e.g. simple operation
return __arm_cx1(0, input, 0);
}
这段看似普通的 C 函数,在编译后就会生成一条“CX1”形式的自定义指令,最终交由硬件中的 ACI 通路执行。这就是 ACI 的魅力之处:紧贴标准工具链,却能完成极具针对性的硬件加速。
2.4 zidian登场
了解了 ACI,我们就能理解 zidian 是如何工作的:
- 在 G32R501 中,zidian 将常见的数学计算 (如 sin、cos、atan、sqrt 甚至一些 CRC/FFT 相关) 封装为“专用指令”,让应用层只需调用 zidian_math.h 提供的函数,即可调用这些定制指令;
- 原理上,zidian 就是利用了 Cortex-M 架构中的 ACI 能力,结合自家的协处理器号 (coproc ID),并封装到一系列“__arm_cxX()”等 intrinsics 中。
3. zidian:Geehy家族的“小宇宙”
3.1 zidian 能加速哪些运算?
针对 G32R501,zidian 提供了典型的整数和浮点加速:
-
整数 (ICAU):
- SIMD 相关运算 (如复数乘法、FFT)
- CRC 算法加速
- 一些特殊位操作
-
浮点 (FCAU):
- sin, cos, atan, atan2 等三角函数
- 平方根 sqrtf32
- 除法、复数比值等
在头文件“zidian_math.h”里,你会看到类似这样的函数:
__sinpuf32()
-
__cos()
__atan2puf32()
__sqrtf32()
__divf32()
这些看似普通的函数背后,其实就是 zidian 对 ACI 的进一步封装,令开发者无缝使用。
在头文件“zidian_cde.h”里,你会看到G32R501支持的全部指令:
// CX2
#define __tas(x) __arm_cx2(0x0, x, 0x1)
#define __rstatus(x) __arm_cx2(0x0, x, 0x2)
#define __wstatus(x) __arm_cx2(0x0, x, 0x3)
#define __neg(x) __arm_cx2(0x0, x, 0x4)
// CX2A
#define __crc8l(y, x) __arm_cx2a(0x0, y, x, 0x8)
#define __crc16p1l(y, x) __arm_cx2a(0x0, y, x, 0x9)
#define __crc16p2l(y, x) __arm_cx2a(0x0, y, x, 0xa)
#define __crc32l(y, x) __arm_cx2a(0x0, y, x, 0xb)
#define __crc8h(y, x) __arm_cx2a(0x0, y, x, 0xc)
#define __crc16p1h(y, x) __arm_cx2a(0x0, y, x, 0xd)
#define __crc16p2h(y, x) __arm_cx2a(0x0, y, x, 0xe)
#define __crc32h(y, x) __arm_cx2a(0x0, y, x, 0xf)
// CX2DA
#define __tast(y, x) __arm_cx2da(0x0, y, x, 0x40)
// CX3
#define __sh1add(x1, x2) __arm_cx3(0x0, x1, x2, 0x8)
#define __sh1addi(x1, x2) __arm_cx3(0x0, x1, x2, 0x18)
#define __sh1addexh(x1, x2) __arm_cx3(0x0, x1, x2, 0xa)
#define __sh1addexhi(x1, x2) __arm_cx3(0x0, x1, x2, 0x1a)
#define __sh1addexl(x1, x2) __arm_cx3(0x0, x1, x2, 0xb)
#define __sh1addexli(x1, x2) __arm_cx3(0x0, x1, x2, 0x1b)
#define __sh1sub(x1, x2) __arm_cx3(0x0, x1, x2, 0xc)
#define __sh1subi(x1, x2) __arm_cx3(0x0, x1, x2, 0x1c)
#define __sh1subexh(x1, x2) __arm_cx3(0x0, x1, x2, 0xe)
#define __sh1subexhi(x1, x2) __arm_cx3(0x0, x1, x2, 0x1e)
#define __sh1subexl(x1, x2) __arm_cx3(0x0, x1, x2, 0xf)
#define __sh1subexli(x1, x2) __arm_cx3(0x0, x1, x2, 0x1f)
// CX3D
#define __hstas(x1, x2) __arm_cx3d(0x0, x1, x2, 0x20)
#define __hstsa(x1, x2) __arm_cx3d(0x0, x1, x2, 0x21)
#define __lstas(x1, x2) __arm_cx3d(0x0, x1, x2, 0x22)
#define __lstsa(x1, x2) __arm_cx3d(0x0, x1, x2, 0x23)
#define __max(x1, x2) __arm_cx3d(0x0, x1, x2, 0x25)
// CX3DA
#define __hbitsel(y, x1, x2) __arm_cx3da(0, y, x1, x2, 0x2)
// VCX2
#define __rd_scr(x) __arm_vcx2_u32(0x0, x, 0x6)
#define __wr_scr(x) __arm_vcx2_u32(0x0, x, 0x7)
...
举个栗子,对于 sinf
函数:
- 开启zidian后,将调用相关的
vcx
指令;
- 关闭zidian时,使用的是arm的
hardfp_sinf
函数

3.2 使用 zidian 的小贴士
-
在编译器启用 ACI: MDK-ARM: 加入 -mcpu=cortex-m52+cdecp0
IAR: 在 Extra Options 里加 --cdecp=0
-
在预编译宏中,定义 __ZIDIAN_FCAU__
(若需要对浮点库进行替换)。

-
在 C 代码中 #include "zidian_math.h"
,然后大胆调用 sinf()
, cosf()
, sqrtf()
, atan2f()
等。
-
编译后,一旦检测到宏开关及指令支持,就会将这些函数替换为 zidian 自定义指令的版本。
4. 来跑个分吧:加速前后的对比
接下来就是见证奇迹的时刻啦~咱们设计一个场景,对比在未使用 zidian 和 使用 zidian 两种情况下的耗时表现。
4.1 设计“超级复杂”的测试驱动
所谓“超级复杂”,可以理解为“重复调用多种函数 + 大循环”。当函数被调用几千几万次后,任何一丁点优化都可能带来大量时间差。CRC 和浮点运算 (sinf/cosf/sqrtf/atanf) 都是嵌入式常见场景,此例就足以说明大规模调用带来的性能落差。
在 G32R501 的 ITCM 区,我们放置了代码,并借助 DWT 计数器测量执行周期数。
整体思路包括:
- test_data[ ] 用于 CRC32 计算;
- float_data[ ] 存放 0 ~ (FLOAT_COUNTS-1) 的弧度数;
- 在循环中分别调用 dsp_crc32_test() 和 dsp_trig_test(),用 GET_DWT_CYCLE_COUNT() 宏记录执行前后的周期值;
- 最后打印计算时间与结果值,以便观测差异。
4.2 参考代码
以下是一整套 zidian_ex1_math.c 文件示例,你可以直接参照使用。
- 其中文件开头包含了相关头文件 driverlib、zidian_math.h 等;
- 代码中用到 GET_DWT_CYCLE_COUNT() 这一宏来简化测量流程;
- dsp_crc32_test() 和 dsp_trig_test() 会根据宏判断走硬件还是纯软件逻辑。
请留意,示例使用了 SECTION_DTCM_DATA,将数组放入 DTCM(ITCM) 中,以减少访问延迟。
实际项目中可根据需要放在合适的内存区域。
//
// Globals
//
SECTION_DTCM_DATA
volatile uint32_t dwtCycleCounts[2];
// 定义测试规模
#define TEST_SIZE 512
#define FLOAT_COUNTS 5000
// CRC32 的常量
#define CRC32_POLY 0x04C11DB7U
#define CRC32_INIT 0xFFFFFFFFU
#define CRC32_XOROUT 0xFFFFFFFFU
// 全局数组 (放在DTCM中)
SECTION_DTCM_DATA
static uint8_t test_data[TEST_SIZE];
SECTION_DTCM_DATA
float float_data[FLOAT_COUNTS];
// 函数声明
uint32_t dsp_crc32_test(const uint8_t *data, uint32_t length);
float dsp_trig_test(void);
//
// Main
//
void example_main(void)
{
printf("\nG32R501 EVAL zidian test!\n");
#if defined(__zidian_FCAU__)
printf("zidian : ENABLED (Hardware Acceleration)\n");
#else
printf("zidian : DISABLED (Pure Software)\n");
#endif
//
// 初始化测试数据
//
for(int i = 0; i < TEST_SIZE; i++)
{
test_data[i] = (uint8_t)i;
}
for(int i = 0; i < FLOAT_COUNTS; i++)
{
// 用 (i / 180.0f) * PI 模拟不同弧度
float_data[i] = (float)(i) / 180.0f * 3.1415926f;
}
//
// 测试
//
// 1) 测量 CRC32
uint32_t crcResult;
GET_DWT_CYCLE_COUNT(dwtCycleCounts[0],
crcResult = dsp_crc32_test(test_data, TEST_SIZE));
// 2) 测量 三角函数测试
volatile float trigResult = 0.0f;
GET_DWT_CYCLE_COUNT(dwtCycleCounts[1],
trigResult = dsp_trig_test());
printf("CRC Cycles : %u\n", dwtCycleCounts[0]);
printf("Trig Cycles : %u\n", dwtCycleCounts[1]);
printf("CRC Value : 0x%X\n", crcResult);
printf("Trig Value : %.6f\n", trigResult);
//
// Loop
//
for(;;)
{
}
}
// 硬件加速 还是 软件版本 取决于编译宏
#if defined(__zidian_FCAU__)
//------------------------------
// 硬件版CRC
//------------------------------
uint32_t zidian_crc32(const uint8_t *data, uint32_t length)
{
uint32_t crc = CRC32_INIT;
for(uint32_t i = 0; i < length; i++)
{
// 专用zidian指令
crc = __crc32l(crc, data[i]);
}
crc ^= CRC32_XOROUT;
return crc;
}
#else
//------------------------------
// 纯软件版CRC
//------------------------------
uint32_t zidian_crc32(const uint8_t *data, uint32_t length)
{
uint32_t crc = CRC32_INIT;
for(uint32_t i = 0; i < length; i++)
{
crc ^= ((uint32_t)data[i]) << 24;
for(int bit = 0; bit < 8; bit++)
{
if(crc & 0x80000000U)
crc = (crc << 1) ^ CRC32_POLY;
else
crc <<= 1;
}
}
crc ^= CRC32_XOROUT;
return crc;
}
#endif
// 浮点测试函数
float dsp_trig_test(void)
{
volatile float result = 0;
for(int j = 0; j < FLOAT_COUNTS; j++)
{
// 连续调用一下各种函数
result += sinf(float_data[j]);
result += cosf(float_data[j]);
result += sqrtf(float_data[j]);
result += atanf(float_data[j]);
// 还来一次 atanf
result += atanf(float_data[j]);
}
return result;
}
4.3 实测结果
将代码放置至ITCM RAM中:

仿真环境下运行代码:

在此上面的代码下,我们采集到如下跑分数据(截取自典型测量结果):

• 未启用 zidian(纯软件模式,zidian : DISABLED):
- CRC Cycles : 114234
- Trig Cycles : 2522328
- CRC Value : 0x2F728526
- Trig Value : 46191.281250
• 启用 zidian(硬件加速模式,zidian : ENABLED):
- CRC Cycles : 12319
- Trig Cycles : 1250023
- CRC Value : 0x2F728526
- Trig Value : 46191.285156
从结果中可以清晰看到,开启 zidian 后的 CRC 计算从 114234 个周期骤降至 12319 个,性能提升近 9 倍;浮点函数测算也从约 252 万周期降低到约 125 万周期,缩短了一半以上的执行时间。 更值得注意的是,CRC 结果在两种模式下完全一致,Trig 结果也仅在浮点小数位上有细微差异,可见 zidian 并不会破坏运算精度。
综上可知,zidian 的硬件“外挂”确实能大大缓解 CPU 在处理大规模 CRC 和浮点函数时的性能压力,让 G32R501 整体算力上“飞”了一大截。这正是 zidian 的魅力所在:在几乎不改变业务逻辑的情况下,给你的 MCU 算力打了一个“超神 Buff”。
5. 结语:迈向高效与优雅的新世界
通过 ACI 接口,zidian 在 G32R501 上为我们打开了一扇“更高效”的大门。无论是 CRC 还是三角函数,都只要在编译时简单切换一个宏,就能把重复的“负担”挪给硬件,让 MCU 的主吃力大大减轻。更妙的是,这一切并不需要你手动改写繁琐的汇编,也不必妥协精度——堪称“一步到位”的加速方案。
当然,zidian 能做的事情或许不止这些,如果你的项目中大量使用了 DSP 之类的运算,也可以挖掘更多潜力。毕竟,当你尝到硬件加速的甜头后,就会发现那些曾经令人头疼的性能瓶颈,或许只是一段 __crc32l()
或 __arm_cx2(…)
指令的距离。
参考代码:
附件:zidian_ex2_test.rar,请解压至 G32R501_SDK_V1.1.0\driverlib\g32r501\examples\eval\zidian\
参考文档:
- arm-custom-instructions-wp.pdf
- arm-custom-instructions-without-fragmentation-whitepaper.pdf
- AN1132_G32R501 zidian应用笔记 V1.0.pdf
以上便是本次分享的全部内容啦,欢迎各位在评论区留下你的想法吧!