前言
本文档为2021年FPGA创新设计竞赛紫光同创杯编写的参考案例。意在辅助同学们了解整个开发平台的架构和开发方式,以在此基础上设计出更棒的参赛作品!
辅助资料
开发必备硬件工具:
PGL12G开发板x1
开发必备软件工具:
Pango Design Suite
Modelsim
原始工程样例代码(文件夹名称):
uart_test_ann
系统总体架构介绍:
单个神经元结构:

整体神经网络结构:

说明:
1、通常一个神经网络由一个input layer,多个hidden layer和一个output layer构成。
2、图中圆圈可以视为一个神经元(又可以称为感知器)
3、设计神经网络的重要工作是设计hidden layer,及神经元之间的权重
Python神经网络训练及参数导出
下载pycharm软件
   下载地址:https://www.jetbrains.com/pycharm/download/#section=windows ,推荐下载community版本。
安装所需要的python库
sklearn是scikit-learn的简称,是一个基于Python的第三方模块。sklearn库集成了一些常用的机器学习方法,在进行机器学习任务时,并不需要实现算法,只需要简单的调用sklearn库中提供的模块就能完成大多数的机器学习任务。
sklearn库是在Numpy、Scipy和matplotlib的基础上开发而成的,因此在介绍sklearn的安装前,需要先安装这些依赖库。
Numpy库:Numpy(Numerical Python的缩写)是一个开源的Python科学计算库.    
Scipy库:是sklearn库的基础,它是基于Numpy的一个集成了多种数学算法和函数的Python模块。
matplotlib库: 是基于Numpy的一套Python工具包,它提供了大量的数据绘图工具。
安装顺序如下所示:
  1.Numpy库:
  2.Scipy库:
  3.matplotlib库:
  4.sklearn库: 
在pycharm中安装python库:
首先打开pycharm工具,选择File中的Setting选项,如下图所示

在打开的setting界面中我们点击python的解释器,你会看到很多导入的第三方库,如下图所示,点击最右边的加号

在弹出的available packages界面中,你会看到一个搜索框。然后我们搜索一个插件,比如我搜索numpy这个插件,会出现如下图所示的界面

最后点击安装
进行训练
调用相关的python库
from sklearn import datasets  # 加载数据集
import numpy as np # 数据处理工具
from sklearn.neural_network import MLPClassifier # 导入神经网络
调用iris训练集
iris=datasets.load_iris()
iris_x=iris.data  # 属性
iris_y=iris.target # 标签
indices = np.random.permutation(len(iris_x)) # 随机产生一个序列,或是返回一个排列范围,为拆分数据做准备
iris_x_train = iris_x[indices[:-10]]
iris_y_train = iris_y[indices[:-10]]
iris_x_test  = iris_x[indices[-10:]]
iris_y_test  = iris_y[indices[-10:]]
把iris数据集的后10组数据作为测试集,其它组数据作为训练集
调用分类函数
在 Scikit 中神经网络被称为多层感知器(Multi-layer Perceptron),它可以用于分类或回归的非线性函数。用于分类的模块是 MLPClassifier,而用于回归的模块则是 MLPRegressor。
MLPClassifier 主要用来做分类,我们用 MLPClassifier 在鸢尾花数据上做测试。
本次示例使用的是2层隐藏层,每层8个神经元。
clf = MLPClassifier(solver='lbfgs', alpha=1e-5,
                   hidden_layer_sizes=(8, 8), random_state=1)
clf.fit(iris_x_train,iris_y_train) #拟合
iris_y_predict = clf.predict(iris_x_test)
score=clf.score(iris_x_test,iris_y_test,sample_weight=None)
训练结果

参数导出
MLPClassifier 属性说明:
- classes:每个输出的类标签 
- loss:损失函数计算出来的当前损失值 
- coefs:列表中的第i个元素表示i层的权重矩阵 
- intercepts:列表中第i个元素代表i+1层的偏差向量 
- n_iter :迭代次数 
- n_layers:层数 
- n_outputs:输出的个数 
- out_activation:输出激活函数的名称。 
我们本次使用- coefs 、- intercepts来导出我们所需要的权重和偏差参数
print ('训练集的权重是:', clf.coefs_)
print('训练集的偏差是:', clf.intercepts_)
参数提取结果

RTL级ANN神经网络的实现
参数定义
因为训练得到的参数结果都是浮点数,所以我们需要先把它转换成定点数
本次示例采用的是21位定点数,一位符号位,八位整数位,十二位小数位。
parameter  w1_1 = 21'b1_1111_1111_1010_0001_1011;  
parameter  w2_1 = 21'b1_1111_1101_1100_0011_1110;
parameter  w3_1 = 21'b1_1111_1111_0100_1011_0010;
parameter  w4_1 = 21'b1_1111_1111_1011_1000_1000;
parameter  w5_1 = 21'b1_1111_1111_1000_0000_0011;
parameter  w6_1 = 21'b1_1111_1101_1100_0000_1001;
parameter  w7_1 = 21'b1_1111_1111_1000_1110_1000;
parameter  w8_1 = 21'b1_1111_1110_1111_1111_1001;
单个神经元的代码实现
乘累加运算:
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
    sum1_1 = 41'd0;
    sum_1 = 21'd0;
    sum2_1 = 21'd0;
    end
else begin
    sum1_1 = in1 * w1;
    sum2_1 = {sum1_1[40],sum1_1[31:24],sum1_1[23:12]}; 
    sum_1 =sum_1 +sum2_1;
    end 
end
乘累加的结果加上偏差:
 else if (cnt_1 == 8'd6) begin
         suma<= sum_1+b1;
         sumb<= sum_2+b2;
         sumc<= sum_3+b3;
         sumd<= sum_4+b4;
         sume<= sum_5+b5;
         sumf<= sum_6+b6;
         sumg<= sum_7+b7;
         sumh<= sum_8+b8;
    end
Relu激活函数的实现:
 assign suma1   = (suma[20] == 1'd0 )? suma: 21'b0;
 assign sumb1   = (sumb[20] == 1'd0 )? sumb: 21'b0;
 assign sumc1   = (sumc[20] == 1'd0 )? sumc: 21'b0;
 assign sumd1   = (sumd[20] == 1'd0 )? sumd: 21'b0;
 assign sume1   = (sume[20] == 1'd0 )? sume: 21'b0;
 assign sumf1   = (sumf[20] == 1'd0 )? sumf: 21'b0;
 assign sumg1   = (sumg[20] == 1'd0 )? sumg: 21'b0;
 assign sumh1   = (sumh[20] == 1'd0 )? sumh: 21'b0;
资源复用设计
因为每层的神经元的运算内容是一样,所以我们可以通过复用神经元来减少对资源的使用
本次示例通过改变每个神经元的输入和参数,来进行对神经元的复用,然后通过计数器来计算在每一层运算的时间,当到达了规定的时间就取出现应的结果,然后进行下一层的运算。
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
    cnt_1<= 8'd0;
    cnt_2<= 8'd0;
    cnt_3<= 8'd0;
end
else if(valid_in == 1'd1) begin
    cnt_1 <= cnt_1 + 1'd1;
    if(cnt_1>=8'd6 && cnt_3 <=8'd20)
        cnt_2 <=cnt_2 + 1'd1;
    if(cnt_2 >= 8'd10 && cnt_3 <=8'd20)
        cnt_3<=cnt_3 + 1'd1;
    if(cnt_3 ==8'd20)begin
        cnt_1<= cnt_1;
        cnt_2<=cnt_2 ;
        cnt_3<= cnt_3;
    end
end
else if (cnt_1 == 8'd6) begin
         suma<= sum_1+b1;
         sumb<= sum_2+b2;
         sumc<= sum_3+b3;
         sumd<= sum_4+b4;
         sume<= sum_5+b5;
         sumf<= sum_6+b6;
         sumg<= sum_7+b7;
         sumh<= sum_8+b8;
    end
    else if (cnt_2 == 8'd10) begin
        suma_1<= sum_1+b1;
        sumb_1<= sum_2+b2;
        sumc_1<= sum_3+b3;
        sumd_1<= sum_4+b4;
        sume_1<= sum_5+b5;
        sumf_1<= sum_6+b6;
        sumg_1<= sum_7+b7;
        sumh_1<= sum_8+b8;
     end
   else if (cnt_3 == 8'd10) begin
     out1<= sum_1+b1;
     out2<= sum_2+b2;
     out3<= sum_3+b3;
    end
 else if (cnt_1 > 8'd0&&cnt_1 <= 8'd4)begin
	      b1 <=  b1_1;
	      b2 <=  b2_1;
	      b3 <=  b3_1;
	      b4 <=  b4_1;
	      b5 <=  b5_1;
	      b6 <=  b6_1;
	      b7 <=  b7_1;
	      b8 <=  b8_1;
	
        case(cnt_1)
         8'd1: begin  
		        in1 <= ina;
		        w1 <= w1_1  ;
				w2 <= w2_1  ;
				w3 <= w3_1  ;
				w4 <= w4_1  ;
				w5 <= w5_1  ;
				w6 <= w6_1  ;
				w7 <= w7_1  ;
				w8 <= w8_1  ;
				end		
         8'd2:  begin
		        in1 <= inb;
		        w1 <= w1_2  ;
				w2 <= w2_2  ;
				w3 <= w3_2  ;
				w4 <= w4_2  ;
				w5 <= w5_2  ;
				w6 <= w6_2  ;
				w7 <= w7_2  ;
				w8 <= w8_2  ;
				end
         8'd3:  begin
		        in1 <= inc;
		        w1 <= w1_3  ;
				w2 <= w2_3  ;
				w3 <= w3_3  ;
				w4 <= w4_3  ;
				w5 <= w5_3  ;
				w6 <= w6_3  ;
				w7 <= w7_3  ;
				w8 <= w8_3  ;
				end
         8'd4:  begin
		        in1 <= ind;
		        w1 <= w1_4  ;
				w2 <= w2_4  ;
				w3 <= w3_4  ;
				w4 <= w4_4  ;
				w5 <= w5_4  ;
				w6 <= w6_4  ;
				w7 <= w7_4  ;
				w8 <= w8_4  ;
		        end
         default: begin
		        in1<=0;
		        w1 <= 0  ;
				w2 <= 0  ;
				w3 <= 0  ;
				w4 <= 0  ;
				w5 <= 0  ;
				w6 <= 0  ;
				w7 <= 0  ;
				w8 <= 0  ;
				end
        endcase   
整体实现
Modelsim仿真结果:

本次示例通过串口来验证神经网络在开发板上的准确性,结果如图:


所得的结果与训练的结果一致!