4. 向量卷积
任务目标
实现一维卷积运算,输入信号与卷积核均为浮点数组,使用RISC-V向量扩展指令(V扩展)完成滑动窗口的乘累加操作。通过对比向量实现与标量参考实现的输出,验证正确性。
环境依赖
需要支持RISC-V向量扩展的编译器(如GCC 12+,编译参数 -march=rv64gcv)以及支持V扩展的模拟器(如QEMU 7.0+)或开发板。本案例代码不依赖外部库,仅使用标准头文件。
核心实现
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <riscv_vector.h>
#define N 16 // 输入信号长度
#define M 4 // 卷积核长度
#define OUT_LEN (N - M + 1) // 输出长度
// 标量参考实现
void conv_scalar(const float *signal, const float *kernel, float *out) {
for (int i = 0; i < OUT_LEN; i++) {
float acc = 0.0f;
for (int j = 0; j < M; j++) {
acc += signal[i + j] * kernel[j];
}
out[i] = acc;
}
}
// 向量实现:每个输出点对应一个滑动窗口
void conv_vector(const float *signal, const float *kernel, float *out) {
size_t vl;
for (int i = 0; i < OUT_LEN; i++) {
// 加载当前窗口的M个信号值
vfloat32m1_t vec_signal = vle32_v_f32m1(&signal[i], M);
// 加载卷积核(每次重复加载,实际可提前加载到寄存器)
vfloat32m1_t vec_kernel = vle32_v_f32m1(kernel, M);
// 向量乘累加:先乘后加
vfloat32m1_t vec_prod = vfmul_vv_f32m1(vec_signal, vec_kernel, M);
// 将M个乘积归约为一个标量
float acc = vfmv_f_s_f32m1_f32(vfredusum_vs_f32m1_f32m1(vec_prod, vfmv_s_f_f32m1_f32(0.0f), M));
out[i] = acc;
}
}
int main() {
float signal[N] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
float kernel[M] = {0.5f, 1.0f, 0.5f, 0.25f};
float out_scalar[OUT_LEN], out_vector[OUT_LEN];
conv_scalar(signal, kernel, out_scalar);
conv_vector(signal, kernel, out_vector);
int pass = 1;
for (int i = 0; i < OUT_LEN; i++) {
if (out_scalar[i] != out_vector[i]) {
printf("Mismatch at %d: scalar=%f vector=%f\n", i, out_scalar[i], out_vector[i]);
pass = 0;
}
}
if (pass) {
printf("PASS: All %d outputs match\n", OUT_LEN);
}
return pass ? 0 : 1;
}
编译与运行
# 使用QEMU模拟运行(需安装qemu-riscv64)
riscv64-unknown-elf-gcc -march=rv64gcv -mabi=lp64d -O2 -o conv_vector conv_vector.c
qemu-riscv64 ./conv_vector
# 或使用原生支持V扩展的Linux环境
gcc -march=rv64gcv -O2 -o conv_vector conv_vector.c
./conv_vector
验证方法
程序执行后,若输出 PASS: All 13 outputs match,说明向量实现与标量参考实现结果完全一致,卷积功能正确。若出现 Mismatch 行,则需检查向量加载长度或归约操作是否正确。
关键说明
- 本案例使用
vfredusum进行向量归约,将M个乘积求和为一个标量;vfmv_s_f用于初始化归约累加器为0。 - 卷积核长度M=4,小于向量寄存器最大长度(LMUL=1时,vfloat32m1_t可容纳至少4个元素),因此单次加载即可完成窗口操作。
- 实际工程中可将卷积核预先加载到向量寄存器中循环复用,本案例为清晰展示滑动窗口操作,每次重新加载。