姓名:张俸玺 学号:20012100022 学院:竹园三号书院
转自https://blog.csdn.net/qq_38798425/article/details/106723877
【嵌牛导读】FPGA,可编程门阵列,作为一种较为新型的技术,为大多数人所陌生。如今,用FPGA实现神经网络成为一种热门技术话题。本文对基于FPGA的卷积神经网络实现框架进行了简要叙述。
【嵌牛鼻子】FPGA 卷积神经网络
【嵌牛提问】基于FPGA的卷积神经网络实现的基本框架是什么?
【嵌牛正文】
github
首先设计整体框架,再根据整体结构去设计每一个子模块,通过例化子模块来形成完整的工程程序。也就是基于FPGA卷积神经网络实现里面的top.v文件。
本项目要实现的神经网络结构如下图所示:
对于在FPGA当中实现神经网络而言,每一种功能的层都单独设计一个子模块。而使用这些子模块搭建一个完整的网络框架主要有两种方法:
1.每一层都单独进行例化,占用单独的硬件资源。
2.同样功能的层复用同一部分硬件资源,通过parameter来进行区分
本项目为了初学者放心食用选择比较简单的第一种方式,当然前提是硬件资源足够多的,如果实现一个较大型的网络这样的方式是不可能的,还是需要使用第二种方法,不过偶从入门来说,第一种方法是非常容易理解的。
在最开始先要设置一个时钟模块,调用片上PLL,也就是clocking Wizard IP核。我使用的片上输入时钟为50M,输出时钟设置为两路,一路为原本的50M时钟,用于和系统时钟相关的地方(当然这个不一定会的上),另一路先随意设置为200M,在后面实际使用的时候再进行调整。
设置好后在top文件中进行例化
clock_pll clock_control
(
.CLK_IN1 (clk_in) ,
.CLK_OUT1 (clk_50) ,
.CLK_OUT2 (clk_200) ,
.RESET (~rst_n) ,
.LOCKED ()
);
那么这样一来我们就拥有一个快速的时钟,能够让我们的卷积神经网络飞速运行起来了。
下面我们需要一些子模块了,根据整个网络的结构,我们需要设计输入数据存储层、参数存储层、卷积层、池化层、全连接层,当然,还需要层间的缓存层。对于一个卷积层参数与输入数据需要同时进入,因此可以将参数存储层和层间输入缓存层合并为同一个模块。
以卷积层为例,先写出大概的框架,后面再慢慢填补。
module Layer1_Conv_Top#(
parameter filter_size=5
)
(
input clk_in,
input rst_n,
input data_in,
input weight_in,
output reg data_out
);
data_out <= data_in*weight_in;
endmodule
各个子模块创建完毕后,再去top文件里面例化一下并串联在一起,一个完整的框架就搞定了。
pic_in layer0(
.clk_in (clk_200) ,
.rst_n (rst_n) ,
.map (l1d1) ,
.ready (l1r1) ,
.weight (l1w1)
);
Layer1_Conv_Top layer1_conv(
.clk_in (clk_200) ,
.rst_n (rst_n) ,
.data_in (l1d1) ,
.weights (l1w1) ,
.data_out (l1d2)
);
Layer1_Pool_Top layer1_pool
(
.clk_in (clk_200) ,
.rst_n (rst_n) ,
.data_in (l1d2) ,
.data_out (l1d3)
);
pool1_out_buffer layer1_pool_buffer(
.clk_in (clk_200) ,
.rst_n (rst_n) ,
.data_in (l1d3) ,
.data_out (l2d1) ,
.weight (l2w1)
);
//layer2
Layer2_Conv_Top layer2_conv(
.clk_in (clk_200) ,
.rst_n (rst_n) ,
.data_in (l2d1) ,
.weights (l2w1) ,
.data_out (l2d2)
);
Layer2_Pool_Top layer2_pool
(
.clk_in (clk_200) ,
.rst_n (rst_n) ,
.data_in (l2d2) ,
.data_out (l2d3)
);
pool2_out_buffer layer2_pool_buffer(
.clk_in (clk_200) ,
.rst_n (rst_n) ,
.data_in (l2d3) ,
.data_out (l3d1) ,,
.weight (l3w1)
);
//layer3
Layer3_Conv_Top layer3_conv(
.clk_in (clk_200) ,
.rst_n (rst_n) ,
.data_in (l3d1) ,
.weights (l3w1) ,
.data_out (l3d2)
);
conv3_out_buffer layer3_conv_buffer
(
.clk_in (clk_200) ,
.rst_n (rst_n) ,
.data_in (l3d2) ,
.data_out (l3d3)
);
Layer3_Pool_Top layer3_pool
(
.clk_in (clk_200) ,
.rst_n (rst_n) ,
.data_in (l3d3) ,
.data_out (l4d1)
);
//full_connection
ful_conn layer4_full_connect(
.clk_in (clk_200),
.rst_n (rst_n),
.data_in (l4d1),
.data_out (Dout)
);
程序中用来连接各个子模块的wire类型数据的命名规则是
l(layer)+第几层+d(data)+第几个模块
到现在一个整体的网络框架应该是搭建完毕了。这里有个很值得注意的地方在于,为了编写简单,我将同样功能的模块都单独写了子模块,这样对于他们的不同就可以一个一个去处理。但是事实上,他们的整体思路是一样的,只需要改一些参数,也就是说通过设置许多的parameter也可以达到同样的目的。在所有的子模块介绍完后我将针对这一问题单独进行讲解。