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
运行与验证方法
- 仿真运行:使用Icarus Verilog运行
iverilog -o tb pe_array.v tb_pe_array.v && vvp tb - 通过条件:观察打印的psum值与期望值一致。由于权重全为1,psum值等于对应卷积窗口内所有输入像素之和,可手工快速验算
- 综合验证:在Vivado中创建工程,添加pe_array.v,综合后检查资源利用率(8×8阵列使用64个DSP或乘法器)
关键设计说明
- 数据路由:本设计采用简化广播方式,实际系统中需在PE阵列前加入行缓存(Line Buffer)实现滑窗数据准备。行缓存深度为输入宽度,数量为(k-1)行,其中k为卷积核大小
- 累加控制:每个有效时钟周期,活动PE执行一次乘累加。当模式切换或新窗口开始时,通过valid_in信号与模式判断逻辑清零累加器
- 可扩展性:修改PE_ROWS/PE_COLS参数可调整阵列规模;增加模式编码位可支持更大卷积核