目录

  • 一、引言
  • 二、程序框架
  • 三、初步设计
  • 四、报告分析
  • 五、优化操作
  • 六、接口优化
  • 七、上板测试
  • 八、补充部分
  • 九、时间与参考

一、引言

矩阵乘法,涉及数组优化、循环优化和接口优化等。是一个学习HLS非常好的Lab。

HLS新建工程这些就不记录了,在新建时有个需要注意,就是Clock Period的设置,如果设为10,会按照100MHz时钟来进行优化,如果一次运算15ms,就会分配到两个时钟来计算。但HLS工具预估是比较保守的,比如预估一次运算15ms,但实际上板卡后这个运算,就只要7ms。因此可以把周期稍微放大些/减轻约束压力,让HLS在做优化时,能放在一个周期内完成,但如果达不到约定的时钟频率,是可以进行更改的。在新建后的工程右键Project Settings,来进行更改。


二、程序框架

有Source、Include源代码和Test Beach目录。

用于实现矩阵乘法功能的代码,放在Source目录下。用于测试矩阵乘法功能的代码,放在Test Beach目录下。

新建文件:matrix_mul.cpp、matrix_mul.h和main.cpp。

在matrix_mul.h文件中,使用#ifdef 宏的方式,来避免重复声明某些内容。


三、初步设计

还未进行优化前的基本功能设计。

1、matrix_mul.h文件
任意精度定点数:在传统C中,char、int、short、long等的位宽都是固定的,8位、32位、16位和64位。如果电路中要求10位宽的数据,则常规变量无法实现,HLS为了能满足需求,搞了一种ap_int<>类型的数据变量,<>内放的是该类型变量的位宽,可以认为ap_int<8> A与char B,是两种类型相同的变量。

#ifndef __MATRIX_MUL__
#define __MATRIX_MUL__// HLS提供的任意精度定点数文件
#include "ap_fixed.h"// A元素对应行,与B元素对应列,进行相乘得到C
void matrix_mul(ap_int<8> A[4][4], ap_int<8> B[4][4], ap_int<16> C[4][4]);#endif

2、matrix_mul.cpp文件

#include "matrix_mul.h"void matrix_mul(ap_int<8> A[4][4], ap_int<8> B[4][4], ap_int<16> C[4][4]){for(int i=0;i<4;i++)for(int j=0;j<4;j++){C[i][j]=0;// 循环乘四次,并进行相加for(int k=0;k<4;k++){C[i][j]=C[i][j]+A[i][k]*B[k][j];}}
}

3、main.cpp文件

#include "matrix_mul.h"
#include <iostream>int main(){ap_int<8>  A[4][4];ap_int<8>  B[4][4];ap_int<16> C[4][4];// 初始化赋值0、1、2、3、4、5...for(int i=0;i<4;i++)for(int j=0;j<4;j++){A[i][j]=i*4+j;B[i][j]=A[i][j];}matrix_mul(A, B, C);for(int i=0;i<4;i++){for(int j=0;j<4;j++)std::cout<<"C["<<i<<","<<j<<"]="<<C[i][j]<<std::endl;}return 0;
}

代码写完,第一步要跑C Simulation,用快捷键Ctrl + B,就可以进行编译了。生成exe文件,就证明没有语法错误。然后,在菜单栏里,有一个Run C Simulation,点后进行C仿真,来看结果是否正确。是纯C语言的仿真。

第二步进行综合,点菜单栏的Run C Synthesis。可能会出现下面这个“The function must be specified”错误,原因是可能有很多C函数,但哪个是顶层,需要确定下。在工程中右键Project Settings,选择Synthesis选项卡,将Top Function设置为matrix_mul,就可以跑综合了。


四、报告分析

综合完,会生成Synthesis的报告文件,下边对该文件的几个关键参数进行分析。

1、Performance Estimates,性能估计。
1°Timing:包含clock,时钟约束。
2°Latency:在Summary中,包含了Latency与Interval,初步设计中,Latency为169,表示这个矩阵乘法的函数模块,需要耗时169个时钟周期。Interval是这次矩阵乘法与下一次,需要间隔多少周期,由于没有进行流水线的设计,这里需要间隔169个时钟周期,才能进行下一个的矩阵运算,因此Interval等于169。在Detail中,有循环内部的一些细节,可以看到每个循环需要的一些周期数,有些循环可能需要状态跳转,会消耗一部分的时钟周期。后续优化,主要也是照着Latency来进行优化。

2、Utilization Estimates,资源利用率估计。
一些资源使用的情况。

3、Interface,端口情况。
模块的参数就是端口,可以通过参数来生成不同的接口,不设置为缺省,默认是存储器里的数据。还有一些信号,ap开头,为控制信号。


五、优化操作

初步设计中,完全没有利用到FPGA的并行性,现考虑一些优化操作,来提高性能。

1、行列相乘部分
行列相乘后相加的代码,我们希望只用一个周期就计算出来。两个方法,1是利用参数循环展开,2是告诉编译器,这部分编译时,需要1个周期来完成,这样也会自动展开。

     C[i][j]=0;for(int k=0;k<4;k++){C[i][j]=C[i][j]+A[i][k]*B[k][j];}

手动循环展开。在功能模块界面,右边有一个Directive,里边有一个for Statement,点开有三个循环,选中最里边那个子循环,右键Insert Directive,打开Vivado HLS Directive Editor这个界面。选择UNROLL,还有Source File和Directive File的选项,Source File是将参数放在C源代码文件中,Directive File是把参数单独放在一个源代码文件中,一般选择Source File就行了。

Note:本来这方式在这里是可以的,但有个C[i][j]=0。如果循环展开的话,这个赋值操作,也会占用一个周期。因此,干脆直接将第二级子循环的Iteration Latency(某个循环消耗的Latency)设置为1,就可以实现上述代码一个周期的目的,如何实现呢?
在右边Directive选中第二级循环,右键Insert Directive,打开Vivado HLS Directive Editor后,选择PIPELINE,将里边的II,就是Iteration Latency,设置为1。操作完,会出现#pragma HLS PIPELINE II=1的命令。该操作可强制让每个运算在一个周期内完成,共16个运算,也就是只需要16个周期。

2、数组部分PARTITION

单、双口存储器的限制,存储器读写冲突,会出现下图那个错误,导致没法一次读取出4个数据,进行行列相乘。如果把数组A的存储器竖着(二维)切成四个,就可以一次读出四个数据。同理,数组B的存储器横着(一维)切成四个,也可以实现一次读出四个数据。不知道这个原因时,可以先进行仿真,来找到问题的关键。

使用#pragma HLS ARRAY_PARTITION variable=A complete dim=2来完成切割。

3、数组部分RESHAPE
将原来深度为16,位宽为8的存储器,变成深度为4,位宽为32的存储器。A数组按二维堆起来,B数组按一维堆起来。
使用#pragma HLS ARRAY_RESHAPE variable=A complete dim=2来完成组合。

PARTITION一次可以读相同或不同的四个数据地址,但RESHAPE一次读的四个数据,是同一个地址的。通过实验波形,可以更加深入区分这两种操作。all是导出所有信号的波形。

至少消耗的时钟周期为16*interval+latency,除此外还有写状态转换的消耗。interval可以被约束为1,那latency呢?latency也可以由#pragma HLS LATENCY min=5 max=5来约束。

经过上述操作,综合后可以发现,只用了18个周期便可以完成了,至于为什么不是16个周期,是因为还有一个周期进行启动,并且写操作也有一个周期的延迟。


六、接口优化

之前使用的都是ap_memory存储器的接口,但模块一般挂接到SoC上,这里将接口改为s_axilite类型,即作为slave设备挂载在SoC上。

基本命令是:#pragma HLS INTERFACE s_axilite port=A。
实际中发现,如果只添加这个命令,interface接口上,还是有ap_start与ap_done模块使能控制信号,这个需要和axi信号加以区分下,在这里,axi只做了数据传输的工作,但模块需要先使能工作才行,就是通过ap_start来完成的。为了让ap_start和ap_done这些信号也受CPU的控制,可以添加#pragma HLS INTERFACE s_axilite port=return。

将ap_start与ap_done都变成axi接口综合后的结果。

最后,点击report RTL,就能将工程输出为一个IP,在工程中调用。


七、上板测试

使用的FPGA型号是xc7z020clg400-1。环境简陋,将就着看。

1、平台搭建。
创建最小系统的Zynq核,并将DDR、UART0配置好,同时引出100MHz的PL时钟,还有一个复位信号。总线互联模块是一接一的,CPU启动工作matrix_mul模块,通过一个AXI口访问,将16个数据从AXI发送到模块,并检测是否done完成。每次改完,记得生成wapper和generate output两步。

2、生成并导出bitstream,同时launch SDK。
这个没啥复杂的,传统流程。需要留意下,matrix_mul模块的寄存器地址。

3、SDK开发
新建一个application project的HelloWorld模板,把串口调通之后,开始进行模块的测试,测试的代码如下。为了看懂这个代码,需要去学习几个AXI外设控制的函数。

#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "stdio.h"
#include "xil_io.h"
#include "xparameters.h"
#include "xmatrix_mul_hw.h"
#include "xmatrix_mul.h"int main()
{int data;int state;// 如果不使用大括号,会出现这个错误:missing braces around initializerchar A[4][4] = {{0,1,2,3},{4,5,6,7},{8,9,10,11},{12,13,14,15}};char B[4][4] = {{0,1,2,3},{4,5,6,7},{8,9,10,11},{12,13,14,15}};short C[4][4];// 定义一个XMatrix_mul类型的变量xmatrix_mul,该变量为自动生成的。XMatrix_mul xmatrix_mul;init_platform();// 将HLS生成的IP赋值给xmatrix_mul,用于后续操作,并将状态信息赋值给state,用于判断是否初始化成功。state = XMatrix_mul_Initialize(&xmatrix_mul, XPAR_MATRIX_MUL_0_DEVICE_ID);if(state != XST_SUCCESS){print("XMatrix_mul_Initialize fail!!\n\r");return XST_FAILURE;}// 由于A是将一行组合成一个数据进行存储,所以接下来是将组合好的新的数值写进RAM。// 这个是和之前进行RESHAPE操作相匹配的,输入的数据是32位的。// Xil_Out32函数是将一个32位的值,写入到一个特定的地址中去。// XMATRIX_MUL_AXILITES_ADDR_A_V_BASE是matrix_mul模块内addr寄存器的偏置地址// XPAR_MATRIX_MUL_0_S_AXI_AXILITES_BASEADDR是matrix_mul模块内addr寄存器的基地址// 基地址+偏置地址,就可以完成对该地址的操作。for(int i=0;i<4;i++){u32 tp = 0;tp = A[i][0] + (A[i][1] << 8) + (A[i][2] << 16) + (A[i][3] << 24);Xil_Out32((XMATRIX_MUL_AXILITES_ADDR_A_V_BASE+XPAR_MATRIX_MUL_0_S_AXI_AXILITES_BASEADDR) + (i*4), (tp));}// 由于B是将一列组成一个数据进行存储,所以接下来是将组合好的新的数值写进RAM。for(int i=0;i<4;i++){u32 tp = 0;tp = B[0][i] + (B[1][i] << 8) + (B[2][i] << 16) + (B[3][i] << 24);Xil_Out32((XMATRIX_MUL_AXILITES_ADDR_B_V_BASE+XPAR_MATRIX_MUL_0_S_AXI_AXILITES_BASEADDR) + (i*4), (tp));}// 第一次启动后可以自启动,不必再初始化。XMatrix_mul_EnableAutoRestart(&xmatrix_mul);// 启动电路,将ap_start置为1,开始计算。XMatrix_mul_Start(&xmatrix_mul);print("Test Start!!!\n\r");// 数据输出// 判断计算完成的条件是ap_done为1,当不是1时说明还尚未完成,就一直读取判断,直至算完。data  = XMatrix_mul_IsDone(&xmatrix_mul);while(data != 1){data  = XMatrix_mul_IsDone(&xmatrix_mul);}for(int i =0;i<4;i++){int tp;for(int j =0;j<2;j++){tp = XMatrix_mul_ReadReg((XMATRIX_MUL_AXILITES_ADDR_C_V_BASE + XPAR_MATRIX_MUL_0_S_AXI_AXILITES_BASEADDR),4*(2*i+j));C[i][2*j+1] = tp >> 16;C[i][2*j]   = tp - C[i][2*j+1] * 65536;}}for(int i=0;i<4;i++){for(int j=0;j<4;j++){printf("C[%d][%d]:%d\n",i,j,C[i][j]);}}print("Test End!!!\n\r");cleanup_platform();return 0;
}

4、实验输出的测试。


八、补充部分

1、关于ap控制信号。
控制信号方面,着重关注的是ap_start与ap_done,在ap_start变为高电平后,模块开始工作,工作结束时ap_done拉高一个时钟周期,如果要继续工作,则ap_start继续为高电平,反之变为低电平,停止该模块的工作。
2、关于SDK方面。
之前因为没有板卡,且对AXI有一部分的恐惧,但这次实验后,发现外设的访问并没有想象中的难,只需要寄存器地址就可以了,就像下面这张表列出来的。至于AXI内部的很多控制信号,CPU的Core其实已经帮忙做完了。

3、最后的matrix_mul.cpp代码。

// matrix_mul.cpp
#include "matrix_mul.h"void matrix_mul(ap_int<8> A[4][4], ap_int<8> B[4][4], ap_int<16> C[4][4]){#pragma HLS INTERFACE s_axilite port=return#pragma HLS INTERFACE s_axilite port=B#pragma HLS INTERFACE s_axilite port=C#pragma HLS INTERFACE s_axilite port=A#pragma HLS ARRAY_RESHAPE variable=B complete dim=1#pragma HLS ARRAY_RESHAPE variable=A complete dim=2for(int i=0;i<4;i++)for(int j=0;j<4;j++){#pragma HLS PIPELINE II=1C[i][j]=0;// 循环乘四次,并进行相加for(int k=0;k<4;k++){C[i][j]=C[i][j]+A[i][k]*B[k][j];}}
}

九、时间与参考

一些关键时间点的记录:

2021年04月09日:看了一些课,学了HLS的一些用法。
2021年04月10日:搭建并测试了HLS写的矩阵乘法模块的准确性。
后续可能做的工作:如果有Pynq板卡,计划将本实验作为Pynq入门实验。

参考性的资料:

warning: missing braces around initializer的解决
基于ZYNQ的HLS矩阵乘法加速实验


HLS:矩阵乘法单元设计与SDK测试相关推荐

  1. HLS:卷积运算单元设计与SDK测试

    目录 一.引言 二.概念 三.程序 四.优化 五.测试 六.补充 一.引言 涉及内容包括:多位宽并行,动态定点数运算,设置LOOP_TRIPCOUNT来性能分析等. 二.概念 1.功能定义. 为了让卷 ...

  2. 基于 Sodor 的矩阵乘法加速器设计之C++

    实现两个矩阵的乘法运算,两个矩阵的大小为 MK 和 KN,其中 M>1024,N,K 不超过 256.输入矩阵 A 和 B 的数据位宽都为 8b,输出矩阵 C 的数据位宽为 16b.由于完成乘加 ...

  3. 5位无符号阵列乘法器设计_可变位宽的大规模矩阵乘法方法

    引言 本文介绍了一种数据位宽可变的乘法方法,由于避免了DSP的使用,可以充分利用LUT资源,在DSP数量少的芯片上也可以获得很高的计算量.这种方法更适合大矩阵乘法,矩阵越大,计算效率就会越高. 01 ...

  4. 并行程序设计方法实验(包括openmp、向量化实现pi计算、SPECOMP2012测试、矩阵乘法优化)

    目录 一.实验环境 二.专题一之积分计算圆周率 2.1向量优化 2.2 OpenMP优化 三.专题二之测试SPECOMP2012 3.1初步了解SPECOMP 3.2系统基本配置 3.3实践 3.3. ...

  5. FPGA HLS Matrix_MUL 矩阵乘法的计算与优化

    新建Vivado工程 设置clock,10表示一个周期10ns,带宽100M vivado工具比较保守,计算需要的延迟是14,实际优化可以在10,设置大一点,优化的计算更多,一般约束设置大一点在30- ...

  6. GTX 295 VS C1060 矩阵乘法测试(cublas)

    测试描述: 使用cublas库进行矩阵乘法测试 C=A*B, 矩阵AB 大小从128*128 到 16384*16384 float 类型 主要测试计算时间, 讨论在不同矩阵大小的情况下, 哪块卡更实 ...

  7. c语言课程设计 矩阵乘法,C语言课程设计(论文)-矩阵乘法.doc

    C语言课程设计(论文)-矩阵乘法 学 号: 课 程 设 计 题 目矩阵乘法 教 学 院计算机学院 专 业09计算机科学与技术 班 级09计科(1)班 姓 名 指导教师 2010年12月25日 课程设计 ...

  8. MegEngine| CUDA 矩阵乘法终极优化

    前言 单精度矩阵乘法(SGEMM)几乎是每一位学习 CUDA 的同学绕不开的案例,这个经典的计算密集型案例可以很好地展示 GPU 编程中常用的优化技巧,而能否写出高效率的 SGEMM Kernel , ...

  9. CUDA 矩阵乘法优化

    这个完全是基础知识啊~~  哪不对 大佬们帮忙指出啊 CUDA 矩阵乘法优化手段详解 Naive 实现的分析:到底差在哪里? 笔者面试过不少具有 CUDA 编程经验的校招同学,当提问使用 CUDA 编 ...

最新文章

  1. Django models部分,数据库建立,错误解决
  2. 虚拟机centos7繁忙关不了机的解决方法(转载)
  3. Android开发之EditText监听软键盘清除(输入是否清除)
  4. php干扰函数,php – 类构造函数干扰另一个类
  5. 简历要求中“ 扎实的JAVA基础”的学习方法
  6. jq 封装弹窗提示框,自动消失,确认
  7. 电脑主板接口_电脑主板接口大全
  8. 一文追溯 ETL 的发展历程
  9. JAVA:文本文件读写使用Reader/Writer,二进制文件使用InputStream/OutputStream
  10. 更改 Inno Setup 5、6卸载图标
  11. Pycharm安装python包的四种常用方式
  12. 硬件工程师学习英语必备
  13. 怎么把电脑上的python软件卸载干净_怎么把一个软件卸载干净_把一个软件卸载干净的两种方法-系统城...
  14. Python-argparse库基本使用方法和add_argument() 参数详解
  15. 7z解压crc错误_rar文件解压缩失败解压末端出现错误的解决方法
  16. 文件被认为是病毒并删除解决办法
  17. 怎么看java是不是1.8_jdk从1.8换成1.7后,查看版本还是1.8解决方法
  18. android浏览器和iPhone浏览器
  19. Web Of Science检索页面错误信息修改申请方法
  20. openGauss亮相ICDE2021,分享数据库的AI晋级之路

热门文章

  1. Java平台,标准版Oracle JDK 9中的新功能
  2. android x86 5.1 微信,微信5.1终于来了!微信5.1安卓版正式发布
  3. 聚观早报 | 网传每日优鲜「就地解散」;支付宝一键取消自动扣款
  4. 十分详细的阳光十六法则 (转)
  5. agc024F Simple Subsequence Problem
  6. 为大地增添一抹绿色植树节公益活动
  7. 【杂】国内游戏创作大赛汇总(望补充)
  8. 刷单会入刑了你知道吗?四招教你迅速识别刷单!
  9. 模电—初探MOSFET
  10. python安卓库拍照_Python Faker库的实战,用Faker库生成伪造的安卓通讯录