距离上次pynq的基础设置一文已经过去蛮久了,难得还有人催我更新233那么今天就再来把玩一番。今天要做的事情是用pynq来搭一个数字识别的工程。
在正式开始介绍工程之前,让我花一点篇幅简单说下pynq的内容结构以及数字识别的原理。
1.pynq结构简介。
pynq这个板子的结构大概是介个样子的,我们知道普通的zynq是只有PS(即ARM)和PL(即FPGA)的,PYNQ就比较厉害了,在PS和PL的基础上自带一个linux,linux里面装好了python3.6、jupyter以及Overlay,jupyter就是一个python的编写软件,Overlay就理解成一个自动化的软件好了,他的作用就是根据tcl文件是指示来把FPGA内部的电路连成我们要的样子,就是个自动烧录代码和连电路的工具。
我光这么说可能大家不好理解,下面我以一个简单的加法器来演示一下在pynq上是怎么建工程并且运行的。先让大家有个直观的感受。
以上是我在学校做演示时用的PPT,首先我们在HLS软件里面将一个c语言的加法器代码adder.cpp打包成ip核,然后在vivado中新建一个工程,加入zynq和我们的ip核,接着就是仿真->综合->实现->生成bitstream文件。这样的流程完成之后,我们所得到的vivado工程的文件夹会变得比较大,因为里面包含了很多中间文件,我们只要记住一件事,就是一个工程跑完generate bitstream之后,pynq需要的只有两个东西,一个是tcl文件,一个是bit文件。我们只要把他们从vivado工程里面取出来,然后把他们命名成同样的名字,然后丢到pynq里面随便什么位置就可以了,如下图所示。
然后,我们可以在网页打开pynq的jupyter(具体过程上次的文章讲得很详细了,不记得就回去看看),在jupyter中输入
from pynq import Overlay
这话的意思相当于是把Overlay加载起来了,这就跟我们在电脑上点开一个软件一样的意思。
overlay=Overlay("我们在pynq中存放.bit文件的位置")
这话的意思就是让overlay这个软件按照.bit文件所描述的电路把FPGA连好,pynq在看到这话之后会自动在同一个路径下搜索tcl文件,tcl文件也找到了才能正确连电路,这也就是为什么我们需要把tcl和bit文件改成一样的名字的原因了。
2.图像识别简介。
本文意不在介绍图像识别,这里只非常简单地过一下。
卷积神经网络做图像识别主要分成四步,就是上图最上方写的那四步(1.生成卷积核 2.卷积 3.池化 4.全连接),上图中下方的那四点是这四步实际上分别对应的数学运算本质,本质来讲并不是特别复杂的运算,就是运算量大而已。
需要指出的是:
第一步,第四步运算是需要一些事先确定的参数的,确定这些参数的方法是机器学习,笼统而言是用TensorFlow之类的软件写好python程序,再丢给程序一堆图片,然后找一部显卡性能比较好的电脑上或者服务器上跑这个程序,跑个几天几夜就可以得到我们第一步和第四步所需要的参数。
第二步,第三步运算是根据已经得到的参数来进行两种运算,这两步是需要我们在pynq上通过连PFGA电路的方式来实现的,最终要得到的效果是我们在电脑上弄一张图片,在上头写一个数字,用python脚本来指挥FPGA跑一遍运算电路,就可以得到对这个数字识别的结果。
下面是一个讲解卷积神经网络的视频,有兴趣就看看,可以说是讲得很清晰明了了。
https://www.youtube.com/watch?v=FmpDIaiMIeAwww.youtube.com
3.搭建工程的过程。
(一)
首先要指出一点,我这个练习是买了moore8上蔡宇杰大佬的教学视频和代码来弄的,代码并不是我原创的,我只是在这里点一下用pynq开发工程的流程,一者是为了自己记录一下学习过程,二来也算是自发给蔡大佬打个广告吧233,为了保护他人的劳动成果,具体代码我是不会展示出来的。
1首先是开一个空文件夹,把h,cpp,testbench文件放进去,然后在HLS中新建一个工程,在向导中往工程加入cpp文件和testbench文件,不要忘了在向导中将cpp文件设置成TOP文件。只要h文件也在这个文件夹里,h文件会自动添加进来。
另外要说一下,我HLS综合卷积和池化函数用的都是20ns的周期,在向导中用系统默认的10ns池化函数是会出现时序违例的。
2对于我们要用电路实现的函数,我们希望可以把他的输入输出参数挂载到axi总线上,axi总线首先是一个总线,就像uart,i2c,spi那样。不了解硬件的孩子就把这玩意当做软件的fifo好了。
挂载的方法是点击函数代码右方的directive,这上头就会显示出函数的各个参数,右击这些参数,在上面可以选择insert directive,然后在弹出的界面先选类型是interface,接着就可以指定我们要把一个参数指定成axi的master端还是slave端,master端就是说这个参数是往外传的,slave端就是说这个参数是从外面传进这个函数的。
3接着在HLS中点击solution的export rtl就可以导出ip核了。
导出了之后,将来在vivado中只要在添加ip核的地方把我们这个hls工程所在的文件夹添加进来,vivado就会自动识别这个我们的ip核了。如果是用zedboard板子来搭项目,那么懂得刚才那些步骤就可以了。但是对于pynq的开发我们还需要知道一点,就是将来为了写python脚本,我们需要知道HLS将c++函数转成verilog的电路描述后,各个函数的参数被分配的axi上的地址是多少,这个内容是在工程中的这个位置。
现在只是提一下,将来用到会再详细说。
(二)搭建vivado工程
1.新建vivado工程。
在工程向导里面什么文件都不用加,直接一路ok到向导的最后一步,上一个文章我讲过怎么添加pynq的信息包到vivado里了,添加过后这里的最后一步就可以直接选择pynq板子。
2.添加block design需要的组件
点击左边的create block design选项。
可以看到右边一片空白的窗口,在窗口中点击+号,弹出的搜索框搜zynq processing system,这就是一个ARM核,添加进来就可以了。
添加了之后点击窗口的auto connection。由于之前在向导那里vivado已经知道pynq板子的信息了,所以这时候它就会自动把DDR3,IO口都连好,连好后窗口中是下面这个样子的。
接下来把我们生产的ip核添加到工程里来
3.设置PS端
由于我们在3.4中加入的arm核默认情况下是没有打开axi总线的master口的,所以我们需要手动打开master口。双击arm核,在如下界面这么选,就阔以了。
在这个界面下还要修改的是下面这个紫色框的位置,vivado跟hls一样,软件默认的时钟周期都是10ns,而在咱们这个例子中10ns是会时序违例的,所以要在这个地方将时序改成20ns,也就是紫框中的数字从原来是100改成50,意思是50Mhz,也就是20ns。当然这步在第一次做的时候肯定是不知道要改的,正常情况是做到后面综合或实现那一步,发现时序违例了才会回来这里改的。
4.连接block design的组件
接下来是再次点击窗口冒出来的自动连线。
5.create HDL wrapper
刚才相当于是在草稿纸上把一个房子的草稿纸画好了,vivado表示这个草稿纸要进行再稍微未加工一下它才能看懂并往下进行施工,这一步就是create HDL wrapper,所幸这个步骤是可以自动化的,主要点击一个按钮就可以了,如下图所示。
6.生成bit和tcl文件
如果对代码和电路都比较有信心,那么在create HDL wrapper后就可以直接点击软件左下角的generate bitstream来生成最后的结果了。vivado就会从综合->实现->生成这样一路跑下来。
跑完我们就得到了bit文件,它在vivado工程中的位置如下所示。
但别忘了,之前说过,pynq需要两个文件,bit和tcl文件,bit有了,tcl哪来呢?看下图。
tcl文件在vivado文件夹中的位置:
得到tcl和bit文件,咱们把这俩拷贝一下,名字改成一样的,先找个地方把它们放起来,以后会用到。比如像下面这样,以后写好python脚本之后把脚本,这俩文件一块丢到pynq的一个文件夹下就可以运行了。
这个工程中硬件部分到上面就算是完成了,接下来还有写python脚本,以及在pynq的terminal中运行脚本的操作。
(三)python脚本的编写和运行
由于这个代码是蔡大大写的,我想了想,觉得讲别人的东西不好,简单提下原理吧,以后有空自己再搞个工程写了python脚本再来细说吧。
之前在HLS那一步不是说了我们需要用到hwh文件吗,它就是下图中的最左边的代码,它里面记录了中间那个HLS自动生成的硬件层面的c代码用到的各个参数所存放的地址,我们需要做的就是根据左边的地址文件,中间的c代码文件来写出右边的python脚本,具体内容就是在指定我们例化的函数的参数要从哪个地址上读或者存数据。
当然脚本里不止是翻译一下c代码这么简单,还有设置参数(),读取图像(用pynq自带的python cv库函数),烧录程序(这个部分怎么写我在前面自己加法器的例子里面讲了)
最后运行的效果