LeakyRelu模块实现
LeakyRelu模块实现
模块功能
-
LeakyRelu模块实现的主要功能用于对卷积神经网络的输出进行激活函数处理。LeakyRelu模块的功能是将输入的8个8位有符号整数,根据一个预设的斜率参数,进行非线性变换,输出8个8位有符号整数。
-
激活函数的计算公式是:
\[y = \begin{cases} x, & \text{if } x \geq 0 \\ a \times x, & \text{if } x < 0 \end{cases} \]其中,\(x\)是输入数据,\(y\)是输出数据,\(a\)是激活函数的参数。
如果输入的整数大于等于零,输出不变;如果输入的整数小于零,输出等于输入乘以斜率参数。斜率参数是从一个64位的数据流stream_rx_data中读取的,每次读取8位,并存储在一个5位地址的RAM(leakyrelu_ram_ip)中。
-
LeakyRelu模块可以处理不同的数据类型(有符号或无符号)、不同的站点类型(中间站点或边缘站点)和不同的批次类型(单批次或多批次)。
模块结构
-
LeakyRelu模块由以下几个子模块组成:
-
main_ctrl
:主控制模块,负责从AXI4-Lite接口读取寄存器的配置参数,如数据类型、卷积核大小、特征图尺寸、乘法因子、偏移量等,并根据这些参数控制其他模块的工作状态和读写地址。 -
stream_rx
:流接收模块,负责从Axi4-Stream接口接收数据,并根据数据类型将其分为特征数据、权重数据、偏置数据和LeakyRelu系数数据,输出给相应的缓存模块。 -
bias_buffer
:偏置缓存模块,负责存储8个通道的从流接收模块stream_rx
接收到的偏置数据,每个通道32位有符号整数,共256位。并根据主控制模块main_ctrl
的读地址,输出给卷积核模块的偏置值。 -
feature_buffer
:特征图缓存模块,负责存储从流接收模块接收到的特征图数据,每个数据64位无符号整数,共512位。并根据卷积核模块的读使能信号,输出特征图数据。 -
weight_top_buffer
:权重缓存模块,负责存储从流接收模块接收到的8个通道的3x3卷积核权重数据,每个权重72位无符号整数,共576位。并根据主控制模块的读地址和卷积核大小,输出对应通道和位置的权重值。 -
conv_kernel_top
:卷积核模块,负责对输入的特征数据和权重数据进行3x3卷积运算,并加上偏置数据,输出8个通道的卷积结果,每个结果24位有符号整数,共192位。(即将特征图数据和权重数据进行逐元素相乘并累加,并加上偏置值,得到卷积输出结果) -
quant_int8_8ch
:量化模块,负责将卷积输出结果从32位有符号整数转换为8位无符号整数,即进行量化操作。量化操作需要用到主控制模块提供的乘法因子、偏移量和零点值,具体公式为:量化输出 = (卷积输出 * 乘法因子) >> 偏移量 + 零点值。量化操作可以减少数据位宽,节省存储空间和传输带宽。 -
leakyrelu_ram_ip
:LeakyRelu系数存储模块,负责存储8个通道的LeakyRelu系数,并根据写地址和写使能信号进行写入操作。 -
leaky_relu
:LeakyRelu计算模块,负责对量化模块的输出进行LeakyRelu变换,根据输入数据和LeakyRelu系数进行乘法和比较运算,输出8位整数结果。负责实现LeakyRelu激活函数,即对量化输出进行非线性变换。LeakyRelu激活函数的公式为:激活输出 = max(量化输出, 量化输出 * 负斜率)。其中,负斜率是从流接收模块接收到的激活函数参数之一。LeakyRelu激活函数可以增加网络的非线性能力,提高目标检测的准确度。
-
LeakyRelu模块实现的仿真过程
- 编写测试平台(testbench)
- 生成时钟信号、复位信号、寄存器信号和流数据信号
- 监测LeakyRelu模块的输出信号
- 绘制测试计划(test plan)
- 设计不同的测试场景(test case)
- 覆盖不同的数据类型、批处理类型、特征选择、权重选择、偏置选择等组合
- 覆盖不同的输入数据范围和分布
- 设计不同的测试场景(test case)
- 运行仿真工具(ModelSim)
- 观察波形图(waveform)
- 检查LeakyRelu模块的输出是否符合预期
- 检查是否存在时序问题或逻辑错误
- 观察波形图(waveform)
- 分析代码覆盖率(Coverity)
- 检查测试平台覆盖了多少LeakyRelu模块的代码
- 检查是否存在未覆盖或冗余的代码
- LeakyRelu模块的实现主要包含以下几个部分:
- wr_addr: 写地址寄存器,用于存储当前写入LeakyRelu参数RAM的地址。每当接收到一个有效的LeakyRelu参数时,写地址加一;否则复位为零。
- data_arr: 数据到达寄存器,用于延迟一个时钟周期后输出输入数据的有效信号。这是为了保证输入数据和输出数据在同一个时钟周期内有效。
- ch_data_vld_o: 输出数据的有效信号,由数据到达寄存器的最高位决定。
- U0_leakyrelu_ram_ip: LeakyRelu参数RAM,用于存储64个LeakyRelu参数,每个参数64位无符号整数。根据写地址和写使能信号写入数据,根据读地址输出数据。
- ch0_data_o ~ ch7_data_o: 输出数据,由输入数据和LeakyRelu参数RAM的输出数据进行运算得到。如果输入数据大于等于零,则输出不变;如果输入数据小于零,则输出等于输入数据乘以LeakyRelu参数RAM的输出数据的低8位,并右移56位。
- 模块仿真时需要观察以下信号:
- s_axis_s2mm_tdata:Axi4-Stream接口的数据信号,用于输出LeakyRelu模块的结果数据。
- s_axis_s2mm_tkeep:Axi4-Stream接口的有效位信号,用于指示数据信号的有效字节。
- s_axis_s2mm_tvalid:Axi4-Stream接口的有效信号,用于指示数据信号是否有效。
- s_axis_s2mm_tready:Axi4-Stream接口的就绪信号,用于反馈外部设备是否准备好接收数据信号。
- s_axis_s2mm_tlast:Axi4-Stream接口的结束信号,用于指示数据信号是否为一帧的最后一个字。
四舍五入功能
- 四舍五入功能是在量化模块中实现的,它是将卷积核模块输出的32位有符号整数转换为8位整数的一部分过程。
- 四舍五入功能的原理是:
- 首先,根据乘法因子和移位因子对32位有符号整数进行放缩,得到一个中间结果。乘法因子是一个15位无符号整数,移位因子是一个8位无符号整数,中间结果是一个38位有符号整数。
- 然后,根据零点对中间结果进行偏移,得到一个偏移结果。零点是一个8位无符号整数,偏移结果是一个39位有符号整数。
- 最后,对偏移结果进行四舍五入,得到一个8位整数。四舍五入的方法是:
- 如果偏移结果的第31位(从右往左数)是0,表示偏移结果是正数或零,则直接截取偏移结果的[30:23]作为8位整数输出。
- 如果偏移结果的第31位是1,表示偏移结果是负数,则先对偏移结果取反加一得到其补码形式,然后判断其[22:0]是否全为0。如果全为0,则直接截取其补码形式的[30:23]作为8位整数输出;如果不全为0,则先将其补码形式的[30:23]加一得到一个临时结果,然后截取临时结果的[7:0]作为8位整数输出。
四舍五入功能是指将一个实数按照一定的规则转换为一个整数,通常是将小数部分舍去或加1。在本模块中,并没有直接使用四舍五入功能,而是使用了一个quant_int8_8ch模块来实现对24位有符号整数数据的量化。这个模块使用了乘法器、移位器和零点偏移来将输入数据转换为8位整数数据。具体的计算公式是:
\[y = \text{clamp}(\frac{x \times mult}{2^{shift}} + zero\_point, 0, 255) \]其中,\(x\)是输入数据,\(y\)是输出数据,\(mult\)是乘法因子,\(shift\)是移位量,\(zero\_point\)是零点偏移量。\(\text{clamp}(x, a, b)\)表示将\(x\)限制在\(a\)和\(b\)之间,如果\(x < a\)则返回\(a\),如果\(x > b\)则返回\(b\)。这个模块相当于对输入数据进行了放缩和偏移,并且将超出范围的值截断为0或255。这种量化方式可以减少数据的位宽和存储空间,但也会引入一定的误差。如果要实现四舍五入功能,可以将上式中的\(\text{clamp}\)函数替换为\(\text{round}\)函数,即将\(x\)四舍五入到最接近的整数。
reg [ 4:0] wr_addr ;
reg [ 1:0] data_arr ;
//=============================================================================
//************** Main Code **************
//=============================================================================
always @(posedge sclk or negedge s_rst_n) begin
if(s_rst_n == 1'b0)
wr_addr <= 'd0;
else if(stream_leakyrelu_vld == 1'b1)
wr_addr <= wr_addr + 1'b1;
else
wr_addr <= 'd0;
end
always @(posedge sclk) begin
data_arr <= {data_arr[0], ch_data_vld_i};
end
assign ch_data_vld_o = data_arr[1];