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个元素),因此单次加载即可完成窗口操作。
  • 实际工程中可将卷积核预先加载到向量寄存器中循环复用,本案例为清晰展示滑动窗口操作,每次重新加载。