如何从零开始将神经网络移植到FPGA(ZYNQ7020)加速 您所在的位置:网站首页 神经网络轻量化工具 如何从零开始将神经网络移植到FPGA(ZYNQ7020)加速

如何从零开始将神经网络移植到FPGA(ZYNQ7020)加速

2024-05-29 14:57| 来源: 网络整理| 查看: 265

如何从零开始将神经网络移植到FPGA(ZYNQ7020)加速推理 前言

本片文章用于对零基础的小白使用,仅供参考,大神绕道。AI一直都是做算法的热点,作为多少研究生都想蹭一蹭热度,本文就神经网络的移植到FPGA做一个简单的教程。使用FPGA做前向的推理而不是训练,训练还是在PC机上完成的,通过训练得到的权重文件然后再去移植到FPGA。

1.神经网络

深度神经网络应该是最简单的一种网络结构了,相比CNN没有卷积相关的参数,仅仅只有几层网络结构,深度神经网络就不多说了,DNN,之所以不多说的原因就是这个东西应该很容易理解,毕竟不是很难的算法,现在前沿的算法估计都没人去研究DNN相关的了。不懂的可以上网去搜索相关的原理包括、前向传播、反向传播、梯度下降等等。既然要移植神经网络,总要有一个神经网络了吧。既然是入门那就用入门的鼻祖–手写字了,首先需要弄一个手写字的神经网络。 神经网络结构图 很简单的三层

1.1如何规划神经网络的结构

1.神经网络的结构可以自己去规划,没必要和本文的一模一样,也可以使用主流的框架去做,比如tensorflow等,本文使用了三层隐藏层网络,输入层为784,隐藏层1为64,2为32,3为16.。三层网络可做到识别率95%以上。 2.其实网络刚一开始用的隐藏层为256、128、32,这样的识别精度更高,能达到98%,但是后面去移植到FPGA中发现权重参数太多了,FPGA的Bram资源远远不够用,所以对网络的结构进行删减。把网络结构调整的简单了一下,再FPGA中能够实现了,精度也不至于削减 的这么多,足够了。 3.激活函数用的sigmod函数,这个也不用多说,了解过的应该都知道是臭了大街 的了。一个神经网络从输入输出都要自己设定好。本文的输入2828,输出101.对应每一层的权重数量为[64][784] 、[64][64]、[10][32]、偏置项[64]、[32]、[10]。 4.保存到数据类型采用的是float类型数据,每个权重按照4字节存储。

1.2mnist数据集

这部分数据集没什么好说的,使用官网的文件解析出来把数据读取,然后使用数据集去训练。只不过注意的是,在使用过程中,将数据集中的参数提取了出来保存为.dat格式的文件,便于使用FPGA预测单张图片。另外就是每张图片的参数原始参数是0-255,最后都归一化到了0-1之间。

1.3 有关权重文件的保存

本文中的权重文件分开保存,对每一层的weight、bias等文件分别保存成独立的文件,weightx.dat与biasx.dat,之所以这样保存的原因是使用HLS语言开发时,可以使用#include参数读取权重文件等。当然,对在PC上完成的训练与测试,输出保存的权重文件只有保存了1个文件。这个可以在源代码中查看,本文全部开源。相关代码可以从本文链接下载也以从github下载。

2.FPGA实现

FPGA实现是一个重要的地方,本出不主要是去探究实现神经网络的各种方法。个人觉得,FPGA去实现神经网络最关键的地方就是设定好一种架构,包括数据的传输、临时存储、结果读取。比如,我PS做控制,调取PL资源做网络推理,那么PS与PL之间的交互九世纪最重要的,可以通过AXI协议、可以通过BRAM等等,这样就要考虑板子的资源够不够用,一个好的架构不同的方式必然会带来不一样的效果。本文中实现使用HLS完成的加速IP核设计,实现ARM+HLS调用的硬件结构。

2.1HLS加速IP核设计

HLS直白了就是直接使用高级语言描述硬件电路,不得不说这种方式开发真的是省时间效率,但是综合出来的电路质量是差一些,后期优化一下可能会更好,一下代码没有经过优化,使用流水线优化下FOR循环效果会更好,废话不多说直接上代码:

/** * Use the saved CNet model to predict over the MNIST dataset. compile by hls **/ #include #include "hls_mnist.h" /** * Sigmoid * * @param double *: Sum of weights * inputs + Bias * @param int: Size */ void Sigmoid( float *a,int size) { for(int i = 0; i #include "weight1.dat" }; const float weight2[32][64] = { #include "weight2.dat" }; const float weight3[10][32] = { #include "weight3.dat" }; // initialize the bias matrix const float bias1[64] = { #include "bias1.dat" }; const float bias2[32] = { #include "bias2.dat" }; const float bias3[10] = { #include "bias3.dat" }; // initialize the confusion matrix int k ,j,i; float output1[64]; float output2[32]; float output3[10]; float z1 = 0.0; float z2 = 0.0; float z3 = 0.0; // pass through every layer in the net // pass through every neuron in the net //the first layer for( k = 0; k // compute z for neuron z2= 0.0; for( j = 0; j if(output[i] > output[max_idx]) { max_idx = i; } } }

1.简简单单的三层网络,最后输出结果为10个分类结果0-9,当然稍加修改可以直接获取单个识别的结果。 2.开头的前三行是把HLS入参综合成BRAM接口,之所以设计成BRAM的接口是因为调用IP核时,可以直接访问BRAM的地址获取数据,当然也可以综合成AXI_slave类型的接口,具体大家可以去试一下,至于返回值综合成了AXI的接口。 3.对于for循环部分大家可以使用 directive相关参数优化加入流水线去改变延时,源码中的没有经过优化,延时非常大,加入了流水线优化是非常明显的,这个可以自行去试一下。 4.#include用法可以直接将文件的参数加载到数组中,这个对于HLS编译来说是可以执行的,这种用法大家可以百度下GCC-E相关 的解释,预处理文件。

3.ARM调用IP核 3.1硬件设计

如图 block design 地址映射

1.首先添加ps处理器硬件,使能串口(用于调试输出)、SD卡引脚(用于从读取手写字数据文件)、axi GP master 等 2.添加HLS的手写字识别 IP核 3.添加BRAM1,双端口,添加BRAM控制器1,两个端口分别连接HLS的IP的INPUT端接口、和BRAM控制器1,BRAM控制器1链接PS处理器,该处Bram1用于存放输入数据,该处的数据是PS端从SD卡读取手写字数据文件后写入,然后HLS IP核从该处读取手写字数据识别计算。。根据实际存放的数据可以计算出该BRAM1的大小,根据计算出来的大小再分配BRAM的。地址深度等。此处的地址深度大于等于手写字图片的数据量。 4添加BRAM2,双端口,添加BRAM控制器2,一个端口链接HLS IP核的输出端OUTPUT,用于存放IP核识别结果。共计10个数;一个端口通过BRAM控制器2连接到PS端,用于PS端读取输出结果串口打印输出。以上便完成了整体的硬件设计。

3.1SDK裸机调用IP核

设计完硬件电路,便开始使用SDK写应用函数。对于PL资源来说,算是PS的一个外设,再使用的时候也是,把PL当成PS的其中一个外设,通过寄存器地址读取就可以了。 由于需要使用SD卡存取手写字,所以需要使用fatfs文件系统,在创建SDK应用的时候,需要将文件系统添加到BSP中。

//****************************************Copyright (c)***********************************// // Created date: 2019/10/8 17:25:36 // Version: V1.0 // Descriptions: The original version // //---------------------------------------------------------------------------------------- //****************************************************************************************// #include "xparameters.h" #include "xil_printf.h" #include "ff.h" #include "xdevcfg.h" #include #include #include "platform.h" #include "xil_printf.h" #include "xil_io.h" #include "xmnist_nn_predict.h" #define FILE_NAME "1.dat" //定义文件名 const char src_str[30] = "www.openedv.com"; //定义文本内容 static FATFS fatfs; //文件系统 u32 float_to_u32(float value) { u32 result; union float_byte { float v; u8 byte[4]; }data; data.v =value ; result = (data.byte[3] FRESULT status; TCHAR *Path = "0:/"; BYTE work[FF_MAX_SS]; //注册一个工作区(挂载分区文件系统) //在使用任何其它文件函数之前,必须使用f_mount函数为每个使用卷注册一个工作区 status = f_mount(&fatfs, Path, 0); //挂载SD卡 if (status != FR_OK) { xil_printf("Volume is not FAT formated; formating FAT\r\n"); } return 0; } //挂载SD(TF)卡 int sd_mount() { FRESULT status; //初始化文件系统(挂载SD卡,如果挂载不成功,则格式化SD卡) status = platform_init_fs(); if(status){ xil_printf("ERROR: f_mount returned %d!\n",status); return XST_FAILURE; } return XST_SUCCESS; } //SD卡写数据 int sd_write_data(char *file_name,u32 src_addr,u32 byte_len) { FIL fil; //文件对象 UINT bw; //f_write函数返回已写入的字节数 //打开一个文件,如果不存在,则创建一个文件 f_open(&fil,file_name,FA_CREATE_ALWAYS | FA_WRITE); //移动打开的文件对象的文件读/写指针 0:指向文件开头 f_lseek(&fil, 0); //向文件中写入数据 f_write(&fil,(void*) src_addr,byte_len,&bw); //关闭文件 f_close(&fil); return 0; } //SD卡读数据 int sd_read_data(char *file_name,u32 src_addr,u32 byte_len) { FIL fil; //文件对象 UINT br; //f_read函数返回已读出的字节数 //打开一个只读的文件 f_open(&fil,file_name,FA_READ); //移动打开的文件对象的文件读/写指针 0:指向文件开头 f_lseek(&fil,0); //从SD卡中读出数据 f_read(&fil,(void*)src_addr,byte_len,&br); //关闭文件 f_close(&fil); return br; } //main函数 int main() { FIL fil; //文件对象 int flielen,br; char dest_str[5000] = ""; char dest_str0[5000] = ""; char dest_str1[784][10] = {{0}}; float f_buff;//teapota float char buff[10] = {0}; float data[784] = {0}; char *p; u32 result, revs;; int status,len,i,k; int n = 0; // init_platform(); Xil_DCacheDisable(); //scleanup_platform(); printf("sd_mount start\n"); //挂载SD卡 status = sd_mount(); if(status != XST_SUCCESS) { printf("sd_mount failed\n"); return 0; } //get the file from sd f_open(&fil,FILE_NAME,FA_READ); //移动打开的文件对象的文件读/写指针 0:指向文件开头 f_lseek(&fil,0); // read file f_read(&fil,(void*)dest_str,5000,&br); //to devide str to several numeral str p = strtok(dest_str,",\n\r"); memcpy(dest_str1[0],p,strlen(p)); i =1; // 继续获取其他的子字符串 while( p != NULL ) { p = strtok(NULL, ",\n\r"); memcpy(dest_str1[i++],p,strlen(p)); } //tran str to float for(i=0;i for( i=0;i result = float_to_u32(data[i]); Xil_Out32(XPAR_AXI_BRAM_CTRL_0_S_AXI_BASEADDR+i*4,result); } //init the pl neron net XMnist_nn_predict HlsXMem_test; XMnist_nn_predict_Config *ExamplePtr; printf("Look Up the device configuration.\n"); ExamplePtr = XMnist_nn_predict_LookupConfig(XPAR_MNIST_NN_PREDICT_0_DEVICE_ID); if (!ExamplePtr) { printf("ERROR: Lookup of accelerator configuration failed.\n\r"); return XST_FAILURE; } printf("Initialize the Device\n"); status = XMnist_nn_predict_CfgInitialize(&HlsXMem_test, ExamplePtr); if (status != XST_SUCCESS) { printf("ERROR: Could not initialize accelerator.\n\r"); return(-1); } XMnist_nn_predict_Start(&HlsXMem_test); while (XMnist_nn_predict_IsDone(&HlsXMem_test) == 0); for(i=0;i


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有