NPU 内联汇编¶
bisheng 使用 GNU extended asm。内联的模板由 AI Core 汇编器处理,因此模板中写的是 NPU 指令,不是 host x86/ARM 指令。
无显式输出的 Vector 示例¶
typedef short __attribute__((address_space(6))) *ubptr;
void inline_vadd(ubptr dst, ubptr src0, ubptr src1,
unsigned long config) {
__asm__ volatile(
"VADD.s16 [%0], [%1], [%2], %3, MASK"
:
: "l"(dst), "l"(src0), "l"(src1), "l"(config)
: "memory");
}
生成结果:
显式输出的 Scalar 示例¶
unsigned long inline_add(unsigned long a, unsigned long b) {
unsigned long out;
__asm__ volatile(
"ADD.s64 %0, %1, %2"
: "=l"(out)
: "l"(a), "l"(b));
return out;
}
常见分配结果为:
约束¶
| 写法 | 含义 |
|---|---|
"l"(value) |
HiIPU 标量/地址寄存器输入 |
"=l"(value) |
只写输出 |
"+l"(value) |
读写同一个值 |
%0、%1 |
第 0、第 1 个操作数的汇编占位符 |
"memory" |
通知优化器该 asm 可能读写未显式列出的内存 |
当前 c220 后端对普通 "r" 约束处理不兼容,可能报告 inline 类型或寄存器类错误。NPU 的地址和 64 位标量操作数应使用已验证的 "l" 约束。
volatile 与 memory¶
- 没有 C 可见输出的指令应使用
volatile,避免被删除或合并。 - 访问 UB、GM、L1、L0 或隐式硬件状态时通常需要
"memory"clobber。 "memory"只约束编译器重排,不会自动插入 AI Core 的事件或屏障。- 如果 asm 只做纯标量计算并准确声明所有输入输出,可不使用
"memory"。
隐式状态¶
MASK、CMPMASK、SPR、事件槽、Cube 累加状态等无法完全通过普通 C 输入输出表达。相关操作应:
- 放在同一个 asm 块中,或用 C 可见依赖连接;
- 保留必要的
volatile与memory; - 不与编译器生成的同步序列交叉重排;
- 使用生成汇编确认最终寄存器和顺序。
配置寄存器示例¶
命名 SPR 也可以通过 "l" 约束读写。下面两段在 dav-c220-vec 上分别生成 MOV X0, CTRL 和 MOV CTRL, X0:
unsigned long read_ctrl_inline(void) {
unsigned long value;
__asm__ volatile("MOV %0, CTRL" : "=l"(value));
return value;
}
void write_ctrl_inline(unsigned long value) {
__asm__ volatile(
"MOV CTRL, %0"
:
: "l"(value)
: "memory");
}
如果工具链已有对应 builtin,应优先用 builtin 表达状态读写。各寄存器方向和 CPU 限制见 配置寄存器与隐式状态。
多行模板¶
__asm__ volatile(
"MOVEMASK MASK[0], %0\n\t"
"MOVEMASK MASK[1], %1\n\t"
"VADD.s16 [%2], [%3], [%4], %5, MASK"
:
: "l"(mask0), "l"(mask1),
"l"(dst), "l"(src0), "l"(src1), "l"(config)
: "memory");
同一模板中的指令保持局部顺序,但跨执行域的数据完成仍需要相应事件或屏障。