2. 开发环境搭建:RVV工具链安装与模拟器配置

做RVV开发,第一步就是把环境搭起来。我见过不少同学在工具链上卡一整天,其实没那么复杂。今天咱们就把GCC/LLVM工具链、QEMU模拟器、Spike仿真器这三件套搞定。

本章核心目标:能编译RVV向量代码,并在模拟器上跑通第一个向量加法程序。

2.1 RVV工具链选型:GCC vs LLVM

目前主流的RVV工具链有两个分支。我个人两个都用过,给你说说实际感受。

特性 GCC (riscv-gnu-toolchain) LLVM (riscv-llvm)
向量化支持成熟度 RVV 0.7.1 / 1.0 均支持 RVV 1.0 支持较好
自动向量化能力 中等,需手动intrinsic 较强,-O2 -mrvv 可自动向量化
调试信息 GDB集成完善 LLDB支持,生态稍弱
编译速度 较慢 快30%-50%
二进制体积 略大 更紧凑

我的建议是:初学者先用GCC,文档多、社区活跃,遇到问题好搜。等上手了再切LLVM,它的自动向量化确实香。

2.2 GCC工具链安装(源码编译)

这是最稳的方式。虽然编译要等一阵子,但一劳永逸。

# 1. 安装依赖(Ubuntu 22.04)
sudo apt-get install autoconf automake autotools-dev curl python3 \
  libmpc-dev libmpfr-dev libgmp-dev gawk build-essential bison \
  flex texinfo gperf libtool patchutils bc zlib1g-dev libexpat-dev

# 2. 克隆仓库(带RVV 1.0支持的分支)
git clone --recursive https://github.com/riscv-collab/riscv-gnu-toolchain
cd riscv-gnu-toolchain

# 3. 配置(重点:启用向量扩展)
./configure --prefix=/opt/riscv-gcc \
  --with-arch=rv64gcv \
  --with-abi=lp64d \
  --enable-multilib

# 4. 编译(-j 根据你的CPU核数调整)
make -j$(nproc)

我曾经踩过的坑:--with-arch 里忘了写 'v',结果编译出来的gcc根本不认识向量指令。检查了半小时才发现。切记:rv64gcv 里的 'v' 就是RVV的开关。

装完后验证一下:

/opt/riscv-gcc/bin/riscv64-unknown-elf-gcc --version
# 输出应包含 "riscv64-unknown-elf-gcc (GCC) 13.x.x"

# 测试向量编译
echo '#include <riscv_vector.h>
int main() {
  vint32m1_t a = __riscv_vmv_v_x_i32m1(1, 1);
  return 0;
}' > test_rvv.c

/opt/riscv-gcc/bin/riscv64-unknown-elf-gcc -march=rv64gcv -mabi=lp64d \
  -O2 -o test_rvv test_rvv.c
echo $?  # 返回0说明编译成功

2.3 LLVM工具链安装

如果你追求编译速度,或者想体验自动向量化,LLVM是更好的选择。

# 方式一:直接下载预编译包(推荐)
wget https://github.com/llvm/llvm-project/releases/download/llvmorg-17.0.6/clang+llvm-17.0.6-x86_64-linux-gnu-ubuntu-22.04.tar.xz
tar xf clang+llvm-*.tar.xz
sudo mv clang+llvm-* /opt/llvm-rvv

# 方式二:源码编译(需要CMake 3.20+)
git clone --depth=1 -b release/17.x https://github.com/llvm/llvm-project.git
cd llvm-project
mkdir build && cd build
cmake -G Ninja ../llvm \
  -DCMAKE_INSTALL_PREFIX=/opt/llvm-rvv \
  -DLLVM_ENABLE_PROJECTS="clang;lld" \
  -DLLVM_TARGETS_TO_BUILD="RISCV" \
  -DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD="RISCV" \
  -DLLVM_RISCV_VECTOR_VLEN=256
ninja install

小技巧:LLVM的预编译包已经带了RVV支持,直接解压就能用。除非你要定制VLEN,否则没必要源码编译。

验证LLVM的RVV支持:

/opt/llvm-rvv/bin/clang --version
# 输出应包含 "clang version 17.0.6"

# 测试自动向量化
echo 'void vec_add(int *a, int *b, int *c, int n) {
  for (int i = 0; i < n; i++) c[i] = a[i] + b[i];
}' > test_auto.c

/opt/llvm-rvv/bin/clang -march=rv64gcv -O2 -S -emit-llvm test_auto.c
grep "llvm.loop.vectorize" test_auto.ll  # 看到这个说明自动向量化生效了

2.4 QEMU模拟器配置

QEMU是我们日常调试的主力。它跑得比Spike快,而且支持完整的Linux用户态。

# 安装QEMU(带RVV支持)
git clone --depth=1 -b v8.1.0 https://github.com/qemu/qemu.git
cd qemu
./configure --target-list=riscv64-linux-user,riscv64-softmmu \
  --prefix=/opt/qemu-rvv \
  --enable-slirp
make -j$(nproc)
make install

# 验证
/opt/qemu-rvv/bin/qemu-riscv64 --version
# 输出应包含 "qemu-riscv64 version 8.1.0"

跑一个RVV程序试试:

# 编译RVV程序(用刚才装好的GCC)
/opt/riscv-gcc/bin/riscv64-unknown-elf-gcc -march=rv64gcv -mabi=lp64d \
  -static -O2 -o vec_add.elf vec_add.c

# 用QEMU运行
/opt/qemu-rvv/bin/qemu-riscv64 -cpu rv64,v=true,vlen=256,elen=64 \
  vec_add.elf

# 查看向量寄存器状态(调试用)
/opt/qemu-rvv/bin/qemu-riscv64 -cpu rv64,v=true,vlen=256 \
  -d in_asm,cpu -D qemu_trace.log vec_add.elf

关键参数说明:

  • v=true:启用向量扩展
  • vlen=256:向量寄存器长度(位),常用128/256/512
  • elen=64:向量元素最大位宽
  • -d in_asm,cpu:打印指令流和CPU状态

2.5 Spike仿真器使用

Spike是RISC-V的官方ISA模拟器。它比QEMU慢,但胜在精确——每条指令都严格按规范执行。做指令级调试时我离不开它。

# 安装Spike
git clone --depth=1 https://github.com/riscv-software-src/riscv-isa-sim.git
cd riscv-isa-sim
mkdir build && cd build
../configure --prefix=/opt/spike-rvv \
  --with-isa=rv64gcv \
  --enable-commitlog
make -j$(nproc)
make install

# 安装PK(代理内核,用于运行ELF文件)
git clone --depth=1 https://github.com/riscv-software-src/riscv-pk.git
cd riscv-pk
mkdir build && cd build
../configure --prefix=/opt/spike-rvv \
  --with-arch=rv64gcv \
  --host=riscv64-unknown-elf
make -j$(nproc)
make install

用Spike运行程序:

# 运行
/opt/spike-rvv/bin/spike --isa=rv64gcv \
  /opt/spike-rvv/riscv64-unknown-elf/bin/pk \
  vec_add.elf

# 记录指令执行日志(调试向量化代码的利器)
/opt/spike-rvv/bin/spike --isa=rv64gcv \
  --log-commits \
  -l vec_add.elf 2> spike_trace.log

# 查看向量指令执行次数
grep "vset" spike_trace.log | wc -l

注意:Spike默认的VLEN是128位。如果你要测256位向量,必须在编译时加 -mllvm -riscv-v-vector-bits-min=256(LLVM)或在GCC里用 -march=rv64gcv_zvl256b。

2.6 环境验证:跑通第一个RVV程序

我们来写一个完整的向量加法,验证整个工具链是否打通。

// vec_add.c
#include <stdio.h>
#include <riscv_vector.h>

#define N 1024

int main() {
  int a[N], b[N], c[N];
  for (int i = 0; i < N; i++) {
    a[i] = i;
    b[i] = i * 2;
  }

  // RVV向量加法
  size_t vl = __riscv_vsetvl_e32m1(N);
  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(c, vc, vl);

  // 验证
  for (int i = 0; i < 8; i++) {
    printf("c[%d] = %d (expected %d)\n", i, c[i], a[i] + b[i]);
  }
  return 0;
}

编译并运行:

# GCC编译
/opt/riscv-gcc/bin/riscv64-unknown-elf-gcc -march=rv64gcv -mabi=lp64d \
  -static -O2 -o vec_add.elf vec_add.c

# QEMU运行
/opt/qemu-rvv/bin/qemu-riscv64 -cpu rv64,v=true,vlen=256 vec_add.elf

# 输出示例:
# c[0] = 0 (expected 0)
# c[1] = 3 (expected 3)
# c[2] = 6 (expected 6)
# ...

调试技巧:如果结果不对,先用 -O0 -g 编译,然后用Spike的 --log-commits 逐条检查向量指令。我排查过一个bug,就是靠Spike日志发现vsetvl的vl值算错了。

2.7 知识体系总览

下面这张图把整个开发环境的结构串起来了。你搭建的时候可以对照着看,心里有个全局图。

RVV 开发环境知识体系 C/RVV Intrinsic 源码 GCC 工具链 LLVM 工具链 RISC-V ELF 可执行文件 QEMU(用户态/系统态) Spike + PK(指令精确) 运行结果 / 调试日志 工作流程: 1. 编写C/RVV Intrinsic源码 → 2. 选择GCC或LLVM编译 → 3. 生成RISC-V ELF 4. 用QEMU快速验证功能,或用Spike做指令级调试 → 5. 分析结果和日志

2.8 常见问题与避坑

  • 编译报错 "unknown ISA extension 'v'":GCC版本太老,需要13.0以上。或者你忘了在 --with-arch 里加 'v'。
  • QEMU运行报 "Illegal instruction":大概率是QEMU版本不支持RVV。检查 qemu-riscv64 -cpu help 看有没有 'v' 选项。
  • Spike跑起来没反应:确认PK是否用正确的arch编译的。我遇到过PK用rv64gc编译,但程序是rv64gcv,直接崩了。
  • 向量化代码性能反而不如标量:先检查VLEN设置。默认128位对于int32只能装4个元素,向量化开销可能抵消收益。设成256或512再试。

一句话总结:GCC+QEMU是日常开发组合,Spike留着做指令级调试。环境搭好后,先跑通vec_add.c,后面章节的优化才有基础。