目录

  • 一、引言
  • 二、概念
  • 三、程序
  • 四、优化
  • 五、测试
  • 六、补充

一、引言

涉及内容包括:多位宽并行,动态定点数运算,设置LOOP_TRIPCOUNT来性能分析等。


二、概念

1、功能定义。
为了让卷积运算单元更具灵活性,在输入图层的长宽高、卷积核的大小、Stride的大小等很多功能上可设置。

2、feature的内存排布。
之前的池化模块的feature,是一个四维数组。而卷积核没有分块前,也是四维的。这边按问答方式对一些概念进行整理。
为什么未分块前,卷积核(feature、kernel)为四维的?
从下面这张图上看,卷积核可以表示为2x3x3x3,即:个数x长x宽x通道数,不同的地方将这四个参数的位置可能颠倒了下,但都只是这四个概念。下面对这四个参数做个理解,①个数:表示不同卷积核的数目,对于一张完整的图(RGB都有),要提取里面的不同特征,需要多个不同的卷积核来进行提取,从之前的学习中,可以知道,卷积核本质就是特征feature,用一个特征怎么可能得到不同特征嘛,所以需要多个卷积核。②长x宽:这个为卷积核的size,很好理解,是和局部特征单独运算相关的。③通道数:相同卷积核的数目(权值共享),与一张完整图片中准备卷积处理的通道数相同,一般处理R、G、B通道,一个卷积核的通道数,也就为3了,同时可以得到3张feature map。在下面这张图的例子中,输入6x6,kernel为3x3,输出4x4,首先确定不是same模式,为valid模式,padding为0。只看H,有4-1=(6-3+2xpadding)/stride,则stride为1。通过在通道方向上相乘求累加后,9个点再做一次累加,得到输出feature map上的一个点。

为什么分块后,卷积核(feature、kernel)为五维的?
从下面这张图上看,可以把kernel上任意个数据表示为kernel: [CHout][Ky][Kx][CHin/K][K]
。CHout表示的是有多少个kernel,Ky和Kx为一个kernel的宽和长,CHin/K为在输入图层方向切了多少个子块,K为每个子块的索引。因此,本设计中,由一个四维的输入图层feature map和五维的feature(卷积核、权重),得到一个输出的feature map输出图层。

3、卷积运算的full、same和vaild模式是啥?
这个参考了前人的一篇文章,讲的很好,非常感谢,Add在最后边了。橙色为image,蓝色为kernel。
1°full mode模式:从image和kernel刚相交,就开始做卷积运算。而其余白色的地方全部填0,这样一张橙色的image先补了两行两列的0后,如果stride为1的卷积运算后,图片反而变大了,这模式很少用。

2°same mode模式:当kernel的中心K与image的边角重叠时,开始做卷积运算。这种模式下,如果stride为1的卷积运算后,输出的feature map尺寸是保持不变的(相当于输入图)。相比full mode,这模式就用的多了。该模式可以在前向传播的过程中,让特征图的大小保持不变,调参师不需要精准计算尺寸的变化,因为根本就没有变化。
由于卷积核的size尺寸一般为奇数,故可以总结一个padding公式,用来计算需要补多少行列的0数据,才能满足same mode模式。具体公式为:padding_x=(kx-1)/2,padding_y=(ky-1)/2。举个例子,这里size为33,那么,padding_x=padding_y=1。

3°valid mode模式:当kernel全在image里时,进行卷积运算,明显地,会丢掉一部分的数据。

如何计算卷积运算后的feature map的尺寸大小?
也参考了前人的博文,但网上貌似没有发现推导的过程,举几个例子应该也能总结下,这里也直接作为经验来使用了。
计算的公式为:output_h =(originalSize_h+padding x 2-kernelSize_h)/stride +1。
其中,originalSize_h为原始输入image的H或W,padding为填充的行或列数,kernelSize_h为kernel的size大小,stride为x或y方向上的步长。

拿一道面试的题来试试手吧。(卷积向下取整,池化向上取整)
input image:200×200
first layer_conv:kernel size 5×5,padding 1,stride 2
second layer_pooling:kernel size 3×3,padding 0,stride 1
third layer_conv:kernel size 3×3,padding 1,stride 1
output feature map?
答:
1°(200-5+21)/2+1 为99.5,取99。
2°(99-3)/1+1 为97。
3°(97-3+2*1)/1+1 为97。

分块时为什么是沿输入图层的方向,而不是输出图层方向?
为了并行地做子块的矩阵乘累加运算,得到结果(和之前FIR滤波器案例类似)。要时刻把握住,是取出输入feature map的一个子块,和权重的一个子块,来进行相乘累加的操作。


三、程序

1、conv_core.h文件。

#ifndef __CONV_CORE_H__
#define __CONV_CORE_H__#include <ap_int.h>
#include <iostream>using namespace std;#define K 8
// 单个数据是16bit位宽,一个子块数据是16*K位宽,用bus表示。
// 输入图层和权重都是ap_int<16>
typedef ap_int<16>   dtype_dat;
typedef ap_int<16*K> dtype_bus;
// 两个INT16数据相乘,得到的结果会溢出,用32位来存。根据经验,用40位来作为累加的结果位宽。
typedef ap_int<32>   dtype_mul;
typedef ap_int<32*K> dtype_mul_bus;
typedef ap_int<40>   dtype_acc;
// C语言无法传可变索引的数组的,但在硬件内存中,都按照一维分布,故可以把地址传进去。
// 后续得自己计算存放的地址来取数据
// 定点化后的数据由三个小数点的参数来计算得到,INT16,小数点由四位来表示
void Conv(ap_uint<16> CHin,  // 输入通道数ap_uint<16> Hin,   // 输入特征的高度ap_uint<16> Win,   // 输入特征的宽度ap_uint<16> CHout, // 输出特征的通道数ap_uint<8> Kx,     // 卷积核的宽度ap_uint<8> Ky,     // 卷积核的高度ap_uint<8> Sx,     // 卷积核扫描时,宽度方向的步进ap_uint<8> Sy,     // 卷积核扫描时,高度方向的步进ap_uint<1> mode,   // 卷积的模式,valid还是sameap_uint<1> relu_en,                // 激活非线性反激活层标志dtype_bus feature_in[],            // 输入feature map的数据,由于C中无法使用可变数组,故使用指针,传地址来定位具体数据,四维数组按一维分布// 之前池化使用的是固定大小的,可以按照固定数组来传,但这里数组长度是变的ap_uint<4> feature_in_precision,   // 输入feature map数据小数点位置dtype_bus W[],                     // 输入权重的数据,也是指针索引ap_uint<4> W_precision,            // 输入权重数据小数点位置dtype_bus feature_out[],           // 输出feature map的数据,也是指针索引ap_uint<4> feature_out_precision   // 输出feature map数据小数点位置);//mode: 0:VALID, 1:SAME
#endif

2、conv_core.cpp文件。

#include "conv_core.h"// 输入特征图四维:[C/K][H][W][K]
// 输入权重值五维:[CHout][Ky][Kx][CHin/K][K]
// 输出特征图四维:[C/K][H][W][K],均可以计算出来
void Conv(ap_uint<16> CHin,  // 输入通道数(输入图层方向上的数目)(卷积核与特征图参量)ap_uint<16> Hin,   // 输入特征的高度(卷积核与特征图参量)ap_uint<16> Win,   // 输入特征的宽度(卷积核与特征图参量)ap_uint<16> CHout, // 输出特征的通道数(卷积核参量)ap_uint<8> Kx,     // 卷积核的宽度ap_uint<8> Ky,     // 卷积核的高度ap_uint<8> Sx,     // 卷积核扫描时,宽度方向的步进ap_uint<8> Sy,     // 卷积核扫描时,高度方向的步进ap_uint<1> mode,   // 卷积的模式,valid还是sameap_uint<1> relu_en,                // 激活非线性反激活层标志dtype_bus feature_in[],            // 输入feature map的数据,由于C中无法使用可变数组,故使用指针,传地址来定位具体数据,四维数组按一维分布// 之前池化使用的是固定大小的,可以按照固定数组来传,但这里数组长度是变的ap_uint<4> feature_in_precision,   // 输入feature map数据小数点位置dtype_bus W[],                     // 输入权重的数据,也是指针索引ap_uint<4> W_precision,            // 输入权重数据小数点位置dtype_bus feature_out[],           // 输出feature map的数据,也是指针索引ap_uint<4> feature_out_precision   // 输出feature map数据小数点位置)//mode: 0:VALID, 1:SAME
{ap_uint<8> pad_x,pad_y;  // padding数据,表示x和y方向待填补的行列数ap_uint<16> CHin_div_K=(CHin+K-1)/K;  // 输入图层方向上,切块后每个的长度ap_uint<5> out_truncate;out_truncate=feature_in_precision+W_precision-feature_out_precision;if(mode==0)  // valid下不进行padding{pad_x=0;pad_y=0;}else  // same模式下进行padding,具体padding的数据值,在之前概念部分补充了{pad_x=(Kx-1)/2;pad_y=(Ky-1)/2;}// 通过输入的参数数据,计算输出图层的高度和宽度,这个的公式也是固定的ap_uint<16> Hout,Wout;Wout=(Win+2*pad_x-Kx)/Sx+1;Hout=(Hin+2*pad_y-Ky)/Sy+1;dtype_acc sum=0;dtype_bus out_tp=0;// 对输出feature map的数据进行计算// 关于是输出方向上需要多少重循环的定位:输出是一个四维的数据,需要前三重做迭代,第四重做处理// 一是输出图像的高度,二是输出图像的宽度,三是输入图层方向上切块了多少,四是具体索引到哪个子块LOOP_i:for(int i=0;i<Hout;i++){// 输出高度和宽度方向上的循环LOOP_j:for(int j=0;j<Wout;j++){// 输出通道方向上的循环,CHout表示有多少个kernel,是卷积核专门的一个参数,与输入特征图无关LOOP_cout:for(int cout=0;cout<CHout;cout=cout+1){// 第四维上的数据处理// 把卷积核size上一个点,在所有子块上的结果都求出来,再循环size上所有点LOOP_ii:for(int ii=0;ii<Ky;ii++){LOOP_jj:for(int jj=0;jj<Kx;jj++){// CHin_div_K输入方向上切块的循环// 输入通道方向上被切块了,需要做一个循环,按输入通道上子块的数目LOOP_cin:for(int cin=0;cin<CHin_div_K;cin=cin+1){ap_int<16> h=i*Sy-pad_y+ii;ap_int<16> w=j*Sx-pad_x+jj;dtype_mul_bus tp;dtype_bus dat;dtype_bus wt;// 判断是否在padding的范围内// 这段if代码是获取特征图数据和权重数据的if(h>=0 && w>=0 && h<Hin && w<Win){// 输入数据dat=feature_in[cin][h][w][K]// 根据四维数据的这个索引,来算出一维的数据// INT16*K feature_in[C/K][H][W]为feature_in的形状,[K]相当于INT16*K了// dat=feature_in[cin][h][w]为这个形状里取的索引dat=feature_in[cin*Hin*Win+h*Win+w];// INT16*K weight[CHout][Ky][Kx][CHin/K]为权重的形状,[K]相当于INT16*K了// wt=weight[cout][ii][jj][cin]为这个形状里取的索引wt=W[cout*CHin_div_K*Kx*Ky+ii*CHin_div_K*Kx+jj*CHin_div_K+cin];//std::cout<<"dat="<<dat<<",W="<<wt<<std::endl;}else{dat=0;wt=0;}// 每次取出一个特征图子块和权重子块,来进行累加for(int k=0;k<K;k++){// 具体子块的累加过程,对应元素相乘// tp是一个32*k的数据,位宽32*ktp.range(k*32+31,k*32)=(dtype_dat)dat.range(k*16+15,k*16)*(dtype_dat)wt.range(k*16+15,k*16);}for(int k=0;k<K;k++){// 累加,40位的数据,这个累加是移位前的数据sum+=(dtype_mul)tp.range(k*32+31,k*32);//std::cout<<"sum="<<sum<<std::endl;}//std::cout<<"sum="<<sum<<std::endl;if((cin==CHin_div_K-1) && (jj==Kx-1) && (ii==Ky-1) ){// 如果使用了非线性激活层,小于0就设为0if(relu_en & sum<0)sum=0;// 数据右移// 右移位数为输入feature map小数点位数+权重小数点位数-输出feature map小数点位数dtype_acc res=sum>>out_truncate;// 移位如果范围,进行前后的钳位if(res>32767)res=32767;elseif(res<-32768)res=-32768;dtype_dat res_16=res;out_tp.range((cout%K)*16+15,(cout%K)*16)=res;sum=0;// feature_out[cout][i][j][cout]if( ((cout%K)==K-1) || (cout==(CHout-1)) ){feature_out[(cout/K)*Wout*Hout+i*Wout+j]=out_tp;out_tp=0;}}}}}}}}
}

3、main.cpp文件。
C仿真是必须的,用来保证C语言这边的设计是没有问题的。

#include "stdio.h"
#include "conv_core.h"#define IN_WIDTH 10
#define IN_HEIGHT 10
#define IN_CH 1
#define IN_CH_DIV_K ((IN_CH+K-1)/K)#define KERNEL_WIDTH 5
#define KERNEL_HEIGHT 5
#define X_STRIDE 1
#define Y_STRIDE 1#define RELU_EN  0
#define MODE     1          //0:VALID, 1:SAME
#define X_PADDING (MODE?(KERNEL_WIDTH-1)/2:0)
#define Y_PADDING (MODE?(KERNEL_HEIGHT-1)/2:0)#define OUT_CH 1
#define OUT_CH_DIV_K ((OUT_CH+K-1)/K)
#define OUT_WIDTH ((IN_WIDTH+2*X_PADDING-KERNEL_WIDTH)/X_STRIDE+1)
#define OUT_HEIGHT ((IN_HEIGHT+2*Y_PADDING-KERNEL_HEIGHT)/Y_STRIDE+1)int main(void)
{dtype_bus feature_in[IN_CH_DIV_K][IN_HEIGHT][IN_WIDTH];dtype_bus W[OUT_CH][KERNEL_HEIGHT][KERNEL_WIDTH][IN_CH_DIV_K];dtype_bus feature_out[OUT_CH_DIV_K][OUT_HEIGHT][OUT_WIDTH];dtype_dat temp;for(int cin=0;cin<IN_CH_DIV_K;cin++)for(int i=0;i<IN_HEIGHT;i++)for(int j=0;j<IN_WIDTH;j++)for(int k=0;k<K;k++)if((cin*K+k)<IN_CH)feature_in[cin][i][j].range(16*k+15,16*k)=(1<<14);//i*IN_WIDTH+j;elsefeature_in[cin][i][j].range(16*k+15,16*k)=0;/*打印测试for(int cin=0;cin<IN_CH_DIV_K;cin++)for(int i=0;i<IN_HEIGHT;i++)for(int j=0;j<IN_WIDTH;j++)for(int k=0;k<K;k++){temp = feature_in[cin][i][j].range(16*k+15,16*k);//printf("feature_in[%d][%d][%d][%d] = %d \n", cin, i, j, k, temp);std::cout<<"feature_in["<<cin<<"]["<<i<<"]["<<j<<"]["<<k<<"]="<<(dtype_dat)temp<<std::endl;}*/for(int i=0;i<KERNEL_HEIGHT;i++)for(int j=0;j<KERNEL_WIDTH;j++)for(int cin=0;cin<IN_CH_DIV_K;cin++)for(int cout=0;cout<OUT_CH;cout++)for(int k=0;k<K;k++)W[cout][i][j][cin].range(16*k+15,16*k)=(1<<14);//(i*KERNEL_WIDTH+j);//(cout==0)?(i*KERNEL_WIDTH+j):0;/*打印测试for(int i=0;i<KERNEL_HEIGHT;i++)for(int j=0;j<KERNEL_WIDTH;j++)for(int cin=0;cin<IN_CH_DIV_K;cin++)for(int cout=0;cout<OUT_CH;cout++)for(int k=0;k<K;k++){temp = W[cout][i][j][cin].range(16*k+15,16*k);std::cout<<"W["<<cin<<"]["<<i<<"]["<<j<<"]["<<k<<"]="<<(dtype_dat)temp<<std::endl;}*/for(int cout=0;cout<OUT_CH_DIV_K;cout++)for(int i=0;i<OUT_HEIGHT;i++)for(int j=0;j<OUT_WIDTH;j++)feature_out[cout][i][j]=0;printf("1234\n");Conv(IN_CH,IN_HEIGHT,IN_WIDTH,OUT_CH,KERNEL_WIDTH,KERNEL_HEIGHT,X_STRIDE,Y_STRIDE,MODE,RELU_EN,&feature_in[0][0][0],14,&W[0][0][0][0],14,&feature_out[0][0][0],10);//mode: 0:VALID, 1:SAMEfor(int i=0;i<OUT_HEIGHT;i++)for(int j=0;j<OUT_WIDTH;j++)for(int cout=0;cout<OUT_CH_DIV_K;cout++){std::cout<<"OUT["<<cout<<"]["<<i<<"]["<<j<<"]="<<(dtype_dat)feature_out[cout][i][j].range(15,0)<<std::endl;}return 0;
}

输入feature map打印出来的数据,可以看出,只有当具体的高度i和宽度j发生变化时,才有数据,其他都为0。而weight数据全都是16384,这里就不贴出来了。

验证数据。


四、优化

1、第一次没加任何优化。
综合结果是直接报错了。原因是之前几个数组采用的是指针来索引的,HLS工具并没能很好知道这几个数组在内存中的具体位置和大小。

回想之前HP口那一次lab(池化),那里为了给自己的模块说明,数据是来源于DDR的具体哪个位置,使用了m_axi和offset=slave的方式。

 #pragma HLS INTERFACE m_axi depth=99999 port=feature_in offset=slave#pragma HLS INTERFACE m_axi depth=99999 port=W offset=slave#pragma HLS INTERFACE m_axi depth=99999 port=feature_out offset=slave

更改完,也确实可以综合过了,但Latency为“?”,这个是没有控制循环次数造成的。

2、循环次数设置。
按照所写的main函数,来对模块的循环变量进行控制。

3、端口部分优化。
除了feature in、feature out和weight数据,其他的都得CPU来进行配置,因此都是用GP口的s_axi。但feature in、feature out和weight数据得用HP,来与DDR内存进行交互。另外,单总线有一个缺陷,就是feature in和weight数据,得两个周期才能读取出来,但axi接口上有一个bundle捆版的操作,就是一个模块,可以产生两个接口。

 #pragma HLS INTERFACE s_axilite port=return#pragma HLS INTERFACE s_axilite port=feature_out_precision#pragma HLS INTERFACE s_axilite port=feature_in_precision#pragma HLS INTERFACE s_axilite port=Sy#pragma HLS INTERFACE s_axilite port=Kx#pragma HLS INTERFACE s_axilite port=Win#pragma HLS INTERFACE s_axilite port=Sx#pragma HLS INTERFACE s_axilite port=Hin#pragma HLS INTERFACE s_axilite port=W_precision#pragma HLS INTERFACE s_axilite port=relu_en#pragma HLS INTERFACE s_axilite port=Ky#pragma HLS INTERFACE s_axilite port=CHin#pragma HLS INTERFACE s_axilite port=mode#pragma HLS INTERFACE s_axilite port=CHout#pragma HLS INTERFACE m_axi depth=100 port=feature_in offset=slave bundle=bus1#pragma HLS INTERFACE m_axi depth=25 port=W offset=slave bundle=bus2#pragma HLS INTERFACE m_axi depth=100 port=feature_out offset=slave bundle=bus1

4、关于latency和II的优化。
这个需要非常理解HLS工具,HLS工具在处理for循环的时候,会做一个Loop Flattening的操作,就是外层的若干个循环中,若没有语句截断,就当成一个for循环,并把Loop的次数给计算出来。在代码设计时,就需要很注意了,如果把语句放在不同的for循环层,会导致出现:消耗周期=(内层Loop次数 x II+latency)x 外层Loop次数。因此,需要做一件事,就是把外层for循环的语句,全部搬移到内部趣,再通过if语句来限制啥时候触发。代码中的这句,就是做了这个工作。

if( (cin==CHin_div_K-1) && (jj==Kx-1) && (ii==Ky-1) )

5、关于feature out数据的优化。
feature out是一个axi的接口,输出数据不能使用range的方式来输出,这样会导致CPU先将最完整的数据读取出来,再使用截断的方式改写后写回(以前学嵌入式的时候,好像是位带的操作)。这操作,直接导致输出的latency变长。


五、测试

1、实验平台搭建。
使用到了两个HP口,具体平台如下。

实验环境,用了Zynq这块板子。

2、数据预备测试。
把feature_in、feature_out和weight数据先存储到内存中,并作打印测试先。结果和HLS的C仿真是一样的,没啥问题。另外,SDK的%d可以直接打印short、int等不同类型,不用单独使用hd来打印,这和c++的输出流很像。

// 初始化feature_in数据for(i=0; i<1; i++)for(j=0; j<10; j++)for(k=0; k<10; k++)for(v=0; v<8; v++)if((i*8+v)<1)feature_in[i][j][k][v]=(1<<14);//i*IN_WIDTH+j;elsefeature_in[i][j][k][v]=0;for(i=0; i<1; i++)for(j=0; j<10; j++)for(k=0; k<10; k++)for(v=0; v<8; v++)xil_printf( "the input_buffer is [%d][%d][%d][%d] : %d \n\r" , i,j,k,v, feature_in[i][j][k][v]);// 初始化weight数据for(i=0; i<1; i++)for(j=0; j<5; j++)for(k=0; k<5; k++)for(v=0; v<1; v++)for(w=0; w<8; w++)weight[i][j][k][v][w]=(1<<14);for(i=0; i<1; i++)for(j=0; j<5; j++)for(k=0; k<5; k++)for(v=0; v<1; v++)for(w=0; w<8; w++)xil_printf( "the weight is [%d][%d][%d][%d][%d] : %d \n\r" , i,j,k,v,w, weight[i][j][k][v][w]);

测试的结果。

通过debug的方式,检查寄存器配置的值。

3、SDK部分测试代码。
可能部分有些更改,详细可看工程。

#include <stdio.h>
#include <stdlib.h>
#include "platform.h"
#include "xil_printf.h"
#include "xparameters.h"
#include "xparameters_ps.h"
#include "xil_io.h"
#include "xConv.h"
#include "xConv_hw.h"
#include "sleep.h"#define DDR_BASEARDDR      XPAR_DDR_MEM_BASEADDR + 0x10000000int main()
{init_platform();int  i,j,k,v,w;//int  rev;int  data,state;short feature_in[1][10][10][8];short weight[1][5][5][1][8];short feature_out[1][10][10][8];XConv xconv;XConv_Config *ConfigPtr;xil_printf("feature_in address is %p \n\r", feature_in);xil_printf("weight address is %p \n\r", weight);xil_printf("feature_out address is %p \n\r", feature_out);print("Hello World\n\r");// 关闭cache,保证内存缓存读写的一致性Xil_DCacheDisable();// 初始化feature_in数据for(i=0; i<1; i++)for(j=0; j<10; j++)for(k=0; k<10; k++)for(v=0; v<8; v++)if((i*8+v)<1)feature_in[i][j][k][v]=(1<<14);//i*IN_WIDTH+j;elsefeature_in[i][j][k][v]=0;
/*for(i=0; i<1; i++)for(j=0; j<10; j++)for(k=0; k<10; k++)for(v=0; v<8; v++)xil_printf( "the input_buffer is [%d][%d][%d][%d] : %d \n\r" , i,j,k,v, feature_in[i][j][k][v]);
*/// 初始化weight数据for(i=0; i<1; i++)for(j=0; j<5; j++)for(k=0; k<5; k++)for(v=0; v<1; v++)for(w=0; w<8; w++)weight[i][j][k][v][w]=(1<<14);
/*for(i=0; i<1; i++)for(j=0; j<5; j++)for(k=0; k<5; k++)for(v=0; v<1; v++)for(w=0; w<8; w++)xil_printf( "the weight is [%d][%d][%d][%d][%d] : %d \n\r" , i,j,k,v,w, weight[i][j][k][v][w]);
*/for(i=0; i<1; i++)for(j=0; j<10; j++)for(k=0; k<10; k++)for(v=0; v<8; v++)feature_out[i][j][k][v]=0;ConfigPtr = XConv_LookupConfig(XPAR_CONV_0_DEVICE_ID);state = XConv_CfgInitialize(&xconv, ConfigPtr);// 判断初始化pool模块是否成功//state = XConv_Initialize(&xconv, XPAR_CONV_0_DEVICE_ID);if(state != XST_SUCCESS){print("XConv_Initialize fail!!\n\r");return XST_FAILURE;}//XConv_DisableAutoRestart(&xconv);// 众多参数配置,得参照slave的优化和C仿真时Conv调用传入的形参// 设置通道数XConv_Set_CHin_V(&xconv, 1);// 设置输入特征的高度XConv_Set_Hin_V(&xconv, 10);// 设置输入特征的宽度XConv_Set_Win_V(&xconv, 10);// 设置输出特征的通道数XConv_Set_CHout_V(&xconv, 1);// 设置卷积核的宽度、高度XConv_Set_Kx_V(&xconv, 5);XConv_Set_Ky_V(&xconv, 5);// 设置卷积核扫描水平与竖直的步进XConv_Set_Sx_V(&xconv, 1);XConv_Set_Sy_V(&xconv, 1);// 设置卷积的模式XConv_Set_mode_V(&xconv, 1);// 设置是否要relu非线性激活层XConv_Set_relu_en_V(&xconv, 0);// 设置feature_in、feature_out和weight的地址,以及各自小数点的位置XConv_Set_feature_in_V(&xconv, (u32)feature_in);XConv_Set_feature_in_precision_V(&xconv, 14);XConv_Set_W_V(&xconv, (u32)weight);XConv_Set_W_precision_V(&xconv, 14);XConv_Set_feature_out_V(&xconv, (u32)feature_out);XConv_Set_feature_out_precision_V(&xconv, 10);// 启动电路,将ap_start置为1,开始计算。XConv_EnableAutoRestart(&xconv);XConv_Start(&xconv);print("Test Start!!!\n\r");// 数据输出// 判断计算完成的条件是ap_done为1,当不是1时说明还尚未完成,就一直读取判断,直至算完。
/*  data  = XConv_IsDone(&xconv);while(data != 1){data  = XConv_IsDone(&xconv);}*/sleep(20);print("Test Done!!!\n\r");for(i=0; i<1; i++)for(j=0; j<10; j++)for(k=0; k<10; k++)//for(v=0; v<8; v++)xil_printf( "the feature_out is [%d][%d][%d][2] : %d \n\r" , i,j,k, feature_out[i][j][k][2]);cleanup_platform();return 0;
}

4、测试结果。
出现一个问题,数据不在k为0时有,而是k为2时才有数据,这是和C仿真冲突的,已经做了下面一些尝试:调试发现,问题关键在于XConv_IsDone函数没有成功返回1,即模块功能没正常完成。
1°调整了conv模块时钟为20MHz,不行。
2°generator output再生成比特流,不行。
3°使用单总线验证,降低效率来验证,同样问题,不行。
4°更改了feature和权重数据存储的方式,不行。
5°尝试了Disable模块后再配置,不行。

5、猜测与后续验证。
1°内存问题,使用了双HP口,如果模块想同时对DDR访问,是不是有些细节没有注意到。可用单端验证过,这问题双HP口应该可以处理,那内存上的问题,可能是地址与存储形式上,需要后续验证。
2°Conv模块配置问题,这部分也是还没验证的,对比了很多文章里的配置,基本上都是一个套路,可依旧不行。要说细节,就是双HP口的模块配置,会不会需要打开interrupt这东西,也需要后续验证,不过得先理解下按几个interrupt是用来干啥的。
就很无语,C语言只有那几行代码,多维数组测试也没啥问题,但数据发生了漂移,而且前面有几个是错的,但后面基本都是对的,前面做的几次测试,也都是这个结果,除了能排除薛定谔的Conv现象外,这个Conv模块是没啥问题的,Bug集中在“SDK测试的配置中”。算了去南亭吃碗面,今天天气太好了,适合出去玩,打球啥的,后续再填这个坑。


六、补充

一些关键时间点的记录:

2021年04月13日:卷积的一些概念。
2021年04月14日:动态定点数概念、网络定点化概念、权重和特征的排布方式、子块间并行、卷积运算单元需支持哪些参数(卷积规模等)、代码的理解。
2021年04月20日:分析模块需要如何工作,约束优化等。
2021年04月21日早上:板级测试,但失败了,原因是数据前几个出错,并且发生漂移。

参考的资料:
为什么卷积核是四维的:https://blog.csdn.net/qq_30763385/article/details/103094391。
卷积的三种模式:https://blog.csdn.net/leviopku/article/details/80327478
面试中常考的feature map的大小计算:https://zhuanlan.zhihu.com/p/49913137
feature map大小计算方法:https://blog.csdn.net/qq_28424679/article/details/78665273

后续可能做的工作:(非常的多)
1、填坑调Bug。
2、卷积相关部分的加深学习。
3、该实验在PynqZ2上的验证。这得等有Pynq板卡后了。
4、HLS工具中Analysis的学习。是一个分析综合后latency和II的组件,值得学习。
5、C语言部分的提高。C语言长期不用带来的后遗症,后续回看朱老师的课,并研究下算法,卷积相关的Lab难点在于多重for循环设计与多维数组。

Some Notes:
1、多重for循环的一个技巧。如果想把外层循环的多条语句放到内部,只需要剪切后,在外面套一层if来判断,就行了。
2、关于程序下载的一个点。之前好像application是默认有勾上的,这次不知为啥,生成的平台没有勾上,下载程序后,啥反应都没有,主要原因就是elf程序没有下载到CPU里边。

HLS:卷积运算单元设计与SDK测试相关推荐

  1. HLS:矩阵乘法单元设计与SDK测试

    目录 一.引言 二.程序框架 三.初步设计 四.报告分析 五.优化操作 六.接口优化 七.上板测试 八.补充部分 九.时间与参考 一.引言 矩阵乘法,涉及数组优化.循环优化和接口优化等.是一个学习HL ...

  2. 基于Verilog搭建一个卷积运算单元的简单实现

    目录 前言 1. 图片的缓存与读取 2. 滑窗的构建 3. 权值的读取 3.1 行列计数器的构建 3.2 权重数据的取存 4. 卷积运算 4.1 乘法运算 4.2 加法运算 4.3 卷积输出有效位 前 ...

  3. 【Chips】VLSI Final Project:小型卷积核单元设计 项目总结

    VLSI Final Project:小型卷积核单元设计 项目总结 0 项目背景 ​ VLSI流程课程在期末要求完成一个project,来让我这样的小白简单走一遍VLSI的设计流程有个概念.作业内容是 ...

  4. 使用C语言实现卷积运算及移动平均滤波器

    文章目录 课题目标 一.首先实现功率为1的白噪声 二.卷积运算头文件 三.主函数 四.结果及结论 课题目标 使用C语言和卷积运算实现N点移动平均滤波器程序. 输入信号如下所示: (1)x[n]=sin ...

  5. HLS:卷积神经网络LeNet5的实现与测试

    目录 一.引言 二.LeNet5的学习 三.数学知识补充 四.HLS代码设计 五.仿真综合与优化 六.Zynq平台搭建测试 七.一些注意点 八.文献时间线与后续工作 一.引言 1.开发环境. Wind ...

  6. 从TeslaAP2.0/2.5运算单元看未来无人驾驶域控制器的设计趋势

    雷锋网·新智驾按:本文来自未来出行服务商新悦智行联合创始人&CEO徐超.联合创始人&CTO李林峰的技术详解.新悦智行目前业务线包括新能源整车和L3级无人驾驶整合方案.今年4月,新悦智行 ...

  7. 01基于HLS的加速器Overlay设计实例 - 快速生成硬件IP

    可进qq群进行相关Verilog知识交流:1073030956 基于HLS的加速器Overlay设计实例 – 快速生成硬件IP 介绍 本章节介绍了HLS的基本操作流程,你将熟悉HLS工程的创建.仿真. ...

  8. DianNao系列加速器总结(1)——架构与运算单元

    本文为DianNao系列加速器总结的第一篇,有较多公式,简书不支持公式渲染,公示完整版待该总结完成后将统一发表在个人博客 简介 DianNao系列是中科院计算所推出的系列机器学习加速器,包括以下四个成 ...

  9. 轻量级卷积神经网络的设计技巧

    点击上方"视学算法",星标公众号 重磅干货,第一时间送达 作者:zhouyuangan 来源:CVer 这篇文章将从一个证件检测网络(Retinanet)的轻量化谈起,简洁地介绍, ...

最新文章

  1. FMS3系列学习网上教程
  2. 开发管理 (2) -规划项目
  3. 只需几步,U盘就能变“光驱”
  4. MAP文件和调试(VC)(从崩溃地址找出错源码位置)
  5. Oracle定时器调用存储过程
  6. Visual Studio 2022这些重大更新,影响每一位.NET开发者!
  7. 面试官问我:Redis 内存满了怎么办
  8. Spring和JSF集成:MVC螺母和螺栓
  9. jQuery的事件绑定和解绑
  10. 微信5.0登录提示服务器繁忙,iOS集成友盟社会化分享微信无法登录?
  11. 验证登陆信息的合法性
  12. 通过运用多线程来异步控制程序的运行
  13. CSDN博客积分规则
  14. 记录自己Kettle下载全过程
  15. 教孩子学编程_教孩子编程的10种工具
  16. 安装ie11提示计算机安装了更新的版本,离线安装IE11浏览器提示quot;获取更新quot;解决方法 - 191路由网...
  17. 浪曦struts2学习笔记3
  18. 哈工大《同义词词林》共享版的若干改进
  19. 深入理解二进制 算法必备底层知识
  20. ​什么是bug?bug的源头在哪里?

热门文章

  1. java工作流引擎,roadflow(一个强大的工作流引擎)
  2. 华硕笔记本台式机安装系统
  3. 【RTSP/RTP/RTCP/SDP】协议详解
  4. 写给小鹤双拼用户的一封信
  5. 逻辑回归算法 - 乳腺癌预测
  6. .net 使用ClickOne方式发布的应用程序无法启动的解决办法
  7. C# 通过点击按钮打开文件夹
  8. 多项式加法(MOOC 翁凯 C语言源码)
  9. 如何插入数学的花体和空心体字母
  10. 关于Iframe嵌入页面,获取内部页面高度的办法