2. 向量配置指令vsetvli/vsetivli/vsetvl的用法与区别
任务目标
本案例实现RISC-V向量扩展下的有符号32位整数向量减法,重点处理两个问题:一是减法运算中可能出现的溢出(结果超出int32范围),二是将8位/16位短向量元素按符号扩展为32位后再参与运算。最终通过向量化代码与标量参考结果比对,验证正确性。
核心实现
#include <stdio.h>
#include <stdint.h>
#include <riscv_vector.h>
#define N 16
// 标量参考实现:有符号减法,不处理溢出(C语言int溢出为UB,此处用int64_t避免)
void scalar_sub(int32_t *a, int32_t *b, int32_t *out, int n) {
for (int i = 0; i < n; i++) {
int64_t diff = (int64_t)a[i] - (int64_t)b[i];
// 溢出饱和处理:超出int32范围时截断到边界
if (diff > INT32_MAX) diff = INT32_MAX;
if (diff < INT32_MIN) diff = INT32_MIN;
out[i] = (int32_t)diff;
}
}
// 向量实现:有符号减法 + 溢出饱和
void vector_sub_sat(int32_t *a, int32_t *b, int32_t *out, int n) {
size_t vl;
for (size_t i = 0; i < n; i += vl) {
vl = vsetvl_e32m1(n - i);
vint32m1_t va = vle32_v_i32m1(a + i, vl);
vint32m1_t vb = vle32_v_i32m1(b + i, vl);
vint32m1_t vdiff = vssub_vv_i32m1(va, vb, vl); // 饱和减法指令
vse32_v_i32m1(out + i, vdiff, vl);
}
}
// 符号扩展示例:将int8_t向量符号扩展为int32_t后做减法
void vector_sub_ext8(int8_t *a, int8_t *b, int32_t *out, int n) {
size_t vl;
for (size_t i = 0; i < n; i += vl) {
vl = vsetvl_e8m1(n - i);
vint8m1_t va8 = vle8_v_i8m1(a + i, vl);
vint8m1_t vb8 = vle8_v_i8m1(b + i, vl);
// 符号扩展:vsext.vf4 将i8扩展为i32(4倍宽度)
vint32m1_t va32 = vsext_vf4_i32m1(va8, vl);
vint32m1_t vb32 = vsext_vf4_i32m1(vb8, vl);
vint32m1_t vdiff = vssub_vv_i32m1(va32, vb32, vl);
vse32_v_i32m1(out + i, vdiff, vl);
}
}
int main() {
int32_t a[N], b[N], out_vec[N], out_scalar[N];
int8_t a8[N], b8[N];
int32_t out_ext[N];
// 测试数据:包含正负边界值
int32_t test_a[N] = {2147483647, -2147483648, 100, -100, 0, 5, -5, 12345,
-12345, 999, -999, 1, -1, 32767, -32768, 42};
int32_t test_b[N] = {-1, 1, 200, -200, 0, 10, -10, 54321,
-54321, 1000, -1000, 2, -2, 32767, -32768, 42};
for (int i = 0; i < N; i++) {
a[i] = test_a[i];
b[i] = test_b[i];
a8[i] = (int8_t)test_a[i];
b8[i] = (int8_t)test_b[i];
}
// 执行向量与标量减法
vector_sub_sat(a, b, out_vec, N);
scalar_sub(a, b, out_scalar, N);
// 验证
int errors = 0;
for (int i = 0; i < N; i++) {
if (out_vec[i] != out_scalar[i]) {
printf("MISMATCH at %d: vec=%d scalar=%d\n", i, out_vec[i], out_scalar[i]);
errors++;
}
}
// 符号扩展减法验证(标量参考)
int32_t ref_ext[N];
for (int i = 0; i < N; i++) {
int64_t d = (int64_t)(int32_t)a8[i] - (int64_t)(int32_t)b8[i];
if (d > INT32_MAX) d = INT32_MAX;
if (d < INT32_MIN) d = INT32_MIN;
ref_ext[i] = (int32_t)d;
}
vector_sub_ext8(a8, b8, out_ext, N);
for (int i = 0; i < N; i++) {
if (out_ext[i] != ref_ext[i]) {
printf("EXT MISMATCH at %d: vec=%d ref=%d\n", i, out_ext[i], ref_ext[i]);
errors++;
}
}
if (errors == 0) {
printf("ALL TESTS PASSED\n");
} else {
printf("%d ERRORS\n", errors);
}
return errors;
}
编译与运行
# 需要支持RVV的编译器(如riscv64-unknown-elf-gcc或带rvv扩展的clang)
riscv64-unknown-elf-gcc -march=rv64gcv -mabi=lp64d -O2 -o vector_sub vector_sub.c
# 在模拟器或开发板上运行
spike --isa=rv64gcv pk vector_sub
验证方法
程序执行后输出ALL TESTS PASSED即通过。测试数据包含INT32_MAX - (-1)(正溢出)和INT32_MIN - 1(负溢出)两个边界场景,用于验证饱和减法指令vssub.vv的溢出处理行为。符号扩展部分使用vsext.vf4将int8元素扩展为int32后做减法,与标量符号扩展结果比对。
关键说明
vssub.vv是RVV的饱和减法指令,结果超出int32范围时自动饱和到边界值,无需手动判断溢出。vsext.vf4将向量元素按符号扩展为4倍宽度(8位→32位),确保短类型数据以正确的有符号语义参与运算。- 标量参考实现使用
int64_t中间变量避免C语言有符号溢出的未定义行为,保证比对基准正确。