HLS:卷积运算单元设计与SDK测试
目录
- 一、引言
- 二、概念
- 三、程序
- 四、优化
- 五、测试
- 六、补充
一、引言
涉及内容包括:多位宽并行,动态定点数运算,设置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测试相关推荐
- HLS:矩阵乘法单元设计与SDK测试
目录 一.引言 二.程序框架 三.初步设计 四.报告分析 五.优化操作 六.接口优化 七.上板测试 八.补充部分 九.时间与参考 一.引言 矩阵乘法,涉及数组优化.循环优化和接口优化等.是一个学习HL ...
- 基于Verilog搭建一个卷积运算单元的简单实现
目录 前言 1. 图片的缓存与读取 2. 滑窗的构建 3. 权值的读取 3.1 行列计数器的构建 3.2 权重数据的取存 4. 卷积运算 4.1 乘法运算 4.2 加法运算 4.3 卷积输出有效位 前 ...
- 【Chips】VLSI Final Project:小型卷积核单元设计 项目总结
VLSI Final Project:小型卷积核单元设计 项目总结 0 项目背景 VLSI流程课程在期末要求完成一个project,来让我这样的小白简单走一遍VLSI的设计流程有个概念.作业内容是 ...
- 使用C语言实现卷积运算及移动平均滤波器
文章目录 课题目标 一.首先实现功率为1的白噪声 二.卷积运算头文件 三.主函数 四.结果及结论 课题目标 使用C语言和卷积运算实现N点移动平均滤波器程序. 输入信号如下所示: (1)x[n]=sin ...
- HLS:卷积神经网络LeNet5的实现与测试
目录 一.引言 二.LeNet5的学习 三.数学知识补充 四.HLS代码设计 五.仿真综合与优化 六.Zynq平台搭建测试 七.一些注意点 八.文献时间线与后续工作 一.引言 1.开发环境. Wind ...
- 从TeslaAP2.0/2.5运算单元看未来无人驾驶域控制器的设计趋势
雷锋网·新智驾按:本文来自未来出行服务商新悦智行联合创始人&CEO徐超.联合创始人&CTO李林峰的技术详解.新悦智行目前业务线包括新能源整车和L3级无人驾驶整合方案.今年4月,新悦智行 ...
- 01基于HLS的加速器Overlay设计实例 - 快速生成硬件IP
可进qq群进行相关Verilog知识交流:1073030956 基于HLS的加速器Overlay设计实例 – 快速生成硬件IP 介绍 本章节介绍了HLS的基本操作流程,你将熟悉HLS工程的创建.仿真. ...
- DianNao系列加速器总结(1)——架构与运算单元
本文为DianNao系列加速器总结的第一篇,有较多公式,简书不支持公式渲染,公示完整版待该总结完成后将统一发表在个人博客 简介 DianNao系列是中科院计算所推出的系列机器学习加速器,包括以下四个成 ...
- 轻量级卷积神经网络的设计技巧
点击上方"视学算法",星标公众号 重磅干货,第一时间送达 作者:zhouyuangan 来源:CVer 这篇文章将从一个证件检测网络(Retinanet)的轻量化谈起,简洁地介绍, ...
最新文章
- FMS3系列学习网上教程
- 开发管理 (2) -规划项目
- 只需几步,U盘就能变“光驱”
- MAP文件和调试(VC)(从崩溃地址找出错源码位置)
- Oracle定时器调用存储过程
- Visual Studio 2022这些重大更新,影响每一位.NET开发者!
- 面试官问我:Redis 内存满了怎么办
- Spring和JSF集成:MVC螺母和螺栓
- jQuery的事件绑定和解绑
- 微信5.0登录提示服务器繁忙,iOS集成友盟社会化分享微信无法登录?
- 验证登陆信息的合法性
- 通过运用多线程来异步控制程序的运行
- CSDN博客积分规则
- 记录自己Kettle下载全过程
- 教孩子学编程_教孩子编程的10种工具
- 安装ie11提示计算机安装了更新的版本,离线安装IE11浏览器提示quot;获取更新quot;解决方法 - 191路由网...
- 浪曦struts2学习笔记3
- 哈工大《同义词词林》共享版的若干改进
- 深入理解二进制 算法必备底层知识
- ​什么是bug?bug的源头在哪里?