4. 卷积计算单元(PE阵列)设计

任务目标

设计一个可重构的PE(Processing Element)阵列,支持1×1、3×3、5×5三种卷积核大小,每个PE完成一次乘累加(MAC)操作。阵列通过配置寄存器选择卷积核模式,在相同硬件资源下完成不同尺寸的滑窗计算。

硬件架构

采用4×4 PE阵列,每个PE包含一个乘法器和一个累加器。输入特征图数据按行广播,权重按列广播。通过输入缓冲与路由逻辑实现不同卷积核的滑窗映射:

  • 1×1卷积:每个PE独立处理一个输入像素与一个权重,无需数据共享
  • 3×3卷积:将3×3输入块映射到相邻3×3 PE组,通过移位寄存器实现行缓存
  • 5×5卷积:使用5×5 PE组,需要4行行缓存

阵列尺寸固定为8×8,通过模式选择信号(2bit)控制数据路由路径,使能对应区域的PE组工作,其余PE旁路(输出0)。

Verilog实现

// pe_array.v - 可重构卷积PE阵列
module pe_array #(
    parameter DATA_WIDTH = 8,
    parameter ACC_WIDTH  = 24,
    parameter PE_ROWS    = 8,
    parameter PE_COLS    = 8
)(
    input  wire clk,
    input  wire rst_n,
    input  wire [1:0] mode,          // 00:1x1, 01:3x3, 10:5x5
    input  wire [DATA_WIDTH-1:0] ifmap [0:PE_ROWS-1][0:PE_COLS-1], // 输入特征图
    input  wire [DATA_WIDTH-1:0] weight [0:PE_ROWS-1][0:PE_COLS-1], // 权重
    input  wire valid_in,
    output reg  [ACC_WIDTH-1:0] psum [0:PE_ROWS-1][0:PE_COLS-1],
    output reg  valid_out
);

    // 内部信号:每个PE的乘累加结果
    reg [ACC_WIDTH-1:0] mac_acc [0:PE_ROWS-1][0:PE_COLS-1];
    wire [DATA_WIDTH-1:0] mac_prod [0:PE_ROWS-1][0:PE_COLS-1];
    reg [ACC_WIDTH-1:0] acc_next [0:PE_ROWS-1][0:PE_COLS-1];

    // 生成乘累加单元
    genvar i, j;
    generate
        for (i = 0; i < PE_ROWS; i = i + 1) begin : row_loop
            for (j = 0; j < PE_COLS; j = j + 1) begin : col_loop
                // 乘法
                assign mac_prod[i][j] = ifmap[i][j] * weight[i][j];

                // 累加器(带模式控制清零)
                always @(posedge clk or negedge rst_n) begin
                    if (!rst_n) begin
                        mac_acc[i][j] <= 0;
                    end else if (valid_in) begin
                        // 模式切换时清零(新卷积窗口开始)
                        if ((mode == 2'b00) || 
                            (mode == 2'b01 && i < 3 && j < 3) ||
                            (mode == 2'b10 && i < 5 && j < 5))
                            mac_acc[i][j] <= mac_prod[i][j];
                        else
                            mac_acc[i][j] <= mac_acc[i][j] + mac_prod[i][j];
                    end
                end
            end
        end
    endgenerate

    // 输出赋值:非活动PE输出0
    integer m, n;
    always @(*) begin
        for (m = 0; m < PE_ROWS; m = m + 1) begin
            for (n = 0; n < PE_COLS; n = n + 1) begin
                if ((mode == 2'b00) ||
                    (mode == 2'b01 && m < 3 && n < 3) ||
                    (mode == 2'b10 && m < 5 && n < 5))
                    psum[m][n] = mac_acc[m][n];
                else
                    psum[m][n] = 0;
            end
        end
    end

    // 有效输出指示(简化:数据有效时拉高一个周期)
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            valid_out <= 0;
        else
            valid_out <= valid_in;
    end

endmodule

测试平台与验证

// tb_pe_array.v - 最小验证平台
module tb_pe_array;
    reg clk = 0;
    reg rst_n = 0;
    reg [1:0] mode;
    reg [7:0] ifmap [0:7][0:7];
    reg [7:0] weight [0:7][0:7];
    wire [23:0] psum [0:7][0:7];
    wire valid_out;

    pe_array dut (
        .clk(clk), .rst_n(rst_n), .mode(mode),
        .ifmap(ifmap), .weight(weight),
        .valid_in(1'b1), .psum(psum), .valid_out(valid_out)
    );

    always #5 clk = ~clk;

    integer i, j;
    initial begin
        // 初始化输入
        for (i = 0; i < 8; i = i + 1)
            for (j = 0; j < 8; j = j + 1) begin
                ifmap[i][j] = i * 8 + j;
                weight[i][j] = 1;  // 全1权重便于手算验证
            end

        // 复位
        #10 rst_n = 1;

        // 测试1x1卷积
        mode = 2'b00;
        #20;
        $display("1x1模式 psum[0][0]=%0d (期望: %0d)", psum[0][0], ifmap[0][0]*1);

        // 测试3x3卷积
        mode = 2'b01;
        #20;
        $display("3x3模式 psum[0][0]=%0d (期望: %0d)", psum[0][0], 
                 ifmap[0][0]+ifmap[0][1]+ifmap[0][2]+
                 ifmap[1][0]+ifmap[1][1]+ifmap[1][2]+
                 ifmap[2][0]+ifmap[2][1]+ifmap[2][2]);

        // 测试5x5卷积
        mode = 2'b10;
        #20;
        $display("5x5模式 psum[0][0]=%0d (期望: %0d)", psum[0][0], 
                 ifmap[0][0]+ifmap[0][1]+ifmap[0][2]+ifmap[0][3]+ifmap[0][4]+
                 ifmap[1][0]+ifmap[1][1]+ifmap[1][2]+ifmap[1][3]+ifmap[1][4]+
                 ifmap[2][0]+ifmap[2][1]+ifmap[2][2]+ifmap[2][3]+ifmap[2][4]+
                 ifmap[3][0]+ifmap[3][1]+ifmap[3][2]+ifmap[3][3]+ifmap[3][4]+
                 ifmap[4][0]+ifmap[4][1]+ifmap[4][2]+ifmap[4][3]+ifmap[4][4]);

        #10 $finish;
    end
endmodule

运行与验证方法

  1. 仿真运行:使用Icarus Verilog运行 iverilog -o tb pe_array.v tb_pe_array.v && vvp tb
  2. 通过条件:观察打印的psum值与期望值一致。由于权重全为1,psum值等于对应卷积窗口内所有输入像素之和,可手工快速验算
  3. 综合验证:在Vivado中创建工程,添加pe_array.v,综合后检查资源利用率(8×8阵列使用64个DSP或乘法器)

关键设计说明

  • 数据路由:本设计采用简化广播方式,实际系统中需在PE阵列前加入行缓存(Line Buffer)实现滑窗数据准备。行缓存深度为输入宽度,数量为(k-1)行,其中k为卷积核大小
  • 累加控制:每个有效时钟周期,活动PE执行一次乘累加。当模式切换或新窗口开始时,通过valid_in信号与模式判断逻辑清零累加器
  • 可扩展性:修改PE_ROWS/PE_COLS参数可调整阵列规模;增加模式编码位可支持更大卷积核