一起学习用Verilog在FPGA上实现CNN----卷积层设计

Posted 鲁棒最小二乘支持向量机

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一起学习用Verilog在FPGA上实现CNN----卷积层设计相关的知识,希望对你有一定的参考价值。

1 打开Vivado工程

Vivado工程文件如图:

打开Vivado软件,打开工程,如图:

自动升级到当前版本,如图:

暂时选择现有开发板的型号,如图:

出现一条警告性信息,暂时先不管,点击OK:

可以看到完整的工程文件包含如下图:

2 卷积层设计

自顶而下分析卷积层的设计过程

2.1 Multi Filter Layer

图为该项目的一个卷积层,其中包含了多个卷积核(Filter),模块的输入为图像矩阵和卷积核设置参数,输出为卷积提取的特征矩阵


图片来自附带的技术文档《Hardware Documentation》

卷积层的原理图如图所示,其中filters的位宽为2400,image的位宽是16384,该层卷积的输出位宽是75264

  • filters位宽计算:卷积核大小为5x5,卷积核个数为6,数据位宽为float16(16bits),所以5x5x6x16=2400
  • image位宽计算:手写数字图像大小为32x32,数据位宽为float16,所以32x32x16=16384
  • outputConv位宽计算:28x28x6x16=75264,式中28x28表示卷积层输出特征矩阵的长和宽,6表示卷积核的数量,数据位宽是float16
  • 补充卷积输出特征尺寸计算:m=[(n-k+2xp)/s]+1,n表示输入图像或特征矩阵的尺寸,k表示卷积核的尺寸,s表示卷积核滑动的步长(stride),p表示填充(padding)。例如,图像大小为32x32,卷积核大小为5x5,步长为1,m=[(32-5+2x0)/1]+1=28

2.2 Single Filter Layer

单个卷积核层的设计如图,输入为图像矩阵image和单个卷积核filter,输出卷积核处理的特征矩阵


图片来自附带的技术文档《Hardware Documentation》

原理图如图所示,filter的位宽为400,image的位宽是16384,输出位宽是12544

  • filters位宽计算:卷积核大小为5x5,卷积核个数为1,数据位宽为float16,所以5x5x1x16=400
  • image位宽计算:手写数字图像大小为32x32,数据位宽为float16,所以32x32x16=16384
  • outputConv位宽计算:28x28x1x16=12544,式中28x28表示卷积层输出特征矩阵的长和宽,1表示卷积核的数量,数据位宽是float16

2.3 Convolution Unit

卷积单元如图所示,输入为卷积核filter和卷积核窗口覆盖的图像image,计算输出该窗口提取的特征


图片来自附带的技术文档《Hardware Documentation》

原理图如图所示,filter的位宽为400,卷积核窗口覆盖的图像image的位宽是400,输出位宽是16

  • filters位宽计算:卷积核大小为5x5,卷积核个数为1,数据位宽为float16,所以5x5x1x16=400
  • image位宽计算:手写数字图像大小为32x32,卷积核窗口覆盖的图像大小为5x5,数据位宽为float16,所,5x5x16=400
  • result位宽计算:输出结果为float16数据类型的数,具体计算见 2.4 Processing Element 章节

2.4 Processing Element

卷积单元具体实现如图所示,即相乘相加操作。卷积计算具体操作就是点乘,本质就是乘法和加法。图中输入为float16类型数据A和B,输出float16数据类型的结果

图片来自附带的技术文档《Hardware Documentation》

原理图如图所示,可以看到输入floatA和floatB,以及输出result位宽均为16

3 模块功能解析

自底向上分析每个模块的功能和具体实现

3.1 Processing Element

如图所示Processing Element由FM(floatMult16),FADD(floatAdd16),result_reg三个单元组成

  • FM(floatMult16)单元是执行两个float16数据的乘法
  • FADD(floatAdd16)单元是执行两个float16数据的加法
  • result_reg寄存器,存放的是新的求和,将电路从组合逻辑转为同步时序电路,保证数据的同步

3.2 Convolution Unit

卷积单元完整的顶层原理图如图所示,对一个卷积核和该卷积核覆盖的图像区域(可以称为窗口)进行计算,输出一个计算结果(float16)

3.3 Single Filter Layer

Single Filter Layer原理图如图所示,由1个RF selector和14个CU组成,该部分是计算一个卷积核与一幅图像的卷积,输出卷积提取的完整图像的特征。

RF selector的作用:将卷积核覆盖的图像区域(可以称为窗口)的数据对应传输给14个CU,输入图像尺寸为32x32x16,卷积核大小为5x5x16,卷积核滑动步长为1,此时一幅完整图像将产生28x28个窗口数据,每个窗口数据为5x5x16。因为14个CU是并行计算的,故RF selector输出位宽为14x5x5x16=5600

为什么选择使用14个CU,作者给出的解释是:LUT的数量在单个或多个卷积核模块中呈指数增长,实验对比后,最终决定使用CU的数量等于输出特征中单行像素数量的一半。例如,输入图像32x32,卷积核5x5,输出特征为28x28,故CU的数量等于28/2=14

3.4 Multi Filter Layer

Multi Filter Layer原理图如图所示,由2个convLayerSingle组成,即并行度为2。上述内容可知Multi Filter Layer的输入是图像和6个卷积核,因此6个卷积核分为2个一组,循环3次输入到convLayerSingle,即每次执行2个卷积核与图像的卷积

4 代码实现

4.1 新建工程

新建工程,操作如图所示:

输入工程名字和工程路径,如图:

选择创建RTL工程,如图:

直接点击Next:

继续点击Next:

添加芯片型号,操作如图:

完成创建:

4.2 floatAdd16

4.2.1 设计输入

创建工程文件,操作如图:

创建floatAdd16文件:

创建完成:

双击打开,输入如下代码:

module floatAdd16 (floatA,floatB,sum);
	
input [15:0] floatA, floatB;  // 输入float16数据A和B
output reg [15:0] sum;   // 输出为float16数据sum

reg sign; // 输出的正负标志位
reg signed [5:0] exponent; //输出数据的指数,有正负故选择有符号数
reg [9:0] mantissa; //输出数据的尾数
reg [4:0] exponentA, exponentB; //输出数据的阶数
reg [10:0] fractionA, fractionB, fraction;	//fraction = 1,mantissa 暂存位
reg [7:0] shiftAmount;// 移位寄存器,计算加法时配平阶数
reg cout;

always @ (floatA or floatB) begin
	exponentA = floatA[14:10];
	exponentB = floatB[14:10];
	fractionA = 1'b1,floatA[9:0];
	fractionB = 1'b1,floatB[9:0]; 
	
	exponent = exponentA;

	if (floatA == 0) begin						//special case (floatA = 0)
		sum = floatB;
	end else if (floatB == 0) begin					//special case (floatB = 0)
		sum = floatA;
	end else if (floatA[14:0] == floatB[14:0] && floatA[15]^floatB[15]==1'b1) begin //A与B互为相反数的情况
		sum=0;
	end else begin
		if (exponentB > exponentA) begin   // 配平阶数,使得A和B在同一阶数
			shiftAmount = exponentB - exponentA;
			fractionA = fractionA >> (shiftAmount);
			exponent = exponentB;
		end else if (exponentA > exponentB) begin 
			shiftAmount = exponentA - exponentB;
			fractionB = fractionB >> (shiftAmount);
			exponent = exponentA;
		end
		if (floatA[15] == floatB[15]) begin			//A与B同符号
			cout,fraction = fractionA + fractionB;
			if (cout == 1'b1) begin
				cout,fraction = cout,fraction >> 1;
				exponent = exponent + 1;
			end
			sign = floatA[15];
		end else begin						//A与B符号不相同
			if (floatA[15] == 1'b1) begin   // A为负数
				cout,fraction = fractionB - fractionA;  // B-A 
			end else begin
				cout,fraction = fractionA - fractionB;  // A-B
			end
			sign = cout;
			if (cout == 1'b1) begin
				fraction = -fraction;  // 0-负数,求出该数的绝对值
			end else begin
			end
			//对franction进行阶数配平,求出尾数
			if (fraction [10] == 0) begin
				if (fraction[9] == 1'b1) begin
					fraction = fraction << 1;
					exponent = exponent - 1;
				end else if (fraction[8] == 1'b1) begin
					fraction = fraction << 2;
					exponent = exponent - 2;
				end else if (fraction[7] == 1'b1) begin
					fraction = fraction << 3;
					exponent = exponent - 3;
				end else if (fraction[6] == 1'b1) begin
					fraction = fraction << 4;
					exponent = exponent - 4;
				end else if (fraction[5] == 1'b1) begin
					fraction = fraction << 5;
					exponent = exponent - 5;
				end else if (fraction[4] == 1'b1) begin
					fraction = fraction << 6;
					exponent = exponent - 6;
				end else if (fraction[3] == 1'b1) begin
					fraction = fraction << 7;
					exponent = exponent - 7;
				end else if (fraction[2] == 1'b1) begin
					fraction = fraction << 8;
					exponent = exponent - 8;
				end else if (fraction[1] == 1'b1) begin
					fraction = fraction << 9;
					exponent = exponent - 9;
				end else if (fraction[0] == 1'b1) begin
					fraction = fraction << 10;
					exponent = exponent - 10;
				end 
			end
		end
		mantissa = fraction[9:0];
		if(exponent[5]==1'b1) begin //exponent is negative
			sum = 16'b0000000000000000;
		end
		else begin
			sum = sign,exponent[4:0],mantissa;//组合数据
		end		
	end		
end

endmodule

如图所示:

4.2.2 分析与综合

对设计进行分析,操作如图:

分析后的设计,Vivado自动生成原理图,如图:

对设计进行综合,操作如图:

综合完成后,弹出窗口如下,直接关闭:

4.2.3 功能仿真

创建TestBench,操作如图所示:

创建激励文件,输入文件名:

创建完成:

双击打开,输入激励代码:

`timescale 100 ns / 10 ps

module tb_floatAdd16();
reg [15:0] floatA;
reg [15:0] floatB;
wire [15:0] sum;

initial begin
	
	// A + B = 16'h3800 = 0.5
	#0
	floatA = 16'h34CD; // 0.3
	floatB = 16'h3266; // 0.2

	// A + B = 34CD
	#10
	floatA = 16'h34CD;
	floatB = 16'h0000; // 0
	#10
	$stop;
end

floatAdd16 FADD
(
	.floatA(floatA),
	.floatB(floatB),
	.sum(sum)
);

endmodule

如图所示:

开始进行仿真,操作如下:

仿真操作,如图:

调整波形,进行观察:

仿真波形如图:

关闭仿真:

点击OK:

4.3 floatMult16

4.3.1 设计输入

创建floatMult16文件,如图:

双击打开,输入如下代码:

module floatMult16 (floatA,floatB,product);

input [15:0] floatA, floatB;  // 输入为两个float16数据A和B
output reg [15:0] product;   // 输出为float16数据

reg sign; // 输出的正负标志位
reg signed [5:0] exponent; // 输出数据的指数,有正负故选择有符号数
reg [9:0] mantissa; // 输出数据的小数
reg [10:0] fractionA, fractionB;	//fraction = 1,mantissa 计算二进制数据最高位 补1
reg [21:0] fraction; // 相乘结果参数


always @ (floatA or floatB) begin
	if (floatA == 0 || floatB == 0) begin  // A或者B为0的情况
		product = 0;
	end else begin
		sign = floatA[15] ^ floatB[15];  // 异或门判断输出的正负
		exponent = floatA[14:10] + floatB[14:10] - 5'd15 + 5'd2; // 由于借位给fractionA和fractionB,需要先补齐两位指数
	
		fractionA = 1'b1,floatA[9:0]; // 借位给fractionA
		fractionB = 1'b1,floatB[9:0]; // 借位给fractionB
		fraction = fractionA * fractionB; // 计算二进制乘法
		//  找到第一个不为0的数字并对指数进行匹配处理
		if (fraction[21] == 1'b1) begin
			fraction = fraction << 1;
			exponent = exponent - 1; 
		end else if (fraction[20] == 1'b1) begin
			fraction = fraction << 2;
			exponent = exponent - 2;
		end else if (fraction[19] == 1'b1) begin
			fraction = fraction << 3;
			exponent = exponent - 3;
		end else if (fraction[18] == 1'b1) begin
			fraction = fraction << 4;
			exponent = exponent - 4;
		end else if (fraction[17] == 1'b1) begin
			fraction = fraction << 5;
			exponent = exponent - 5;
		end else if (fraction[16] == 1'b1) begin
			fraction = fraction << 6;
			exponent = exponent - 6;
		end else if (fraction[15] == 1'b1) begin
			fraction = fraction << 7;
			exponent = exponent - 7;
		end else if (fraction[14] == 1'b1) begin
			fraction = fraction << 8;
			exponent = exponent - 8;
		end else if (fraction[13] == 1'b1) begin
			fraction = fraction << 9;
			exponent = exponent - 9;
		end else if (fraction[12] == 1'b0) begin
			fraction = fraction << 10;
			exponent = exponent - 10;
		end 
	
		mantissa = fraction[21:12];
		if(exponent[5]==1'b1) begin //exponent is negative
			product=16'b0000000000000000;
		end
		else begin
			product = sign,exponent[4:0],mantissa;// 拼接输出数据
		end
	end
end

endmodule

如图所示:

4.3.2 分析与综合

将floatMult16设置为顶层:

关闭上次的分析文件:

对设计进行分析,操作如图:

分析后的设计,Vivado自动生成原理图,如图:

对设计进行综合,操作如图:

4.3.3 功能仿真

创建TestBench,操作如图所示:

双击打开,输入激励代码:

`timescale 100 ns / 10 ps

module tb_floatMult16();
reg [15:0] floatA;
reg [15:0] floatB;
wire [15:0] product;

initial begin
	
	// 4 * 5
	#0
	floatA = 16'b0100010000000000;
	floatB = 16'b0100010100000000;

	// 0.0004125 * 0
	#10
	floatA = 16'b0000111011000010;
	floatB = 16'b0000000000000000;

	#10
	$stop;
end

floatMult16 FM
(
	.floatA(floatA),
	.floatB(floatB),
	.product(product)
);

endmodule

如图所示:

将tb_floatMult16设置为顶层:

开始进行仿真,操作如下:

添加仿真对象,操作如图:

开始仿真,如图:

仿真波形,如图:

4.4 Processing Element

4.4.1 设计输入

创建processingElement16文件,如图:

双击打开,输入如下代码:

module processingElement16(clk,reset,floatA,floatB,result);

parameter DATA_WIDTH = 16;  // 数据类型float16

input clk, reset;
input [DATA_WIDTH-1:0] floatA, floatB; // 输入float16数据A和B
output reg [DATA_WIDTH-1:0] result;  // 输出float16数据

wire [DATA_WIDTH-1:0] multResult;
wire [DATA_WIDTH-1:0] addResult;

floatMult16 FM (floatA,floatB,multResult); // float16乘法运算
floatAdd16 FADD (multResult,result,addResult);// float16加法运算

always @ (posedge clk or posedge reset) begin
	if (reset == 1'b1) begin
		result = 0;    // 开始时,result赋值为0
	end else begin
		result = addResult;  // 求和结果不断更新为result,即为累加操作,result作为最后的输出
	end
end

endmodule

如图所示:

4.4.2 分析与综合

关闭上次的分析文件:

将processingElement16设置为顶层:

对设计进行分析,操作如图:

分析后的设计,Vivado自动生成原理图,如图:

对设计进行综合,操作如图:

4.4.3 功能仿真

创建TestBench,操作如图所示:

双击打开,输入激励代码:

`timescale 100 ns / 10 ps

module tb_processingElement16();
reg clk,reset;
reg [15:0] floatA, floatB;
wire [15:0] result;

localparam PERIOD = 100;

always
	#(PERIOD/2) clk = ~clk;

initial begin
	#0
	clk = 1'b0;
	reset = 1;
	// A = 2 , B = 3
	floatA = 16'h4000;
	floatB = 16'h4200;

	一起学习用Verilog在FPGA上实现CNN----integrationFC设计

一起学习用Verilog在FPGA上实现CNN----池化层设计

一起学习用Verilog在FPGA上实现CNN----integrationConv设计

一起学习用Verilog在FPGA上实现CNN----SoftMax层设计

一起学习用Verilog在FPGA上实现CNN----全连接层设计

一起学习用Verilog在FPGA上实现CNN----卷积层设计