【FPGA】基于HLS的全连接神经网络手写体识别

目录

一 系统分析

1.1 全连接神经网络简介

二 通过HLS 编写全连接神经网络传入权重参数和偏置参数文件

2.1 获得图片、权重以及偏置的参数

2.2 编写C语言的全连接算子

2.3 Slave Interfaces

2.3.1 hls_avalon_slave_component

2.3.2 hls_avalon_slave_register_argument

2.3.3 slave_memory_argument

三 输入图片进行测试并生成IP

3.1 编译、测试

3.1.1 初始化环境

3.1.2 编译

3.2 添加IP进Quartus并添加到SOC工程中生成硬件

3.2.1 将IP文件夹复制到黄金工程的IP文件夹下

3.2.2 打开黄金工程

四 更新SD卡

4.1 生成设备树

4.2 生成rbf文件

4.3 更新头文件

​编辑

五 设计软件

5.1 新建C工程

5.2 代码设计

六 调试

七 参考链接

一 系统分析

1、手写体输入为28×28的黑白图片,所以输入为784个
2、输出为识别0-9的数字的概率,所以有10个输出
3、输入只能是-1~1的小数,主要是防止计算溢出

1.1 全连接神经网络简介

全连接神经网络模型是一种多层感知机(MLP),感知机的原理是寻找类别间最合理、最具有鲁棒性的超平面,最具代表的感知机是SVM支持向量机算法。神经网络同时借鉴了感知机和仿生学,通常来说,动物神经接受一个信号后会发送各个神经元,各个神经元接受输入后根据自身判断,激活产生输出信号后汇总从而实现对信息源实现识别、分类,一个典型的神经网络如下图所示:

【FPGA】基于HLS的全连接神经网络手写体识别

上图是典型的全连接神经网络模型(DNN),有的场合也称作深度神经网络,与传统的感知机不同,每个结点和下一层所有结点都有运算关系,这就是名称中’全连接’的含义,上图的中间层也成为隐藏层,全连接神经网络通常有多个隐藏层,增加隐藏层可以更好分离数据的特征,但过多的隐藏层也会增加训练时间以及产生过拟合。

观察上图,输入数据是一个3维向量,隐藏层有5个结点,意味着通过线性映射将3维向量映射为一个5维向量,最后再变为一个2维向量输出。当原输入数据是线性不可分时,全连接神经网络是通过激活函数产生出非线性输出,常见的激活函数有Sigmoid,Tanh,Relu,分别如下图所示:

【FPGA】基于HLS的全连接神经网络手写体识别

全连接神经网络训练分为前向传播、后向传播两个过程,前向传播数据沿输入到输出后计算损失函数值,后向传播则是一个优化过程,利用梯度下降法减小前向传播产生的损失函数值,从而优化、更新参数。

简言之:

输入层输入数据,在经过中间隐藏层计算,最后通过右边输出层输出数据

本次项目做的手写体识别就是基于全连接神经网络来实现的

二 通过HLS 编写全连接神经网络传入权重参数和偏置参数文件

2.1 获得图片、权重以及偏置的参数

python+tensorflow对mnist数据集的神经网络训练和推理 加参数提取

参数提取

2.2 编写C语言的全连接算子

头文件导入 :

#include <stdio.h>
#include "HLS/hls.h"

#include "input_0.h"//&#x5341;&#x5E45;&#x56FE;&#x7247;
#include "input_1.h"
#include "input_2.h"
#include "input_3.h"
#include "input_4.h"
#include "input_5.h"
#include "input_6.h"
#include "input_7.h"
#include "input_8.h"
#include "input_9.h"

#include "layer1_bias.h"    //&#x7B2C;&#x4E00;&#x5C42;&#x504F;&#x7F6E;&#x5E38;&#x6570;
#include "layer1_weight.h"  //&#x7B2C;&#x4E00;&#x5C42;&#x6743;&#x91CD;
#include "layer2_bias.h"    //&#x7B2C;&#x4E8C;&#x5C42;&#x504F;&#x7F6E;&#x5E38;&#x6570;
#include "layer2_weight.h"  //&#x7B2C;&#x4E8C;&#x5C42;&#x6743;&#x91CD;&#x503C;</stdio.h>

将十幅图像导入,并且将权重和偏置参数头文件加入进去

2.3 Slave Interfaces

Intel HLS Compiler提供了两种不同类型的从接口,您可以在组件中使用它们。一般来说,较小的标量输入应该使用从寄存器。如果您打算将大数组复制到组件中或从组件中复制出来,那么应该使用从属内存。

【FPGA】基于HLS的全连接神经网络手写体识别

【FPGA】基于HLS的全连接神经网络手写体识别

2.3.1 hls_avalon_slave_component

【FPGA】基于HLS的全连接神经网络手写体识别
#include <hls hls.h>
#include <stdio.h>
hls_avalon_slave_component
component int dut(int a,int b)
{
    return a*b;
}
int main()
{
    int a=2;
    int b=3;
    int y;
    y = dut(a,b);
    printf("y=%d",y);
    return 0;
}
</stdio.h></hls>

2.3.2 hls_avalon_slave_register_argument

【FPGA】基于HLS的全连接神经网络手写体识别
#include <hls hls.h>
#include <stdio.h>

hls_avalon_slave_component
component int dut(
                int a,
                hls_avalon_slave_register_argument int b)
{
    return a*b;
}
int main()
{
    int a=2;
    int b=3;
    int y;
    y = dut(a,b);
    printf("y=%d",y);
    return 0;
}
</stdio.h></hls>

可见 b变成了寄存器

【FPGA】基于HLS的全连接神经网络手写体识别

2.3.3 slave_memory_argument

【FPGA】基于HLS的全连接神经网络手写体识别
#include <hls hls.h>
#include <hls stdio.h>

hls_avalon_slave_component
component int dut(
    hls_avalon_slave_memory_argument(5*sizeof(int)) int *a,
    hls_avalon_slave_memory_argument(5*sizeof(int)) int *b
)
{
    int i;
    int sum=0;
    for(i=0;i<5;i++) { sum="sum" + a[i] * b[i]; printf("a[%d]%d",i,a[i]); } return sum; int main() a[5]="{1,2,3,4,5};" b[5]="{1,2,3,4,5};" printf("sum="%d",sum);" 0; < code></5;i++)></hls></hls>

这样子 a、b都变成了存储器类型

【FPGA】基于HLS的全连接神经网络手写体识别

本次实验就是使用HLS将输入图片、权重、偏置生成为从存储器类型的电路元件,方便后续在软件端将数据存入从存储器中并调用。

全连接代码:

#include <stdio.h>
#include "HLS/hls.h"

#include "input_0.h"//&#x5341;&#x5E45;&#x56FE;&#x7247;
#include "input_1.h"
#include "input_2.h"
#include "input_3.h"
#include "input_4.h"
#include "input_5.h"
#include "input_6.h"
#include "input_7.h"
#include "input_8.h"
#include "input_9.h"

#include "layer1_bias.h"    //&#x7B2C;&#x4E00;&#x5C42;&#x504F;&#x7F6E;&#x5E38;&#x6570;
#include "layer1_weight.h"  //&#x7B2C;&#x4E00;&#x5C42;&#x6743;&#x91CD;
#include "layer2_bias.h"    //&#x7B2C;&#x4E8C;&#x5C42;&#x504F;&#x7F6E;&#x5E38;&#x6570;
#include "layer2_weight.h"  //&#x7B2C;&#x4E8C;&#x5C42;&#x6743;&#x91CD;&#x503C;

hls_avalon_slave_component component
int my_predit(
    hls_avalon_slave_memory_argument(784*sizeof(float)) float *img,
    hls_avalon_slave_memory_argument(64*sizeof(float)) float *b1,
    hls_avalon_slave_memory_argument(784*64*sizeof(float)) float *w1,
    hls_avalon_slave_memory_argument(10*sizeof(float)) float *b2,
    hls_avalon_slave_memory_argument(64*10*sizeof(float)) float *w2){

    float res1[64]={0},res2[10]={0};    //&#x521B;&#x5EFA;&#x4E24;&#x4E2A;&#x6D6E;&#x70B9;&#x6570;&#x6570;&#x7EC4; yongyu

//&#x5FAA;&#x73AF;1
/*  w1&#x6743;&#x91CD;&#x5728; layer1_weight.h &#x4E2D;&#x6309;&#x7167;&#x4E00;&#x884C;64&#x4E2A;&#xFF0C;784&#x5217;&#x987A;&#x5E8F;&#x6392;&#x5217;&#xFF0C;
     &#x4F46;&#x5B9E;&#x9645;&#x4E0A;&#x662F;&#x4E00;&#x7EF4;&#x6570;&#x7EC4;&#xFF0C;&#x6211;&#x4EEC;&#x8BA1;&#x7B97;&#x7B2C;&#x4E00;&#x5C42;64&#x4E2A;&#x795E;&#x7ECF;&#x5143;&#x7684;&#x8F93;&#x51FA;*/
    for (int i = 0; i < 64; i++)
    {
        for (int j = 0; j < 784; j++)
        {
            res1[i] = res1[i]+ img[j] * w1[i+j*64];     //w1x1+w2x2 ... wnxn+b
        }
        res1[i] +=b1[i]; //&#x5F97;&#x5230;&#x7B2C;&#x4E00;&#x5C42;&#x7684;&#x8F93;&#x51FA;
        //printf("%f \n",res1[i]);
    }
//&#x5FAA;&#x73AF;2
    for (int i = 0; i < 10; i++)
    {
        for (int j = 0; j < 64; j++)
        {
           res2[i] = res2[i]+ res1[j] * w2[i+j*10]; //&#x8F93;&#x5165;&#x7B2C;&#x4E00;&#x5C42;&#x7684;&#x8F93;&#x51FA;
        }
        res2[i] +=b2[i];
        //printf("%f \n",res2[i]);
    }
//&#x8F93;&#x51FA;
    float temp = 0; //&#x7528;&#x4E00;&#x4E2A;&#x4E2D;&#x95F4;&#x503C;&#x6765;&#x5BC4;&#x5B58;&#x7279;&#x5F81;&#x503C;&#x6700;&#x5927;&#x503C;
    int res3;
    for (int i = 0; i < 10; i++)
    {
        //printf("%f \n",res2[i]);
        if (res2[i] > temp) //&#x6BD4;&#x8F83;10&#x4E2A;&#x7279;&#x5F81;&#x503C;&#xFF0C;&#x627E;&#x51FA;&#x6700;&#x5927;&#x503C;
        {
            temp = res2[i];
            res3 = i;       //res3&#x7684;&#x503C;&#x5373;&#x4E3A;&#x8F93;&#x51FA;&#x5C42;&#x6570;&#x7EC4;&#x4E2D;&#x7279;&#x5F81;&#x503C;&#x6700;&#x5927;&#x503C;&#x5BF9;&#x5E94;&#x7684;&#x4E0B;&#x6807; &#xFF0C;&#x4E5F;&#x662F;&#x6211;&#x4EEC;&#x60F3;&#x8981;&#x7684;&#x7ED3;&#x679C;
        }
    }
    return res3;    //&#x6700;&#x540E;&#x8FD4;&#x56DE;i&#xFF0C;&#x5373;&#x662F;&#x6211;&#x4EEC;&#x7684;&#x9884;&#x6D4B;&#x7ED3;&#x679C;
}

int main()
{
    //&#x7528;&#x6307;&#x9488;&#x6570;&#x7EC4;&#x6765;&#x8868;&#x793A;10&#x5E45;&#x56FE;&#x7247;
    float *a[10] = {input_0,input_1,input_2,input_3,input_4,input_5,input_6,input_7,input_8,input_9};

    for (int i = 0; i < 10; i++)    //&#x5FAA;&#x73AF;&#x8F93;&#x51FA;&#x8BAD;&#x7EC3;&#x7ED3;&#x679C;
    {
        int res =  my_predit(a[i],layer1_bias,layer1_weight,layer2_bias,layer2_weight);//&#x8C03;&#x7528;&#x51FD;&#x6570;&#x8F93;&#x51FA;&#x8FD4;&#x56DE;&#x503C;
        printf("input_%d.h&#x9884;&#x6D4B;&#x7ED3;&#x679C;&#x4E3A;:%d\n",i,res);
    }

    return 0;
}

</stdio.h>

三 输入图片进行测试并生成IP

main函数的作用仅仅是测试用的,并没有实际的意义,目的就是将十幅图像的像素输入,得到返回结果并输出。

3.1 编译、测试

3.1.1 初始化环境

就是进入到你 Quartus安装目录下的 HLS路径下,用 终端运行后,初始化hls环境。

下面以我的安装目录为例,作为示范:

1 :先找到路径

【FPGA】基于HLS的全连接神经网络手写体识别

2 : 敲cmd 回车

【FPGA】基于HLS的全连接神经网络手写体识别

3 :输入初始化命令

【FPGA】基于HLS的全连接神经网络手写体识别

【FPGA】基于HLS的全连接神经网络手写体识别

初始化完成。

3.1.2 编译

终端先不关闭,还要进行编译工作

回到你代码编写的路径下

在 x86-64平台上编译:

【FPGA】基于HLS的全连接神经网络手写体识别

-v : 作用是显示信息
-0 full :生成名为 full 的可执行文件

运行结果:

【FPGA】基于HLS的全连接神经网络手写体识别

在FPGA平台上编译测试:

【FPGA】基于HLS的全连接神经网络手写体识别

【FPGA】基于HLS的全连接神经网络手写体识别

生成IP文件夹

【FPGA】基于HLS的全连接神经网络手写体识别

到这里神经网络IP制作完成。

3.2 添加IP进Quartus并添加到SOC工程中生成硬件

3.2.1 将IP文件夹复制到黄金工程的IP文件夹下

3.2.2 打开黄金工程

1. 打开platform designer

【FPGA】基于HLS的全连接神经网络手写体识别

2 添加神经网络IP到工程并连线

【FPGA】基于HLS的全连接神经网络手写体识别

【FPGA】基于HLS的全连接神经网络手写体识别

Avalon Memory Mapped Slave接口的 权重、偏置、图片、控制状态存储器连接到 mm_bridgeavalon Memory Mapped Masterm0上 ,时钟和复位都连到mm_bridge上, irq连接到 f2h_irq0.

3. 然后分配基地址

【FPGA】基于HLS的全连接神经网络手写体识别

4. generate

一般会编译十几分钟,慢慢等吧。

5. 全编译

这一步会更久,半小时加,可以直接去设计软件端。

编译完后会生成sof文件

四 更新SD卡

4.1 生成设备树

打开EDS工具,是Intel专门为SOC FPGA开发设计的一款工具,类似于终端。里面包含了很多工具。

进入到黄金工程目录后,

更新设备树文件:

make dtb

生成设备树文件

【FPGA】基于HLS的全连接神经网络手写体识别

【FPGA】基于HLS的全连接神经网络手写体识别

4.2 生成rbf文件

进入黄金工程目录下的output_files目录下,双击 sof_to_rbf.bat

【FPGA】基于HLS的全连接神经网络手写体识别

【FPGA】基于HLS的全连接神经网络手写体识别

二进制文件更新完毕。

4.3 更新头文件

./generate_hps_qsys_header.sh

【FPGA】基于HLS的全连接神经网络手写体识别

【FPGA】基于HLS的全连接神经网络手写体识别

将更新的后的二进制文件和设备树文件更换SD卡中的文件。

五 设计软件

5.1 新建C工程

创建完项目,再创建c程序,

添加库文件路径:

【FPGA】基于HLS的全连接神经网络手写体识别

路径是根据自己安装目录下去寻找。

编写源代码,添加权重、偏置、测试图片文件

将全连接生成的权重、偏置、测试图片的头文件以及hps_0.h复制到工程中。

【FPGA】基于HLS的全连接神经网络手写体识别

5.2 代码设计

/*
 * full.c
 *
 *  Created on: 2022&#x5E74;7&#x6708;27&#x65E5;
 *      Author: &#x836F;&#x77F3;&#x65E0;&#x533B;
 */

#include "layer1_bias.h"
#include "layer1_weight.h"
#include "layer2_bias.h"
#include "layer2_weight.h"

#include "input_0.h"//&#x5341;&#x5E45;&#x56FE;&#x7247;
#include "input_1.h"
#include "input_2.h"
#include "input_3.h"
#include "input_4.h"
#include "input_5.h"
#include "input_6.h"
#include "input_7.h"
#include "input_8.h"
#include "input_9.h"

//gcc&#x6807;&#x51C6;&#x5934;&#x6587;&#x4EF6;
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys mman.h>
#include <stdlib.h>

//HPS&#x5382;&#x5BB6;&#x63D0;&#x4F9B;&#x7684;&#x5E95;&#x5C42;&#x5B9A;&#x4E49;&#x5934;&#x6587;&#x4EF6;
#define soc_cv_av //&#x5F00;&#x53D1;&#x5E73;&#x53F0;Cyclone V &#x7CFB;&#x5217;

#include "hwlib.h"
#include "socal/socal.h"
#include "socal/hps.h"

//&#x4E0E;&#x7528;&#x6237;&#x5177;&#x4F53;&#x7684;HPS &#x5E94;&#x7528;&#x7CFB;&#x7EDF;&#x76F8;&#x5173;&#x7684;&#x786C;&#x4EF6;&#x63CF;&#x8FF0;&#x5934;&#x6587;&#x4EF6;
#include "hps_0.h"

#define HW_REGS_BASE (ALT_STM_OFST)     //HPS&#x5916;&#x8BBE;&#x5730;&#x5740;&#x6BB5;&#x57FA;&#x5730;&#x5740;
#define HW_REGS_SPAN (0x04000000)       //HPS&#x5916;&#x8BBE;&#x5730;&#x5740;&#x6BB5;&#x5730;&#x5740;&#x7A7A;&#x95F4; 64MB&#x5927;&#x5C0F;
#define HW_REGS_MASK (HW_REGS_SPAN - 1) //HPS&#x5916;&#x8BBE;&#x5730;&#x5740;&#x6BB5;&#x5730;&#x5740;&#x63A9;&#x7801;

static volatile unsigned long long *dout = NULL;

static  float *img_virtual_base = NULL;
static  float *b1_virtual_base = NULL;
static  float *b2_virtual_base = NULL;
static  float *w1_virtual_base = NULL;
static  float *w2_virtual_base = NULL;

int full_init(int *virtual_base){
    int fd;
    void *virtual_space;
//&#x4F7F;&#x80FD;mmu
    if((fd = open("/dev/mem",(O_RDWR | O_SYNC))) == -1){
        printf("can't open the file");
        return fd;
    }

//&#x6620;&#x5C04;&#x7528;&#x6237;&#x7A7A;&#x95F4;
    virtual_space = mmap(NULL,HW_REGS_SPAN,(PROT_READ | PROT_WRITE),MAP_SHARED,fd,HW_REGS_BASE);

//&#x5F97;&#x5230;&#x504F;&#x79FB;&#x7684;&#x5916;&#x8BBE;&#x5730;&#x5740;
    dout    = virtual_space + ((unsigned)(ALT_LWFPGASLVS_OFST+PREDIT_0_MY_PREDIT_INTERNAL_INST_AVS_CRA_BASE)
            &(unsigned)(HW_REGS_MASK));
    b1_virtual_base      = virtual_space + ((unsigned)(ALT_LWFPGASLVS_OFST+PREDIT_0_MY_PREDIT_INTERNAL_INST_AVS_B1_BASE)
            &(unsigned)(HW_REGS_MASK));
    b2_virtual_base      = virtual_space + ((unsigned)(ALT_LWFPGASLVS_OFST+PREDIT_0_MY_PREDIT_INTERNAL_INST_AVS_B2_BASE)
            &(unsigned)(HW_REGS_MASK));
    w1_virtual_base      = virtual_space + ((unsigned)(ALT_LWFPGASLVS_OFST+PREDIT_0_MY_PREDIT_INTERNAL_INST_AVS_W1_BASE)
                &(unsigned)(HW_REGS_MASK));
    w2_virtual_base      = virtual_space + ((unsigned)(ALT_LWFPGASLVS_OFST+PREDIT_0_MY_PREDIT_INTERNAL_INST_AVS_W2_BASE)
                &(unsigned)(HW_REGS_MASK));
    img_virtual_base     = virtual_space + ((unsigned)(ALT_LWFPGASLVS_OFST+PREDIT_0_MY_PREDIT_INTERNAL_INST_AVS_IMG_BASE)
                    &(unsigned)(HW_REGS_MASK));
    *virtual_base = virtual_space;
    return fd ;
}

int main(){
    int fd,virtual_base,i;
    fd = full_init(&virtual_base);
    float *image[10] = {input_0,input_1,input_2,input_3,input_4,input_5,input_6,input_7,input_8,input_9};

    //&#x5148;&#x5C06;&#x6743;&#x91CD;&#x548C;&#x504F;&#x7F6E;&#x8D4B;&#x503C;
    memcpy(w1_virtual_base,layer1_weight,784*64*sizeof(float));
    memcpy(b1_virtual_base,layer1_bias,64*sizeof(float));
    memcpy(w2_virtual_base,layer2_weight,64*10*sizeof(float));
    memcpy(b2_virtual_base,layer2_bias,10*sizeof(float));

    //&#x4E00;&#x5C42;for&#x5FAA;&#x73AF;&#x8F93;&#x51FA;&#x5341;&#x5F20;&#x56FE;&#x7247;&#x7684;&#x503C;

    for(i=0;i<10;i++) 0 { memcpy(img_virtual_base,image[i],784*sizeof(float)); while((*(dout + 0)&(unsigned)1) !="0);" *(dout 2)="1;" 3)="1;" 1)="1;" & 0x2)="=" ); printf("input:%d 预测结果:%d \n",i,*(dout 4)); } 取消映射 取消地址映射 if(munmap(virtual_base,hw_regs_span)="=-1){" printf("取消映射失败..\n"); close(fd); 关闭mmu return 0; < code></10;i++)></stdlib.h></sys></fcntl.h></unistd.h></stdio.h>

保存之后 编译生成二进制可执行文件。

六 调试

上板验证,这一步就偷个懒,就是连接开发板和电脑,将可执行文件复制到

/opt 目录下

给可执行文件赋予权限

chmod 777 full

之后就可以运行了。

最终可实现对28 *28 的手写体图片的识别。并显示出结果

七 参考链接

HLS的各种接口案例实现

全连接神经网络

Original: https://blog.csdn.net/qq_52445967/article/details/126364115
Author: 藏进小黑屋
Title: 【FPGA】基于HLS的全连接神经网络手写体识别

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/649763/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球