RISC-V向量扩展

介绍

标准头文件

#include <riscv_vector.h>

可以用下面宏定义来测试运行环境是否支持RVV

//判断是否支持RVV扩展
#if defined(__riscv) && defined(__riscv_vector)
    #define IS_RISCV_WITH_RVV 1
#else
    #define IS_RISCV_WITH_RVV 0
#endif

/*
也可以使用 __riscv_v_intrinsic 来进行判断,但是 __riscv_v_intrinsic 是用来测试编译器对RVV支持情况的宏。
它的行为在不同编译器,特别是新老版本之间可能会有差异。在gcc13之前(包括GCC13)的版本可能不支持。
*/

相关概念

概念 解释 常见值
向量寄存器组 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)
    性质:软件动态配置的参数。
    定义:当前向量操作中,单个数据元素占用的位宽。常见的值有 8163264 等,对应 char、short、int、long 等数据类型。

LMUL (Vector Length Multiplier)
    性质:软件动态配置的参数。
    定义:向量寄存器分组因子。它将多个独立的向量寄存器组合成一个逻辑向量寄存器组,以增加单条向量指令能够处理的元素总量。取值通常为 1248(也支持小数,用于截断寄存器)。

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 必须按比例变化。

常用函数

配置向量长度

// 无符号整数
size_t vl = __riscv_vsetvl_e8m8(n);    // 8位,m8
size_t vl = __riscv_vsetvl_e16m8(n);   // 16位,m8
size_t vl = __riscv_vsetvl_e32m8(n);   // 32位,m8
size_t vl = __riscv_vsetvl_e64m8(n);   // 64位,m8

// 浮点数
size_t vl = __riscv_vsetvl_e32m8(n);   // f32
size_t vl = __riscv_vsetvl_e64m8(n);   // f64

加载/存储

// 加载
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);

运算函数

// 整数运算
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);  // 加宽乘:注意这里的va,vb 只能使用u32m4,才能装下

// 浮点运算
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 安装

# 下载
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 的程序编写分为四个步骤:

  1. 配置向量长度 vl = vsetvl_eXXmX(n)
  2. 加载数据 v = vleXX_v_XXmX(ptr, vl)
  3. 向量运算 v = vop_vv_XXmX(v1, v2, vl)
  4. 存回数据 vseXX_v_XXmX(ptr, v, vl)
#include <stdio.h>
#include <riscv_vector.h>

int main() {
    // 1. 定义普通数组
    int a[4] = {1,2,3,4};
    int b[4] = {10,20,30,40};
    int res[4];

    // 2. 设置本次向量运算长度:一次算4个32位int
    size_t vl = __riscv_vsetvl_e32m1(4);

    // 3. 从内存加载到向量寄存器
    vint32m1_t va = __riscv_vle32_v_i32m1(a, vl);
    vint32m1_t vb = __riscv_vle32_v_i32m1(b, vl);

    // 4. 执行向量加法(vv 就是向量+向量)
    vint32m1_t vc = __riscv_vadd_vv_i32m1(va, vb, vl);

    // 5. 把向量写回内存
    __riscv_vse32_v_i32m1(res, vc, vl);

    // 6. 打印结果
    for(int i=0;i<4;i++){
        printf("%d ", res[i]);
    }
    return 0;
}

编译

/opt/riscv64/bin/riscv64-unknown-linux-gnu-gcc ./main.c -march=rv64gcv -o rvv_test

运行

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 向量寄存器组倍数

相关文档

Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐