RVV向量扩展

向量扩展

使用 Intrinsics 的 C 语言 RISC-V 向量编程

RVV 的灵活性体现在以下几个方面:

  1. 向量长度的可变性:RVV 提供了灵活的向量长度(VL)设置,使得同一指令可以处理不同数量的数据元素。例如,你可以在同一程序中为不同的操作指定不同的向量长度,以适应硬件的能力或特定的计算任务。一个寄存器可以包含多个数据元素(例如 32 位整数、64 位浮点数等),并根据当前的 vl 设置来决定每个寄存器要处理多少数据元素。因此,向量寄存器组有时可以半满或不满——取决于 vl 设置以及每个操作处理的数据量。
  2. 向量寄存器组的大小:RISC-V 向量寄存器组通常由多个寄存器组成,每个寄存器的大小是固定的。寄存器的大小和数据类型(如整数或浮点数)是固定的,但由于 vl 是动态可调整的,寄存器的填充率可能会小于其最大容量。例如,假设一个寄存器是 128 位宽,如果你只需要处理 64 位的数据元素,寄存器的 “半满” 情况就可能发生。
  3. 指令和掩码的作用:在 RVV 中,某些指令可以允许掩码操作(例如,vfmv_v_f_f64m1),这意味着某些元素可能会被忽略或处理为零,而不影响其他寄存器元素的计算。这使得向量寄存器的实际使用情况(如 1/2 或更少)变得有可能。

可行性方案

工具链的安装

1
2
3
4
5
6
7
8
9
10
11
12
git clone https://github.com/riscv-collab/riscv-gnu-toolchain
cd riscv-gnu-toolchain
git submodule update --init --recursive##速度很慢
mkdir build && cd build
../configure --prefix=/opt/riscv64 --enable-multilib --enable-languages=c,c++ --with-arch=rv64gcv --with-abi=lp64 --enable-rvv
make -$(nproc)j
riscv64-unknown-elf-gcc --version
#riscv64-unknown-elf-gcc (g04696df0963) 14.2.0
#Copyright (C) 2024 Free Software Foundation, Inc.
#This is free software; see the source for copying conditions. There is NO
#warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

程序编写

经过测试以上配置的工具版本只能支持具有riscv前缀的函数如__riscv_vle32_v_i32m2(lhs, vl)

更高版本对向量指令的处理方式更严格,因此它不再直接解析某些低级别函数,特别是那些没有前缀的 RVV 内建函数。这种变化旨在减少混淆,并统一 LLVM 中的向量扩展处理。

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// Reference: https://pages.dogdog.run/toolchain/riscv_vector_extension.html
// #define __riscv_vector // make VSC happy when reading riscv_vector.h
#include <riscv_vector.h>
#include <stdio.h>

int x[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
int y[10] = {0, 9, 8, 7, 6, 5, 4, 3, 2, 1};
int z[10], z_real[10];

void vec_add_rvv(int* dst, int* lhs, int* rhs, size_t avl) {
vint32m2_t vlhs, vrhs, vres;
for (size_t vl; (vl = __riscv_vsetvl_e32m2(avl));
avl -= vl, lhs += vl, rhs += vl, dst += vl) {
vlhs = __riscv_vle32_v_i32m2(lhs, vl);
vrhs = __riscv_vle32_v_i32m2(rhs, vl);
vres = __riscv_vadd_vv_i32m2(vlhs, vrhs, vl);
__riscv_vse32_v_i32m2(dst, vres, vl);
}
}

void vec_add_real(int* dest, int* lhs, int* rhs, size_t vlen) {
for (size_t i = 0; i < vlen; i++) {
dest[i] = lhs[i] + rhs[i];
}
}

void print_vec(int* v, size_t vlen, char* msg) {
printf("%s={ ", msg);
for (size_t i = 0; i < vlen; i++) {
printf("%d, ", v[i]);
}

printf("}\n");
}

int main(int argc, char const* argv[]) {

// check RVV support
#ifndef __riscv_v_intrinsic
printf("RVV NOT supported in this compiler\n");
return 0;
#endif

vec_add_rvv(z, x, y, 10);
vec_add_real(z_real, x, y, 10);

print_vec(x, 10, "x[10]");
print_vec(y, 10, "y[10]");

for (size_t i = 0; i < 10; i++) {
if (z[i] != z_real[i]) {
printf("==========\nTest FAILED: pos %d mismatch\n", i);
print_vec(z, 10, "z[10]");
print_vec(z_real, 10, "z_real[10]");

return -1;
}
}

print_vec(z, 10, "z[10]");
printf("==========\nTest PASSED\n");

return 0;
}

模拟器对于RVV的支持

qemu

1
2
3
##编译执行报错,说是不支持rvv扩展,所以采用了spike模拟器
qemu-riscv64 -cpu rv64,v=true,vlen=128,vext_spec=v1.0 ./vadd
qemu-riscv64: can't apply global rv64-riscv-cpu.v=true: Property 'rv64-riscv-cpu.v' not found

Spike

安装依赖

在开始之前,你需要确保你的系统上安装了必要的依赖项:

1
2
sudo apt-get update
sudo apt-get install -y autoconf automake libtool g++ pkg-config make python3

克隆 Spike 仓库

首先克隆 Spike 仓库到本地:

1
2
git clone https://github.com/riscv/riscv-isa-sim.git
cd riscv-isa-sim

配置和编译 Spike

配置和编译 Spike,使其支持 RVV 扩展。通过以下步骤来完成:

1
2
3
4
5
mkdir build
cd build
../configure --prefix=/opt/riscv --enable-rvv
make
sudo make install

在这里,--enable-rvv 确保 Spike 配置支持 RISC-V 向量扩展 (RVV)。

配置环境变量

安装完成后,确保将 /opt/riscv/bin 添加到你的 PATH 环境变量中,以便可以方便地调用 Spike。

1
2
vi ~./basrch #将一下内容放入最后
export PATH=/opt/riscv/bin:$PATH

为了让Spike,正确执行成程序需要将RISCV-V Proxy Kernel(pk)。

安装PK

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
git clone https://github.com/riscv/riscv-pk.git
cd riscv-pk
mkdir build
cd build
../configure --prefix=/opt/riscv --host=riscv64-unknown-linux-gnu
make
#报错
##../machine/flush_icache.c: Assembler messages:
##../machine/flush_icache.c:4: Error: unrecognized opcode fence.i', extension ##zifencei' required
##make: *** [Makefile:336:flush_icache.o] 错误 1
## vim Makefile
## 启用 zifencei 扩展
## 在CFLAGS中添加 -march=rv64imafdc_zifencei
sudo make install
## 验证安装成不成功
ls /opt/riscv/riscv64-unknown-linux-gnu/lib/riscv-pk
##看到诸如 libpk.a、libbbl.a、libsoftfloat.a 等文件。

gcc编译

spike只能跑静态编译的程序。

1
2
3
4
5
6
##执行
$ riscv64-unknown-linux-gnu-gcc -march=rv64gcv -O2 -g -static ./vadd.c -o ./vadd
##或者
$ riscv64-unknown-elf-gcc -march=rv64gcv -O2 -g -static ./vadd.c -o ./vadd
##执行
$ spike --isa=rv64gcv /opt/riscv/riscv64-unknown-linux-gnu/bin/pk ./vadd

clang编译

1
$ clang --target=riscv64-unknown-linux-gnu -O2 -g -march=rv64gcv1p0 -menable-experimental-extensions --gcc-toolchain=/opt/riscv64 --sysroot=/opt/riscv64/sysroot -mllvm --riscv-v-vector-bits-min=256 ./vadd.c -o vadd

报错

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
./vadd.c:10:14: warning: implicit declaration of function '__riscv_vsetvl_e32m1' is invalid in C99 [-Wimplicit-function-declaration]
vl = __riscv_vsetvl_e32m1(vlen - i);
^
./vadd.c:12:27: warning: implicit declaration of function '__riscv_vle32_v_f32m1' is invalid in C99 [-Wimplicit-function-declaration]
vfloat32m1_t va = __riscv_vle32_v_f32m1(&a[i], vl);
^
./vadd.c:12:22: error: initializing 'vfloat32m1_t' (aka '__rvv_float32m1_t') with an expression of incompatible type 'int'
vfloat32m1_t va = __riscv_vle32_v_f32m1(&a[i], vl);
^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
./vadd.c:13:22: error: initializing 'vfloat32m1_t' (aka '__rvv_float32m1_t') with an expression of incompatible type 'int'
vfloat32m1_t vb = __riscv_vle32_v_f32m1(&b[i], vl);
^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
./vadd.c:15:29: warning: implicit declaration of function '__riscv_vfmul_vv_f32m1' is invalid in C99 [-Wimplicit-function-declaration]
vfloat32m1_t vres = __riscv_vfmul_vv_f32m1(va, vb, vl);
^
./vadd.c:15:22: error: initializing 'vfloat32m1_t' (aka '__rvv_float32m1_t') with an expression of incompatible type 'int'
vfloat32m1_t vres = __riscv_vfmul_vv_f32m1(va, vb, vl);
^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
./vadd.c:17:9: warning: implicit declaration of function '__riscv_vse32_v_f32m1' is invalid in C99 [-Wimplicit-function-declaration]
__riscv_vse32_v_f32m1(&dest[i], vres, vl);
^
./vadd.c:55:66: warning: format specifies type 'int' but the argument has type 'size_t' (aka 'unsigned long') [-Wformat]
printf("==========\nTest FAILED: pos %d mismatch\n", i);
~~ ^
%zu
5 warnings and 3 errors generated.

等待验证方案

qemu的RVV扩展以及clang为什么编译报错

1
#使用的qemu是9.0.1的,但是在命令中识别不了vv=true,

参考链接:[中科院RVV配置Intrinsic](RVV C Intrinsic 配置教程)

参考报告中的工具链版本是13.2,使用的clang是14.0.6,但是我使用的工具链是最新版的14.2.0,这个更高的版本clang14还支持不了,需要更高的版本才可以对RVV的全面支持。

屏幕截图 2024 11 15 161722

使用clang17是可以编译的。

后续编程实例:

https://github.com/riscv-non-isa/rvv-intrinsic-doc/blob/v0.11.x/examples/rvv_branch.c

函数API为:

Intrinsics viewer

qemu关于RVV的编译报错:

QEMU RVV

在Ubuntu20.04上可以成功运行。