本文是为了帮助开发者快速入门 risc-v 架构下vector 的 intrinsic 编程,首先介绍了risc-v vector extension 的特性和 intrinsic 编程常见的数据类型与指令接口命名,然后给出一个数组/向量相加的完整例程,介绍c语言的普通实现与intrinsic向量化实现,最后展示了如何获取平头哥相关工具链编译程序并通过qemu模拟器运行。需要说明的是,本文介绍的特性与案例均以玄铁 c906 处理器为基础。
01vector extension
与arm cpu支持的 sve(scalable vector extension) 类似,risc-v vector extension 可以通过向量化来提升程序运行速度。平头哥玄铁 c906 cpu支持 risc-v vector extension 0.7.1,新增了32个向量寄存器 v0-v31,下面对 vector extension中重要概念作介绍:
(1) 向量寄存器位宽 vlen:对于玄铁 c906,vlen = 128,即可以并行计算16个8位整数或4个32位浮点数等。
(2) 标准元素宽度 sew:sew = 8/16/32/64b,对于float计算,sew=32b;对于int8计算,sew=8b。
(3) 向量寄存器组 lmul:lmul = 1/2/4/8,多个向量寄存器可以组合在一起形成一个向量寄存器组,一条向量指令就可以对多个向量寄存器进行操作,还能提供更好的执行效率(具体的执行效率取决于执行部件的带宽)。
(4) 向量长度寄存器 vl:vl 寄存器保存一个无符号整数,指定由向量指令更新的元素个数。在向量指令执行期间,任何具有索引 ≥ vl 的目标向量寄存器组中的元素都被清零。如当 vl = 3 时,对于 float32 的运算,由于vlen=128,只会取向量寄存器中的低96位的3个float32数据进行计算,最高32位将清零,如下图所示。
图1. c906 vl=3向量浮点相加示意图
02intrinsic
intrinsic 本质上是底层汇编指令的封装,与手工编写汇编程序相比,intrinsic 编程不需要考虑底层寄存器的分配,对于c语言用户而言更加容易上手,可维护性强,可读性更强,在跨平台的可移植性上也更加友好,配上相关编译器工具链后在性能上几乎可以媲美手工汇编。
2.1 数据类型
根据不同的 sew 和 lmul 组合,risc-v vector extension intrinsic 基本数据类型格式如下:
vm_t 其中:
基本类型:int8,int16,int32,int64,uint8,uint16,uint32,uint64,float16,float32
向量寄存器组 lmul:1,2,4,8
例如:
vint32m1_t:1个向量寄存器中存放 vl 个int32 数据, 0 < vl <= 4 (vlen/sew*lmul=128/32*1=4 )
vfloat32m1_t:1个向量寄存器中存放 vl 个float32数据, 0 < vl <=4
vint8m2_t:2个向量寄存器中存放 vl 个int8数据, 0 < vl <= 32
vfloat16m2_t:2个向量寄存器中存放 vl 个float16数据, 0 < vl <= 16
2.2 intrinsic命名
几乎每一条 risc-v vector 指令都有其对应的 intrinsic 函数接口,函数名格式如下:
_m 其中:
指令名:vmul.vv (vmul_vv), vadd.vv (vadd_vv) 等
数据基本类型简写:i8,i16,i32,i64,u8,u16,u32,u64,f16,f32
向量寄存器组 lmul:1,2,4,8
例如:
// 2个向量寄存器中各自 vl 个float32数据逐个相乘得到 vl 个float32结果存入1个向量寄存器中,其中 0 < vl <= 4
vfloat32m1_t vfmul_vv_f32m1(vfloat32m1_t op1, vfloat32m1_t op2, size_t vl)
// 4个向量寄存器两两分组,每组寄存器中各自 vl 个int32数据逐个相加得到 vl 个int32结果存入2个向量寄存器中,其中 0 < vl <= 8
vint32m2_t vadd_vv_i32m2(vint32m2_t op1, vint32m2_t op2, size_t vl)
关于rvv intrinsic更多数据类型和指令接口可以参考附录中intrinsic手册。
03示例程序
本节通过数组相加的例子对 vector 扩展 intrinsic 使用作简单介绍;假设有长度为 array_len 的 float 类型数组 a,b,c,为了实现 c = a + b,有:
3.1 普通标量实现
在循环中对数组 a,b 中的浮点数逐个相加,循环的次数为 array_len 次。
void add(const float *a, const float *b, float *c, size_t length){ for (int i = 0; i 0) { size_t vl = vsetvl_e32m1(length); // 设置向量寄存器每次操作的元素个数 vfloat32m1_t va = vle32_v_f32m1(a, vl); // 从数组a中加载vl个元素到向量寄存器va中 vfloat32m1_t vb = vle32_v_f32m1(b, vl); // 从数组b中加载vl个元素到向量寄存器vb中 vfloat32m1_t vc = vfadd_vv_f32m1(va, vb, vl); // 向量寄存器va和向量寄存器vb中vl个元素对应相加,结果为vc vse32_v_f32m1(c, vc, vl); // 将向量寄存器中的vl个元素存到数组c中 a += vl; b += vl; c += vl; length -= vl; }}
设置 lmul = 1,由于 vlen = 128,向量加法每次最多能操作4个 float 数据,即循环的次数为 ceil(array_len / 4) 次,本例中定义array_len = 11,需循环计算3次,且第3次时剩余元素3个,即 vl=3,计算过程如图1所示。
从上述代码看,在使用 vector intrinsic 实现向量化时,需要手动从指定地址 load 数据到向量寄存器变量中,计算后,同样需要手动将向量寄存器变量中数据 store 回指定地址。相比于普通串行实现,利用 vector intrinsic 实现理论上有接近4倍的加速比,当设置 lmul = 2/4/8 或数据类型是short或者char时,可以取得更高的加速比。
04编译运行
4.1 获取安装工具链
从平头哥开放社区occ下载对应版本的risc-v工具链(https://occ.t-head.cn/community/download?spm=a2cl5.25411629.0.0.51b975321lplnb&id=4090445921563774976):
wget https://occ-oss-prod.oss-cn-hangzhou.aliyuncs.com/resource//1663142514282/xuantie-900-gcc-linux-5.10.4-glibc-x86_64-v2.6.1-20220906.tar.gz tar -zxvf xuantie-900-gcc-linux-5.10.4-glibc-x86_64-v2.1.9-20210918.tar.gz -c {yourtoolchaindir} # 解压至自己的目录 cd {yourtoolchaindir}/binriscv64-unknown-linux-gnu-gcc -v #查看工具链版本
确保gcc工具链版本为 v2.6.1
gcc version 10.2.0 (xuantie-900 linux-5.10.4 glibc gcc toolchain v2.6.1 b-20220906)
4.2 编译
{yourtoolchaindir}/bin/riscv64-unknown-linux-gnu-gcc -march=rv64gcv0p7xthead -static add.c -o add.elf
注:c906 只支持risc-v vector 0.7.1 扩展,需要在编译选项中加上 -march=rv64gcv0p7xthead 选项
4.3 qemu运行
从平头哥开放社区occ下载最新qemu模拟器(https://occ.t-head.cn/community/download?spm=a2cl5.25411629.0.0.51b975321lplnb&id=4168444414324183040)
wget https://occ-oss-prod.oss-cn-hangzhou.aliyuncs.com/resource//1681722863240/xuantie-qemu-x86_64-ubuntu-18.04-20230413-0706.tar.gz tar -zxvf csky-qemu-x86_64-ubuntu-16.04-20210202-1445.tar.gz -c {yourqemudir} # 解压至自己的目录 {yourqemudir}/bin/qemu-riscv64 -cpu c906fdv add.elf
05附
(1)完整示例代码 add.c :
#include #include #include #define array_size 11float a_array[array_size] = {1.18869953, 1.55298864, -0.17365574, -1.86193886,-1.52391526, -0.36566814, 0.70753702, 0.73992422,-0.13493693, 1.09563677,1.03797902};float b_array[array_size] = {1.19655525, 0.23393777, -0.11629651, -0.54508896,-1.2424749, -1.54835913, 0.86935212, 0.12946646,0.81831905, -0.42723697, -0.89793257};// float c_array[array_size] = {// 2.38525478, 1.78692641, -0.28995225, -2.40702783,// -2.76639016 -1.91402727,1.57688914, 0.86939068,// 0.68338213, 0.66839979, 0.14004644// };float c_array_ref[array_size];float c_array_vec[array_size];void add(const float *a, const float *b, float *c, size_t length){ for (int i = 0; i 0) { size_t vl = vsetvl_e32m1(length); // 设置向量寄存器每次操作的元素个数 vfloat32m1_t va = vle32_v_f32m1(a, vl); // 从数组a中加载vl个元素到向量寄存器va中 vfloat32m1_t vb = vle32_v_f32m1(b, vl); // 从数组b中加载vl个元素到向量寄存器vb中 vfloat32m1_t vc = vfadd_vv_f32m1(va, vb, vl); // 向量寄存器va和向量寄存器vb中vl个元素对应相加,结果为vc vse32_v_f32m1(c, vc, vl); // 将向量寄存器中的vl个元素存到数组c中 a += vl; b += vl; c += vl; length -= vl; }}int main(){ printf(test add function by rvv0.7.1 intrinsic ); add(a_array, b_array, c_array_ref, array_size); add_vec(a_array, b_array, c_array_vec, array_size); // 逐个比较普通实现与intrinsic实现的结果 for (int i = 0; i 1e-6) { printf(index[%d] failed, %f=!%f, i, c_array_ref[i], c_array_vec[i]); } else { printf(index[%d] successed, %f=%f, i, c_array_ref[i], c_array_vec[i]); } } return 0;}
(2)运行结果:
在 main 函数中,对普通实现与 intrinsic 实现的结果逐个进行比对,若两者绝对误差大于 1e-6,认为向量化计算结果错误,从下图运行结果看,两者输出结果完全一致,符合预期。
test add function by rvv0.7.1 intrinsic
index[0] successed, 2.385255=2.385255
index[1] successed, 1.786926=1.786926
index[2] successed, -0.289952=-0.289952
index[3] successed, -2.407028=-2.407028
index[4] successed, -2.766390=-2.766390
index[5] successed, -1.914027=-1.914027
index[6] successed, 1.576889=1.576889
index[7] successed, 0.869391=0.869391
index[8] successed, 0.683382=0.683382
index[9] successed, 0.668400=0.668400
index[10] successed, 0.140046=0.140046
(3)riscv 编译工具链下载:https://occ-oss-prod.oss-cn-hangzhou.aliyuncs.com/resource//1663142514282/xuantie-900-gcc-linux-5.10.4-glibc-x86_64-v2.6.1-20220906.tar.gz
(4)qemu模拟器下载:https://occ-oss-prod.oss-cn-hangzhou.aliyuncs.com/resource//1681722863240/xuantie-qemu-x86_64-ubuntu-18.04-20230413-0706.tar.gz
(5)xuantie 900 series rvv-0.7.1 intrinsic manual.pdf 手册下载:https://occ-oss-prod.oss-cn-hangzhou.aliyuncs.com/resource//1663142187133/xuantie+900+series+rvv-0.7.1+intrinsic+manual.pdf
哪些光伏项目需要用到反孤岛装置
基于TMS320DM642视频采集驱动开发
忆阻器(RRAM)存算一体路线再次被肯定
基于太阳能电力系统的微型逆变器的介绍
频谱监测和记录中SDR的高数据速率考虑因素
RISC-V vector intrinsic编程入门指南
中国钻出亚洲最深井记录,比珠穆朗玛峰高29.97米
瑞萨电子推出5V高性能RX660 32位MCU, 为家电和工业应用提供卓越的噪声容限
提升充电效率新选择!22.5W快充方案
矩形连接器是什么,它有什么优势
荣耀10青春版渲染图以及配置信息曝光 将搭载骁龙710处理器芯片
screen在屏幕拆分功能上的优缺点
兆元光电与福建中科芯源合作 将在陶瓷荧光片领域展开战略合作
三星Galaxy A80已在国内开启预售8GB+128GB内存组合版售价3799元
第一届半导体先进封测产业技术创新大会圆满落幕!国内封测龙头企业齐聚厦门~
振荡电路的概要
苹果或在2018年iPhone手机用上oled柔性屏
埃尔法光电宣布完成近亿元A轮融资
气密测试水冷管快速密封接头【厦门连拓精密科技演示】
详细分析14种可用于时间序列预测的损失函数