(9)FPGA-CNN之Relu和全连接层 Relu激活函数ReLU 激活函数的核心作用是把负数特征抑制为 0同时保留正数特征不变数学表达式为ymax⁡(0,x)在 CNN 中ReLU 通常放在卷积层或全连接层之后用于引入非线性能力。如果没有 ReLU 这类激活函数多层卷积和全连接本质上仍然只是线性变换网络表达能力会很弱加入 ReLU 后网络可以学习更复杂的边缘、纹理、形状等特征。对 FPGA 来说ReLU 实现也非常简单只需要判断输入数据的符号位如果是负数就输出 0如果是正数就原样输出。FPGA实现起来也比较容易如下所示assign dout din[DW-1] ? 0 : din;全连接层全连接层Fully Connected LayerFC是 CNN 中负责分类决策的重要模块。它将前面卷积层和池化层提取出的特征图展平成一个特征向量然后与权重矩阵进行矩阵乘法并加上偏置得到最终输出YW⋅Xb。例如猫狗分类网络中经过卷积和池化后得到一个长度为 400 的特征向量FC 层可能将其映射到 2 个输出节点随后通过 Softmax 或直接比较分数大小得到最终分类结果。从 FPGA 实现角度来看全连接层本质上就是大量的乘法累加MAC运算。因此全连接层虽然数学形式简单但通常是 CNN 推理阶段权重数量最多的部分而在硬件中则常采用 DSP 复用、并行 MAC 阵列或 Systolic Array 等方式来提高计算效率。对于一个in_features400、out_features120的全连接层共需要完成400×12048000次乘加MAC运算。假设 FPGA 中仅使用一个乘法累加单元并且每个时钟周期完成一次 MAC 操作则总共需要约 48000 个时钟周期。当工作频率为 100MHz 时时钟周期为 T1/100M10ns因此该全连接层的理论计算时间约为 48000×10ns480000ns0.48ms而 60fps 视频对应的帧间隔为 1000ms60≈16.67ms因此即使仅采用单个 MAC 单元串行计算该全连接层也能在约 0.48ms 内完成推理远小于 16.67ms 的帧时间预算完全能够满足 60fps 实时处理需求FC 层通常不会成为系统实时性的瓶颈。笔者所用FPGA的DSP资源有1000多个为了追求更快的处理速度所以使用了全并行的全连接层实现方式。仿真验证可配置的输入输出feature数为了使模块方便复用把输入位宽输入特征和输出特征都设置成了可配置的形式可以用于任意形状。module FC_layer#( parameter IN_DATA_WIDTH 24, parameter IN_FEATURE 400, parameter OUT_FEATURE 10, parameter EX_WIDTH $clog2(IN_FEATURE), parameter OUT_DATA_WIDTH IN_DATA_WIDTH EX_WIDTH 8 )( input clk, input aresetn, //S_AXIS input signed [IN_DATA_WIDTH - 1:0] data, input data_en, input first_data, //M_AXIS output signed [OUT_DATA_WIDTH- 1:0] fc_result, output fc_valid ); localparam ADDR_WIDTH $clog2(IN_FEATURE); localparam OUT_CNT $clog2(OUT_FEATURE); localparam MEMORY_SIZE (1ADDR_WIDTH) * 8;权重文件的加载笔者仿真的是400个输入10输出的有多少个out feature就使用多少个乘法器来实现并行的累加。不同的out feature有不同的weight系数一般使用generate语句来加载不然如果out feature特别多代码会特别长。//不同weight权重的加载方式 function automatic string int2str(input int num); string s; s ; if (num 0) begin return 0; end while (num 0) begin s {string(8h30 num%10), s}; num num / 10; end return s; endfunction genvar j; generate for(j 0;j OUT_FEATURE;j j 1)begin:gen_weight localparam string FILENAME {../rtl/coef, int2str(j), .mem}; xpm_memory_sprom #( .ADDR_WIDTH_A (ADDR_WIDTH ), .MEMORY_SIZE (MEMORY_SIZE ), .READ_DATA_WIDTH_A (8 ), .MEMORY_INIT_FILE (FILENAME ), .SIM_ASSERT_CHK (1 ), .READ_LATENCY_A (1 ), .MESSAGE_CONTROL (1 ), .MEMORY_PRIMITIVE (block ) )u_rom( .clka (clk ), .rsta (1b0 ), .ena (1b1 ), .regcea (1b1 ), .addra (addr ), .injectsbiterra (1b0 ), .injectdbiterra (1b0 ), .douta (weight[j] ) ); end endgenerate实现累加always (posedge clk or negedge aresetn) begin if(~aresetn)begin for(i 0;i OUT_FEATURE;i i1 )begin r_fc_result[i] 0; end end else if(first_data)begin for(i 0;i OUT_FEATURE;i i1 )begin r_fc_result[i] 0; end end else if(en_d2)begin for(i 0;i OUT_FEATURE;i i1 )begin r_fc_result[i] r_fc_result[i] data_d2 * weight[i]; end end end结果对比rtl和python使用同样的输入和weight系数得到的10个输出完全一致。总结本章节完成了参数化全连接层的 Verilog RTL 设计与功能验证。模块采用全参数化架构输入特征维度、输出特征维度、运算位宽均可通过顶层参数灵活配置无需改动核心运算逻辑即可适配不同规模的全连接运算场景。验证环节以 Python NumPy 的矩阵内积运算结果作为黄金参考模型构造多组随机输入与权重测试用例比对 RTL 仿真输出与参考结果与参考模型完全一致。该模块为后续 FPGA 端神经网络加速阵列的搭建提供了基础运算单元也沉淀了「RTL 模块化实现 Python 参考模型对标」的可复用验证流程。