卷积神经网络LeNet-5的RTL实现(二)

卷积神经网络LeNet-5的RTL实现(二):Padding

前文回顾

上一期文章简单介绍了工程用到的一些结构性电路:FIFO、Shift RAM和二叉加法树。这篇文章将使用FIFO和padding电路实现卷积神经网络的padding。

Padding简介

卷积神经网络结构中,padding是通过为输入FMAP边缘补充像素的方式改变输出FMAP的尺寸。为了不改变原图信息,padding补充的像素值一般为0,下图展示了N为1的padding:


令输入FMAP尺寸为Ni×Ni,卷积核尺寸为K×K,窗口滑动步长为S,则可以计算出输出FMAP的长宽为
(Ni + 2 * N - K + 1) / S

特征图输入

上文提到,本工程中的FMAP数据在模块和层之间均以串行方式流动,特征图输入时会被被打平成一维。padding前后FMAP的尺寸会发生变化,因此在输入端设置有一个FIFO对图像数据进行暂存。后级padding电路可以根据需要读取输入FIFO中的数据。

Padding电路

接口

Padding电路的接口如下所示:

module PADDING
#(
	parameter DATA_WIDTH = 16,				//数据位宽
	parameter FMAP_SIZE = 28,				//输入特征图尺寸
	parameter N = 2							//padding尺寸
)
(
	input clk,
	input rst_n,
	input ena,								//电路使能信号,高电平有效
	input clear,							//电路数据清零信号,上升沿有效
	
	input  [DATA_WIDTH-1:0]fmap_raw,		//输入特征图
	output [DATA_WIDTH-1:0]fmap_pad,		//补零后输出特征图
	
	output rd_en,							//FIFO数据读取信号
	output reg valid,						//输出有效信号
	output reg done							//padding完成信号
);

计数信号

Padding电路的核心是判断当前输入像素在FMAP中的位置,以此作为是否补零的依据。模块中使用多个计数变量进行像素位置的记录:

	reg [CNT_BIT_NUM-1:0]cnt;
	reg [LINE_CNT_BIT_NUM-1:0]cnt_col;
	reg [LINE_CNT_BIT_NUM-1:0]cnt_row;

cnt为总输出像素计数,cont_col和cnt_row分别记录列和行输出像素。Padding电路在每个时钟周期输出一个像素值,因此每个周期cnt、cnt_col、cnt_row分别加一:

	always @(posedge clk or negedge rst_n)
		if(!rst_n)
			cnt <= 0;
		else if(clear)
			cnt <= 0;
		else if(ena) begin
			if(cnt == (FMAP_SIZE + N * 2) * (FMAP_SIZE + N * 2) - 1) 
				cnt <= 0;
			else
				cnt <= cnt + 1;
		end
		
	always @(posedge clk or negedge rst_n)
		if(!rst_n) begin
			cnt_col <= 0;
			cnt_row <= 0;
		end
		else if(clear) begin
			cnt_col <= 0;
			cnt_row <= 0;	
		end
		else if(ena) begin
			if (cnt_col >= FMAP_SIZE + N * 2 - 1) begin
				cnt_col <= 0;
				cnt_row <= cnt_row + 1;
			end
			else 
				cnt_col <= cnt_col + 1;
		end

外部标志信号

validdone是电路的外部标志信号,valid表示padding电路正输出有效数据,高电平有效;done表示电路输出结束,上升沿有效。在电路被使能的情况下,valid信号在cnt计数到达输出特征图的最后一个像素之前都有效,done信号则在cnt计数到达输出特征图的最后一个像素时输出一个高电平脉冲。

	assign valid = (ena) ? ((cnt < (FMAP_SIZE + N * 2) * (FMAP_SIZE + N * 2) - 1) ? 1 : 0) : 0;
	assign done = (ena) ? ((cnt == (FMAP_SIZE + N * 2) * (FMAP_SIZE + N * 2) - 1) ? 1 : 0) : 0;

输入和输出信号

Padding模块的输入数据来自FIFO,因此需要提供一个FIFO的读使能信号rd_en。由计数信号可得知下一个输出像素在特征图的有效数据位置还是padding位置。如果在有效数据位置,需要读取FIFO数据并在下一个时钟输出;若在padding位置,则不需要读取FIFO数据,在下一个时钟直接输出0即可。rd_en信号的逻辑如下:

	generate 
		if(N != 0) begin: padding
			assign rd_en = (!ena || cnt_row < N || cnt_row > FMAP_SIZE + N - 1) ? 0 : ((cnt_col < N - 1 || cnt_col > FMAP_SIZE + N - 2) ? 0 : 1);
		end
		else begin: no_padding
			assign rd_en = (ena) ? 1 : 0;
		end
	endgenerate

这里使用了generate-if块来生成rd_en:当N为0时不需要padding,每个时钟都需要读取FIFO数据;当N不为0时,行计数或列计数值在输入特征图的长宽与输出特征图的长宽之间时,下一个输出像素位于padding区域,不需要读取FIFO数据,rd_en为低;反之则位于有效数据区域,需要读取FIFO数据,rd_en为高。
输出信号fmap_pad的逻辑也基本相同:

assign fmap_pad = (!ena || cnt_row < N  || cnt_row > FMAP_SIZE + N - 1) ? 0 : ((cnt_col < N || cnt_col > FMAP_SIZE + N - 1) ? 0 : fmap_raw);

可以注意到,rd_enfmap_pad在对cnt_col的判断中相差了一个周期,这是因为在rd_en有效后,下一个周期FIFO才会输出数据,本质上rd_en记录的是下一个像素的信息;而fmap_pad是当前的输出数据,而且是一个组合电路的输出,立即有效。因此rd_en的信号变化要比fmap_pad提前一个周期。

电路完整代码

/* PADDING.v */
module PADDING
#(
	parameter DATA_WIDTH = 16,
	parameter FMAP_SIZE = 28,
	parameter N = 2
)
(
	input clk,
	input rst_n,
	input ena,
	input clear,
	input [DATA_WIDTH-1:0]fmap_raw,
	output [DATA_WIDTH-1:0]fmap_pad,
	output rd_en,
	output valid,
	output done
);

	function integer clogb2 (input integer bit_depth);
	begin
		for(clogb2=0; bit_depth>0; clogb2=clogb2+1)
		bit_depth = bit_depth >> 1;
	end
	endfunction
	
	localparam CNT_BIT_NUM = clogb2((FMAP_SIZE+N*2)*(FMAP_SIZE+N*2));
	localparam LINE_CNT_BIT_NUM = clogb2(FMAP_SIZE+N*2);
	
	reg [CNT_BIT_NUM-1:0]cnt;
	reg [LINE_CNT_BIT_NUM-1:0]cnt_col;
	reg [LINE_CNT_BIT_NUM-1:0]cnt_row;
	
	assign valid = (ena) ? ((cnt < (FMAP_SIZE + N * 2) * (FMAP_SIZE + N * 2) - 1) ? 1 : 0) : 0;
	assign done = (ena) ? ((cnt == (FMAP_SIZE + N * 2) * (FMAP_SIZE + N * 2) - 1) ? 1 : 0) : 0;
	assign fmap_pad = (!ena || cnt_row < N  || cnt_row > FMAP_SIZE + N - 1) ? 0 : ((cnt_col < N || cnt_col > FMAP_SIZE + N - 1) ? 0 : fmap_raw);
	
	//conditional generate
	generate 
		if(N != 0) begin: no_padding
			assign rd_en = (!ena || cnt_row < N|| cnt_row > FMAP_SIZE + N - 1) ? 0 : ((cnt_col < N - 1 || cnt_col > FMAP_SIZE + N - 2) ? 0 : 1);
		end
		else begin: padding
			assign rd_en = (ena) ? 1 : 0;
		end
	endgenerate
	
	always @(posedge clk or negedge rst_n)
		if(!rst_n)
			cnt <= 0;
		else if(clear)
			cnt <= 0;
		else if(ena) begin
			if(cnt == (FMAP_SIZE + N * 2) * (FMAP_SIZE + N * 2) - 1) 
				cnt <= 0;
			else
				cnt <= cnt + 1;
		end
		
	always @(posedge clk or negedge rst_n)
		if(!rst_n) begin
			cnt_col <= 0;
			cnt_row <= 0;
		end
		else if(clear) begin
			cnt_col <= 0;
			cnt_row <= 0;	
		end
		else if(ena) begin
			if (cnt_col >= FMAP_SIZE + N * 2 - 1) begin
				cnt_col <= 0;
				cnt_row <= cnt_row + 1;
			end
			else 
				cnt_col <= cnt_col + 1;
		end
				
endmodule

TestBench

`timescale 1ns / 1ns

module tb_padding();

	parameter 	DATA_WIDTH = 16,
				FMAP_SIZE = 28,
				PADDING_SIZE = 2;
   	
   	reg clk, rst_n;
   	reg ena;
   	wire fifo_rd_en;
   	reg fifo_wr_en;
   	
   	reg[DATA_WIDTH-1:0] fmap_data;
   	wire[DATA_WIDTH-1:0] fifo_out;
   	wire[DATA_WIDTH-1:0] padding_out;
   	
   	wire padding_done;
   	wire padding_valid;
   	
	initial begin
		clk = 0;
		rst_n = 0;
		ena = 0;
		fmap_data = 0;
		
		#100;
		rst_n = 1;
		ena = 1;
		fifo_wr_en = 1;
		
		//generate feature map
		repeat(784) begin
			#100
			clk = ~clk;
			#100
			clk = ~clk;			
			fmap_data = fmap_data + 1;
		end
		fifo_wr_en = 0;
		fmap_data = 0;
		
		forever begin
			#100
			clk = ~clk;
			#100
			clk = ~clk;			
		end	
	end
	
	FIFO
	# (	
		.DATA_WIDTH(DATA_WIDTH),
		.BUF_SIZE(FMAP_SIZE*FMAP_SIZE)
	)
	u_fifo_1(
		.clk(clk),
		.rst_n(rst_n),
		.buf_in(fmap_data),
		.wr_en(fifo_wr_en),
		.rd_en(fifo_rd_en),
		.rd_rewind(),
		.fifo_empty(),
		.fifo_full(),
		.buf_out(fifo_out) 
	);
	
	PADDING
	#(
		.DATA_WIDTH(DATA_WIDTH),
		.FMAP_SIZE(FMAP_SIZE),
		.N(PADDING_SIZE)
	)
	u_padding
	(
		.clk(clk),
		.rst_n(rst_n),
		.clear(),
		.ena(ena),
		.fmap_raw(fifo_out),
		.fmap_pad(padding_out),
		.rd_en(fifo_rd_en),
		.valid(padding_valid),
		.done(padding_done)
	);
	
endmodule

本工程的FMAP测试数据均为从0开始,步长为1的序列,在initial中的repeat块中生成,在此TestBench中为0-783。fmap_data首先输入一个FIFO中暂存,然后被padding模块读取,最后输出的padding_out为补零后的FMAP。

仿真结果

在这里插入图片描述
可以看到,在输入FMAP第一行最后一个像素27结束后,padding_out输出了4个周期的0,即左右两侧的补零。与此同时,在27输出的前一个周期rd_en就已经拉低,不再读取FIFO中数据。

总结

本期文章实现了卷积神经网络中的padding电路,利用FIFO暂存特征图数据并受padding模块的控制进行数据输出。Padding电路的实现关键是处理好rd_enfmap_pad两个信号。下一期文章将详细讲解基于Shift RAM的卷积层的实现。

  • 9
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值