1. 基于FPGA的卷积神经网络加速器框架搭建
任务目标
搭建一个可配置的CNN加速器RTL框架,支持卷积层、池化层与全连接层的统一调度,并完成输入特征图、权重与输出特征图的基础数据通路设计。框架通过参数化配置适配不同网络结构,为后续算子优化提供稳定骨架。
环境依赖
- Vivado 2020.2+(综合与仿真)
- Verilog-2001
核心架构与数据通路
加速器采用层控制器(Layer Controller)+ 算子单元(PE Array)两级结构。层控制器解析配置寄存器中的层参数,按顺序调度各层执行;PE Array在卷积层执行3×3滑动窗乘累加,在池化层执行2×2最大值池化,在全连接层执行向量点积。输入特征图与权重分别经AXI-Stream接口载入片上BRAM,计算结果经写回通道存储至输出BRAM。
关键完整代码
//======================================================
// CNN_Accelerator_Framework.v
// 可配置CNN加速器框架:层调度 + 基础数据通路
//======================================================
module CNN_Accelerator_Framework #(
parameter DATA_WIDTH = 16,
parameter ACT_BUF_DEPTH = 1024, // 激活BRAM深度
parameter WGT_BUF_DEPTH = 2048, // 权重BRAM深度
parameter MAX_LAYERS = 8 // 最大层数
)(
input wire clk,
input wire rst_n,
// 配置接口(寄存器写)
input wire [7:0] cfg_addr,
input wire [31:0] cfg_data,
input wire cfg_wr_en,
// 输入特征图AXI-Stream
input wire [DATA_WIDTH-1:0] s_axis_data,
input wire s_axis_valid,
output wire s_axis_ready,
input wire s_axis_last,
// 输出特征图AXI-Stream
output wire [DATA_WIDTH-1:0] m_axis_data,
output wire m_axis_valid,
input wire m_axis_ready,
output wire m_axis_last,
// 状态指示
output wire [3:0] state_out,
output wire idle_out
);
//======================================================
// 层配置寄存器组
//======================================================
reg [7:0] layer_type [0:MAX_LAYERS-1]; // 0:CONV 1:POOL 2:FC
reg [15:0] layer_in_dim [0:MAX_LAYERS-1]; // 输入维度(宽/高)
reg [15:0] layer_in_ch [0:MAX_LAYERS-1]; // 输入通道
reg [15:0] layer_out_ch [0:MAX_LAYERS-1]; // 输出通道
reg [7:0] layer_num;
reg [7:0] current_layer;
// 配置写入
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
layer_num <= 0;
current_layer <= 0;
end else if (cfg_wr_en) begin
case (cfg_addr)
8'h00: layer_num <= cfg_data[7:0];
8'h10: begin
layer_type[current_layer] <= cfg_data[7:0];
layer_in_dim[current_layer] <= cfg_data[23:8];
end
8'h11: begin
layer_in_ch[current_layer] <= cfg_data[15:0];
layer_out_ch[current_layer] <= cfg_data[31:16];
end
8'h20: current_layer <= current_layer + 1;
endcase
end
end
//======================================================
// 主状态机:层调度
//======================================================
localparam S_IDLE = 4'd0, S_LOAD = 4'd1, S_CONV = 4'd2,
S_POOL = 4'd3, S_FC = 4'd4, S_STORE = 4'd5, S_DONE = 4'd6;
reg [3:0] state, next_state;
reg [7:0] layer_idx;
assign idle_out = (state == S_IDLE);
assign state_out = state;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) state <= S_IDLE;
else state <= next_state;
end
always @(*) begin
next_state = state;
case (state)
S_IDLE: if (layer_num > 0) next_state = S_LOAD;
S_LOAD: next_state = (layer_type[layer_idx] == 2'd0) ? S_CONV :
(layer_type[layer_idx] == 2'd1) ? S_POOL : S_FC;
S_CONV, S_POOL, S_FC: next_state = S_STORE;
S_STORE: begin
if (layer_idx < layer_num - 1) next_state = S_LOAD;
else next_state = S_DONE;
end
S_DONE: next_state = S_IDLE;
endcase
end
// 层索引推进
always @(posedge clk or negedge rst_n) begin
if (!rst_n) layer_idx <= 0;
else if (state == S_STORE) layer_idx <= layer_idx + 1;
else if (state == S_IDLE) layer_idx <= 0;
end
//======================================================
// 数据通路:激活与权重BRAM
//======================================================
reg [DATA_WIDTH-1:0] act_bram [0:ACT_BUF_DEPTH-1];
reg [DATA_WIDTH-1:0] wgt_bram [0:WGT_BUF_DEPTH-1];
reg [15:0] act_wr_addr, act_rd_addr;
reg [15:0] wgt_wr_addr, wgt_rd_addr;
reg act_wr_en, wgt_wr_en;
// 输入写入(简化:按顺序写入)
always @(posedge clk) begin
if (s_axis_valid && s_axis_ready) begin
act_bram[act_wr_addr] <= s_axis_data;
act_wr_addr <= act_wr_addr + 1;
end
end
// 权重写入(通过配置接口,此处示意)
always @(posedge clk) begin
if (cfg_wr_en && cfg_addr == 8'h30) begin
wgt_bram[wgt_wr_addr] <= cfg_data[15:0];
wgt_wr_addr <= wgt_wr_addr + 1;
end
end
//======================================================
// 算子单元(简化:卷积3x3乘累加示意)
//======================================================
reg [DATA_WIDTH*2-1:0] acc_reg;
reg [DATA_WIDTH-1:0] pe_out;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
acc_reg <= 0;
pe_out <= 0;
end else if (state == S_CONV) begin
// 3x3窗口乘累加(示意:取连续9个激活与9个权重)
acc_reg <= act_bram[act_rd_addr] * wgt_bram[wgt_rd_addr] +
act_bram[act_rd_addr+1] * wgt_bram[wgt_rd_addr+1] +
act_bram[act_rd_addr+2] * wgt_bram[wgt_rd_addr+2];
pe_out <= acc_reg[DATA_WIDTH-1:0];
end else if (state == S_POOL) begin
// 2x2最大值池化(示意)
pe_out <= (act_bram[act_rd_addr] > act_bram[act_rd_addr+1]) ?
act_bram[act_rd_addr] : act_bram[act_rd_addr+1];
end else if (state == S_FC) begin
// 全连接点积(示意)
pe_out <= act_bram[act_rd_addr] * wgt_bram[wgt_rd_addr];
end
end
//======================================================
// 输出写回
//======================================================
reg [DATA_WIDTH-1:0] out_bram [0:ACT_BUF_DEPTH-1];
reg [15:0] out_wr_addr;
always @(posedge clk) begin
if (state == S_STORE) begin
out_bram[out_wr_addr] <= pe_out;
out_wr_addr <= out_wr_addr + 1;
end
end
assign m_axis_data = out_bram[out_wr_addr];
assign m_axis_valid = (state == S_STORE);
assign m_axis_last = (state == S_STORE) && (out_wr_addr == layer_in_dim[layer_idx]-1);
assign s_axis_ready = (state == S_LOAD);
endmodule
运行与验证
- 仿真验证:在Vivado中创建仿真源文件,实例化上述模块。编写testbench依次完成:
① 复位释放后,通过配置接口写入层参数(如layer_num=2,第0层CONV,第1层POOL);
② 向s_axis输入16个激活数据(模拟4×4输入特征图);
③ 观察state_out状态跳转:IDLE→LOAD→CONV→STORE→LOAD→POOL→STORE→DONE;
④ 检查m_axis_valid在STORE状态拉高,且m_axis_data输出非零值。 - 综合验证:运行综合,确认无LUT/BRAM资源溢出(默认器件xc7z020),时序约束满足100MHz时钟。
通过条件:仿真波形中状态机按配置的层类型正确跳转,且每个STORE周期m_axis_valid有效时m_axis_data与BRAM中写入值一致;综合报告无错误且无关键路径时序违例。
常见问题
- 配置未生效:检查cfg_wr_en时序,需在clk上升沿前保持稳定;层参数寄存器在current_layer递增前完成写入。
- 数据通路错位:确认s_axis_ready仅在S_LOAD状态拉高,避免输入数据在非加载阶段被写入BRAM。
- 状态卡死:若layer_num配置为0,状态机停留在IDLE属正常;检查layer_num写入地址0x00是否正确。