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

运行与验证

  1. 仿真验证:在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输出非零值。
  2. 综合验证:运行综合,确认无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是否正确。