RISC-V向量扩展
介绍
标准头文件
1
| #include <riscv_vector.h>
|
可以用下面宏定义来测试运行环境是否支持RVV
1 2 3 4 5 6 7 8 9 10 11
| #if defined(__riscv) && defined(__riscv_vector) #define IS_RISCV_WITH_RVV 1 #else #define IS_RISCV_WITH_RVV 0 #endif
|
相关概念
| 概念 |
解释 |
常见值 |
| 向量寄存器组 |
RVV64 有 32 个独立的向量寄存器 |
v0 ~ v31 |
| VLEN |
单个向量寄存器的硬件宽度 |
128/256/512 位 |
| SEW |
Standard Element Width,标准元素宽度 |
8/16/32/64/128 位 |
| LMUL |
Vector Length Multiplier,向量长度倍数 |
m1/m2/m4/m8 |
| VL |
Vector Length,本次实际能处理的元素个数 |
由vsetvli/vsetvl动态返回 |
在 RISC-V 向量架构中,VLEN、SEW、LMUL 和 VL 构成了向量编程模型的核心参数。它们通过严格的数学和架构约束关联在一起,决定了向量指令如何映射到硬件寄存器上。
以下是这四个概念的严格定义及其逻辑关系:
概念定义
VLEN (Vector Length in bits)
性质:硬件实现的常量,软件不可修改。
定义:单个向量寄存器的位宽。例如 VLEN=128 表示一个向量寄存器由 128 个比特位组成。RISC-V 规范要求 VLEN ≥≥ 128。
SEW (Selected Element Width)
性质:软件动态配置的参数。
定义:当前向量操作中,单个数据元素占用的位宽。常见的值有 8、16、32、64 等,对应 char、short、int、long 等数据类型。
LMUL (Vector Length Multiplier)
性质:软件动态配置的参数。
定义:向量寄存器分组因子。它将多个独立的向量寄存器组合成一个逻辑向量寄存器组,以增加单条向量指令能够处理的元素总量。取值通常为 1、2、4、8(也支持小数,用于截断寄存器)。
VL (Vector Length)
性质:由 vsetvl 指令动态计算得出的运行时变量。
定义:当前向量指令实际需要处理的元素个数。
核心数学关系
这四个参数通过以下等式严密绑定:
VLmax=VLEN×LMULSEW
VLmax=SEWVLEN×LMUL
VLmaxVLmax:在给定的 VLEN、SEW 和 LMUL 配置下,硬件单次操作最多能容纳的元素个数。
等式右侧的 VLEN * LMUL 代表一个逻辑向量寄存器组的总位宽。
用总位宽除以单个元素的位宽(SEW),即得出能装入的元素最大数量。
实际执行的向量长度 VL 必须满足以下约束:
0≤VL≤VLmax
0≤VL≤VLmax
同时,在软件循环处理长数组时,VL 还受限于剩余待处理的数据量 n:
VL=min(n,VLmax)
VL=min(n,VLmax)
参数间的联动机制
VLEN 与 LMUL 的关系:容量扩展
VLEN 是固定的硬件限制。当 SEW 较大(例如 64 位)而 VLEN 较小(例如 128 位)时,单个寄存器只能容纳 2 个元素。为了提高单条指令的并行度,软件可以通过增大 LMUL 来扩充容量。
将 LMUL 设为 4,意味着将 4 个物理向量寄存器拼接,总位宽变为 VLEN * 4,从而在单次操作中容纳更多的元素。
SEW 与 LMUL 的关系:状态空间守恒
在向量计算中,经常需要执行拓宽或缩窄操作。RISC-V 架构要求:在发生数据位宽变化的指令中,输入和输出必须包含相同数量的元素(即 VL 必须一致)。
根据公式 VL=VLEN×LMULSEWVL=SEWVLEN×LMUL,要保证 VL 不变(VLEN 是常量),当 SEW 发生变化时,LMUL 必须按比例变化。
常用函数
配置向量长度
1 2 3 4 5 6 7 8 9
| size_t vl = __riscv_vsetvl_e8m8(n); size_t vl = __riscv_vsetvl_e16m8(n); size_t vl = __riscv_vsetvl_e32m8(n); size_t vl = __riscv_vsetvl_e64m8(n);
size_t vl = __riscv_vsetvl_e32m8(n); size_t vl = __riscv_vsetvl_e64m8(n);
|
加载/存储
1 2 3 4 5 6 7 8
| vuint64m8_t v = __riscv_vle64_v_u64m8(ptr, vl); vuint64m8_t v = __riscv_vle64_v_i64m8(ptr, vl); vfloat64m8_t v = __riscv_vle64_v_f64m8(ptr, vl);
vse64_v_u64m8(ptr, v, vl); vse64_v_f64m8(ptr, v, vl);
|
运算函数
1 2 3 4 5 6 7 8 9 10
| vc = vadd_vv_u64m8(va, vb, vl); vc = vsub_vv_u64m8(va, vb, vl); vc = vmul_vv_u64m8(va, vb, vl); vc = vwmul_vv_u64m8(va, vb, vl);
vc = vfadd_vv_f64m8(va, vb, vl); vc = vfsub_vv_f64m8(va, vb, vl); vc = vfmul_vv_f64m8(va, vb, vl);
|
__riscv_vsetvl_e8m8
函数名解析表
| 组成部分 |
含义 |
| _riscv |
RISC-V 向量指令集通用头文件 |
| vsetvl |
设置向量长度指令 |
| e32 |
元素宽度为32 |
| m8 |
使用8个向量寄存器, |
| vv |
向量+向量(vv = vector-vector) |
| vx |
向量+标量(vx = vector-scalar) |
| vi |
向量+立即数(vi = vector-immediate) |
| vred |
Vector REDuction — 向量归约运算 |
riscv32-gcc 安装
1 2 3 4 5 6 7 8
| wget https://github.com/riscv-collab/riscv-gnu-toolchain/releases/download/2026.05.19/riscv64-glibc-ubuntu-24.04-gcc.tar.xz -P ./package
tar -xvf riscv64-glibc-ubuntu-24.04-gcc.tar.xz sudo mv ./riscv /opt/riscv64
echo 'export PATH=/opt/riscv32/bin:$PATH' >> ~/.bashrc source ~/.bashrc
|
案例编写
RVV 的程序编写分为四个步骤:
- 配置向量长度 vl = vsetvl_eXXmX(n)
- 加载数据 v = vleXX_v_XXmX(ptr, vl)
- 向量运算 v = vop_vv_XXmX(v1, v2, vl)
- 存回数据 vseXX_v_XXmX(ptr, v, vl)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| #include <stdio.h> #include <riscv_vector.h>
int main() { int a[4] = {1,2,3,4}; int b[4] = {10,20,30,40}; int res[4];
size_t vl = __riscv_vsetvl_e32m1(4);
vint32m1_t va = __riscv_vle32_v_i32m1(a, vl); vint32m1_t vb = __riscv_vle32_v_i32m1(b, vl);
vint32m1_t vc = __riscv_vadd_vv_i32m1(va, vb, vl);
__riscv_vse32_v_i32m1(res, vc, vl);
for(int i=0;i<4;i++){ printf("%d ", res[i]); } return 0; }
|
编译
1
| /opt/riscv64/bin/riscv64-unknown-linux-gnu-gcc ./main.c -march=rv64gcv -o rvv_test
|
运行
1
| qemu-riscv64 -cpu rv64,v=true -L /opt/riscv64/sysroot ./rvv_test
|
测试题
为了方便检验成功笔者编写了一些测试题。
更完整的头文件参考
加法运算
| 指令 |
说明 |
__riscv_vadd_vv_i32m1 |
向量 + 向量(vv = vector-vector) |
__riscv_vadd_vx_i32m1 |
向量 + 标量(vx = vector-scalar) |
__riscv_vadd_vi_i32m1 |
向量 + 立即数(vi = vector-immediate) |
减法运算
| 指令 |
说明 |
__riscv_vsub_vv_i32m1 |
向量 - 向量 |
__riscv_vsub_vx_i32m1 |
向量 - 标量 |
__riscv_vrsub_vx_i32m1 |
标量 - 向量(reverse subtract) |
乘法运算
| 指令 |
说明 |
__riscv_vmul_vv_i32m1 |
向量 × 向量 |
__riscv_vmul_vx_i32m1 |
向量 × 标量 |
__riscv_vmulh_vv_i32m1 |
高位乘法(保留高半部分) |
__riscv_vmulhu_vv_u32m1 |
无符号高位乘法 |
__riscv_vmulhsu_vv_i32m1 |
有符号×无符号高位乘法 |
除法/取模运算
| 指令 |
说明 |
__riscv_vdiv_vv_i32m1 |
有符号除法 |
__riscv_vdivu_vv_u32m1 |
无符号除法 |
__riscv_vrem_vv_i32m1 |
有符号取模 |
__riscv_vremu_vv_u32m1 |
无符号取模 |
位运算
| 指令 |
说明 |
__riscv_vand_vv_i32m1 |
按位与 |
__riscv_vor_vv_i32m1 |
按位或 |
__riscv_vxor_vv_i32m1 |
按位异或 |
__riscv_vnot_v_i32m1 |
按位取反 |
位移运算
| 指令 |
说明 |
__riscv_vsll_vv_i32m1 |
逻辑左移 |
__riscv_vsrl_vv_u32m1 |
逻辑右移 |
__riscv_vsra_vv_i32m1 |
算术右移 |
比较/最小最大
| 指令 |
说明 |
__riscv_vmin_vv_i32m1 |
有符号最小值 |
__riscv_vminu_vv_u32m1 |
无符号最小值 |
__riscv_vmax_vv_i32m1 |
有符号最大值 |
__riscv_vmaxu_vv_u32m1 |
无符号最大值 |
浮点数加减乘除
| 指令 |
说明 |
__riscv_vfadd_vv_f32m1 |
浮点向量加 |
__riscv_vfsub_vv_f32m1 |
浮点向量减 |
__riscv_vfmul_vv_f32m1 |
浮点向量乘 |
__riscv_vfdiv_vv_f32m1 |
浮点向量除 |
__riscv_vfrsub_vf_f32m1 |
标量 - 向量(reverse) |
浮点数乘加
| 指令 |
说明 |
__riscv_vfmacc_vv_f32m1 |
乘加:vd = vs1 * vs2 + vd |
__riscv_vfnmacc_vv_f32m1 |
负乘加:vd = -(vs1 * vs2) + vd |
__riscv_vfmsac_vv_f32m1 |
乘减:vd = vs1 * vs2 - vd |
__riscv_vfmadd_vv_f32m1 |
乘加(另一个形式) |
__riscv_vfmsub_vv_f32m1 |
乘减(另一个形式) |
平方根/比较
| 指令 |
说明 |
__riscv_vfsqrt_v_f32m1 |
平方根 |
__riscv_vfmin_vv_f32m1 |
浮点最小值 |
__riscv_vfmax_vv_f32m1 |
浮点最大值 |
__riscv_vfsgnj_vv_f32m1 |
符号注入 |
__riscv_vfsgnjn_vv_f32m1 |
符号取反注入 |
__riscv_vfsgnjx_vv_f32m1 |
符号异或注入 |
类型转换
| 指令 |
说明 |
__riscv_vfcvt_f_x_v_f32m1 |
整数转浮点 |
__riscv_vfcvt_x_f_v_i32m1 |
浮点转整数 |
__riscv_vfcvt_rtz_x_f_v_i32m1 |
浮点转整数(向零舍入) |
__riscv_vfcvt_f_xu_v_f32m1 |
无符号整数转浮点 |
归约运算(Reduction)
对向量所有元素进行归约,结果存入标量/第一个元素:
| 指令 |
说明 |
__riscv_vredsum_vs_i32m1_i32m1 |
整数求和归约 |
__riscv_vredmax_vs_i32m1_i32m1 |
整数最大归约 |
__riscv_vredmin_vs_i32m1_i32m1 |
整数最小归约 |
__riscv_vredand_vs_i32m1_i32m1 |
按位与归约 |
__riscv_vredor_vs_i32m1_i32m1 |
按位或归约 |
__riscv_vfredosum_vs_f32m1_f32m1 |
浮点有序求和归约 |
__riscv_vfredusum_vs_f32m1_f32m1 |
浮点无序求和归约 |
__riscv_vfredmax_vs_f32m1_f32m1 |
浮点最大归约 |
__riscv_vfredmin_vs_f32m1_f32m1 |
浮点最小归约 |
掩码/条件运算
| 指令 |
说明 |
__riscv_vmerge_vvm_i32m1 |
根据掩码合并两个向量 |
__riscv_vadc_vvm_i32m1 |
带进位加法 |
__riscv_vsbc_vvm_i32m1 |
带借位减法 |
__riscv_vmadc_vvm_i32m1 |
掩码进位加法 |
__riscv_vmsbc_vvm_i32m1 |
掩码借位减法 |
整数扩展/截断
| 指令 |
说明 |
__riscv_vsext_vf2_i64m1 |
有符号扩展(2倍宽度) |
__riscv_vzext_vf2_u64m1 |
零扩展(2倍宽度) |
__riscv_vncvt_x_x_w_i32m1 |
截断(窄化转换) |
命名规则总结
_riscv_v[操作][类型]_[数据类型][宽度]m[LMUL]
| 部分 |
示例 |
含义 |
v |
vadd |
向量操作 |
操作 |
add, sub, mul, div |
运算类型 |
类型 |
vv, vx, vi, vf |
操作数类型 |
数据类型+宽度 |
i32, u32, f32, f64 |
元素类型 |
m+LMUL |
m1, m2, m4, m8 |
向量寄存器组倍数 |
相关文档
感谢鼓励