手写识别(二)

前言

一、环境准备

软件

  • Quartus Prime 18.1 Standard Edition
  • Visual Studio2010 Ultimate X86
  • Win32 Disk Imager
  • SoCEDS18.1
  • MobaXterm 22.0
  • CP210x Universal Windows Driver

硬件

  • Cyclone V C5MB_PCBA

二、测试文件及ip制作

2.1 测试文件

  1. 目录结构,手写识别(一)课程中获取的头文件

在这里插入图片描述

  1. 增加mm_slave.cpp文件,代码如下:
#include "HLS/hls.h"
#include "stdio.h"
#include "input_1.h" //图片 1 的像素值
#include "input_5.h"
#include "input_8.h"
#include "layer1_weight.h" //权重参数、偏置参数
#include "layer1_bias.h"
#include "layer2_weight.h"
#include "layer2_bias.h"
#define IMG_ROW 1
#define IMG_COL 784
#define WEIGHT0_ROW 784
#define WEIGHT0_COL 64
#define WEIGHT1_ROW 64
#define WEIGHT1_COL 10
hls_avalon_slave_component
component int full_connect(
    hls_avalon_slave_memory_argument(784*sizeof(float)) float* img_pixel, //输入像素 1*784
    hls_avalon_slave_memory_argument(50176*sizeof(float)) float* weight1, //输入第一层权重参数 784*64
    hls_avalon_slave_memory_argument(64*sizeof(float)) float* bias1, //输入第一层偏置参数 1*64
    hls_avalon_slave_memory_argument(640*sizeof(float)) float* weight2, //输出第二层权重参数 64*10
    hls_avalon_slave_memory_argument(10*sizeof(float)) float* bias2 //输出第二层偏置参数 1*10
)
{
    static float a[WEIGHT0_COL];
    static float b[WEIGHT1_COL];
    static float result[WEIGHT1_COL];
    float c=0.0f;
    int num=0;
    for(int i=0;i<WEIGHT0_COL;i++)
    { 
        a[i] = 0.0f;
        for(int j=0;j<IMG_COL;j++)
        {
            a[i] = a[i] + img_pixel[j]*weight1[i+j*WEIGHT0_COL]; //第一层全连接
        }
        a[i] = (a[i] + bias1[i])>0?(a[i] + bias1[i]):0.0; //第一层偏置 、 激活
    }
    for(int i=0;i<WEIGHT1_COL;i++)
    { 
        b[i] = 0.0f;
        for(int j=0;j<WEIGHT1_ROW;j++)
        { 
            b[i] = b[i] + a[j]*weight2[i+j*WEIGHT1_COL]; //第二层全连接
        }
        b[i] = b[i] + bias2[i]; //第二层偏置 1*10
        if(b[i] > c)
        { 
            c = b[i]; //选出10个数中的最大值 和对应的索引号 ,然后返回索引号
            num = i;
        } 
    }
    return num;
}

int main()
{
    int res = 0;
    res = full_connect(input_5,layer1_weight,layer1_bias,layer2_weight,layer2_bias);
    printf("\nthe number is %d\n",res);
    return 0; 
}

  1. win + r cmd打开dos窗口

在这里插入图片描述

  1. 初始化hls环境,在安装的IntelFPGA\18.1\hls下执行init_hls.bat

在这里插入图片描述
注意路径,初始化过后不能关闭dos窗口,关闭过后环境重置。

  1. 切换至工程目录

在这里插入图片描述

  1. 运行命令:i++ -march=x86-64 mm_slave.cpp

在这里插入图片描述
tips:march是machine architecture机器架构的意思。

  1. 执行过后会生成一个exe可执行文件,运行测试一下

在这里插入图片描述

2.2 ip制作

  1. 在FPGA平台上测试,执行命令:i++ -march=CycloneV mm_slave.cpp -v -ghdl

在这里插入图片描述

三、硬件制作

  1. 将ip制作步骤中生成的ip,复制到黄金工程的ip文件夹下(platform designer会自动搜索ip)

在这里插入图片描述

  1. 打开黄金工程的qpf(quartus project file)文件

在这里插入图片描述

  1. 点击platform designer

在这里插入图片描述

  1. 选择qsys文件

在这里插入图片描述

  1. 选择full_connect

在这里插入图片描述

  1. 点击finish

在这里插入图片描述

  1. 生成结果

在这里插入图片描述

  1. 点击空心圈进行连线

在这里插入图片描述

  1. 分配基地址

在这里插入图片描述

  1. Generate生成HDL

在这里插入图片描述

  1. Generate

在这里插入图片描述

  1. finish过后弹出以下窗口

在这里插入图片描述

  1. 编译工程(编译后生成sof文件,大约10多分钟)

在这里插入图片描述

  1. 搜索SoC EDS

在这里插入图片描述

  1. 切换至黄金工程目录,操作和linux一致

在这里插入图片描述

  1. make dtb生成设备树

在这里插入图片描述

  1. 设备树文件

在这里插入图片描述

  1. 进入output_files目录,双击sof_to_rbf.bat生成rbf文件

在这里插入图片描述

  1. 点击运行结果

在这里插入图片描述

四、烧写硬件和系统

  1. Win32 Disk Imager刻录镜像文件至SD卡

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  1. 替换rbf文件

在这里插入图片描述

  1. 替换dtb文件

在这里插入图片描述

五、搭建软件工程

  1. 生成头文件

Soc EDS Command Shell 命令窗口切换到硬件工程目录,输入./generate_hps_qsys_header.sh

在这里插入图片描述

  1. 执行生成的头文件

在这里插入图片描述

  1. 黄金工程新建app目录

在这里插入图片描述

  1. 打开eclipse

在这里插入图片描述

  1. 工作空间

在这里插入图片描述

  1. 新建c工程

在这里插入图片描述

  1. 工程名字

在这里插入图片描述

  1. 新建c源文件

在这里插入图片描述
在这里插入图片描述

  1. 将手写识别产生的权重、偏置、测试图片的头文件,以及hps_0.h文件复制到工程

在这里插入图片描述

  1. 添加库文件路径,因人而异,看你的quartus安装在哪里

F:\intelFPGA\18.1\embedded\ip\altera\hps\altera_hps\hwlib\include
F:\intelFPGA\18.1\embedded\ip\altera\hps\altera_hps\hwlib\include\soc_cv_av

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

  1. 编写main.c
//1.导入头文件
//2.接口定义(结构体的方式)
//3.定义初始化函数
// 例如:int led_init(void *virtual_base)
//4.主函数功能
//gcc标准头文件
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>
//HPS厂家提供的底层定义头文件
#define soc_cv_av //开发平台Cyclone V 系列
#include "hwlib.h"
#include "socal/socal.h"
#include "socal/hps.h"
//与用户具体的HPS 应用系统相关的硬件描述头文件
#include "hps_0.h"
#include "input_0.h" //10张图片的像素值矩阵 还可以通过网口、串口输入像素值,存储再数组 中,全部接收完成后再发送给FPGA进行计算
#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" //权重参数、偏置参数矩阵
#include "layer1_weight.h"
#include "layer2_bias.h"
#include "layer2_weight.h"
#define HW_REGS_BASE (ALT_STM_OFST) //HPS外设地址段基地址
#define HW_REGS_SPAN (0x04000000) //HPS外设地址段地址空间 64MB大小
#define HW_REGS_MASK (HW_REGS_SPAN - 1) //HPS外设地址段地址掩码
//接口定义(结构体的方式)
typedef struct{
	volatile float *img;
	volatile float *w1;
	volatile float *b1;
	volatile float *w2;
	volatile float *b2;
}fc_port_def;
fc_port_def fc_port;
typedef struct{
	volatile long long busy;
	volatile long long start;
	volatile long long ire_en;
	volatile long long done;
	volatile long long result;
}fc_ctrl_def;
fc_ctrl_def *fc_ctrl;

int fc_init(void *virtual_base)
{
	void *fc_ctrl_addr;
	fc_ctrl_addr = virtual_base + ((unsigned long)(ALT_LWFPGASLVS_OFST + CONNECT_0_FULL_CONNECT_INTERNAL_INST_AVS_CRA_BASE) & (unsigned long)(HW_REGS_MASK));
	fc_ctrl = (fc_ctrl_def*)fc_ctrl_addr; //接口映射
	fc_ctrl->start = 0;
	fc_port.img = virtual_base + ((unsigned long)(ALT_LWFPGASLVS_OFST + CONNECT_0_FULL_CONNECT_INTERNAL_INST_AVS_IMG_PIXEL_BASE) & (unsigned long)(HW_REGS_MASK));
	fc_port.w1 = virtual_base + ((unsigned long)(ALT_LWFPGASLVS_OFST + CONNECT_0_FULL_CONNECT_INTERNAL_INST_AVS_WEIGHT1_BASE) & (unsigned long)(HW_REGS_MASK));
	fc_port.b1 = virtual_base + ((unsigned long)(ALT_LWFPGASLVS_OFST + CONNECT_0_FULL_CONNECT_INTERNAL_INST_AVS_BIAS1_BASE) & (unsigned long)(HW_REGS_MASK));
	fc_port.w2 = virtual_base + ((unsigned long)(ALT_LWFPGASLVS_OFST + CONNECT_0_FULL_CONNECT_INTERNAL_INST_AVS_WEIGHT2_BASE) & (unsigned long)(HW_REGS_MASK));
	fc_port.b2 = virtual_base + ((unsigned long)(ALT_LWFPGASLVS_OFST + CONNECT_0_FULL_CONNECT_INTERNAL_INST_AVS_BIAS2_BASE) & (unsigned long)(HW_REGS_MASK));
	//加载权重参数、偏置参数
	memcpy(fc_port.w1,layer1_weight,784*64*sizeof(float));
	memcpy(fc_port.b1,layer1_bias,64*sizeof(float));
	memcpy(fc_port.w2,layer2_weight,64*10*sizeof(float));
	memcpy(fc_port.b2,layer2_bias,10*sizeof(float));
	return 0;
}
const float *imgx[10]={input_0,input_1,input_2,input_3,input_4,input_5,input_6,input_7,input_8,input_9};
int main()
{
	int fd,i;
	void *virtual_base;
	float time_s,time_ns,time_ms;
	struct timespec ts1,ts2;
	//1.打开MMU open()
	fd = open("/dev/mem",(O_RDWR | O_SYNC));
	if(fd == (-1))
	{
		printf("ERROR:could not open\"/dev/mem\"...\n");
		return 1;
	}
	//2.将外设地址空间映射到用户空间 mmap()
	virtual_base = mmap(NULL,HW_REGS_SPAN,( PROT_READ | PROT_WRITE ),MAP_SHARED,fd,HW_REGS_BASE);
	//3.初始化(一般是自己写的函数 )
	fc_init(virtual_base);
	//4.对外设进行相应的操作
	while(1)
	{
		for(i=0;i<10;i++)
		{
			memcpy(fc_port.img,imgx[i],784*sizeof(float));
			clock_gettime(CLOCK_MONOTONIC,&ts1); //记录函数开始时间
			fc_ctrl->start = 1;//打开推理
			while((fc_ctrl->done & 0x02) == 0);//当done不为2的时候(推理未完成),就阻塞(等待)
			printf("%d",fc_ctrl->done);
			fc_ctrl->start = 0; //推理完成,关闭使能
			clock_gettime(CLOCK_MONOTONIC,&ts2); //记录函数结束时间
			//由于总的时间=time_s+time_ns
			//为了显示方便,将总的时间统一转化为毫秒
			time_s = ts2.tv_sec - ts1.tv_sec;
			time_ns = ts2.tv_nsec - ts1.tv_nsec;
			time_ms = time_s*1000 + time_ns/1000000;
			printf("predict time:%.6f ms\n",time_ms);
			printf("input:%d,predict result:%d\n",i,fc_ctrl->result);
		}
	break;
	}
	//5.取消映射 munmap()
	if(munmap(virtual_base,HW_REGS_SPAN)!=0)
	{
		printf("ERROR:munmap()failed...\n");
		close(fd);
		return 1;
	}
	//6.关闭设备描述符 close()
	close(fd);
	return 0;
}

  1. ctrl + s保存然后编译

在这里插入图片描述

  1. 生成binary

在这里插入图片描述

六、调试

  1. 插入sd卡,启动开发板
  2. 打开MobaXterm

在这里插入图片描述

在这里插入图片描述

  1. 输入用户:root

在这里插入图片描述

  1. 输入密码:test

在这里插入图片描述

  1. 设置静态ip

在这里插入图片描述

  1. 配置开发板ip地址,使电脑和开发板处于同一网段,使用vi编辑器:vi /etc/network/interfaces

在这里插入图片描述

  1. 配置结果

在这里插入图片描述

  1. 测试电脑和开发板是否连通

在这里插入图片描述

  1. 配置ssh

在这里插入图片描述

在这里插入图片描述

  1. reboot重启

  2. eclipse配置ssh

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  1. 输入开发板ip地址

在这里插入图片描述

  1. 切换视图

在这里插入图片描述
在这里插入图片描述

  1. 建立连接

在这里插入图片描述

  1. 输入用户名和密码

在这里插入图片描述

  1. 复制生成的binary文件full_connect

在这里插入图片描述

  1. 粘贴至开发板Linux的/opt文件夹下

在这里插入图片描述

  1. 打开terminal

在这里插入图片描述

  1. 修改full_connect文件的权限,执行chmod 777 full_connect

在这里插入图片描述
  运行结果显示,推理一张的图片所花的时间是1ms多一点,与手写识别(一)中推理一张图片要使用0.8s时间相比,低了一个量级的推理时间,可见FPGA的优势是显而易见的。

总结

  手写识别项目到此就画上句号了,过程很艰辛,但结果让人满意。通过实验对比,我们也看到了FPGA推理神经网络模型的优势。后期将推出使用FPGA实现口罩识别项目,敬请期待!

  • 4
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值