ZED Board从入门到精通系列(八)——Vivado HLS实现矩阵相乘

2024-01-07 10:38

本文主要是介绍ZED Board从入门到精通系列(八)——Vivado HLS实现矩阵相乘,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

转载地址:http://blog.csdn.net/kkk584520/article/details/18812321

HLS的学习资源可以参考http://xilinx.eetrend.com/article/5096。本节给出较为通用的矩阵与向量相乘例子,从全串行到全并行进行了一步步优化实现。

矩阵实验室Matlab是比较常用的数学仿真软件。本博主用的是R2013a版本。为了验证矩阵向量相乘正确性,我们先用matlab生成测试矩阵和向量,并利用matlab计算结果。代码如下:

[plain]  view plain copy
print ? 在CODE上查看代码片 派生到我的代码片
  1. clear;  
  2. clc;  
  3. close all;  
  4.   
  5. N = 5;  
  6.   
  7. A = randi([1,100],N,N);  
  8. b = randi(100,N,1);  
  9.   
  10. c = A*b;  
  11.   
  12. KKK_SaveToCHeaderFile(A,'A.h');  
  13.   
  14. KKK_SaveToCHeaderFile(b,'b.h');  
  15. KKK_SaveToCHeaderFile(c,'c.h');  

这里给出的是A*b = c的简单例子,A为5X5矩阵,b为5X1向量,结果c为5X1向量。其中KKK_SaveToCHeaderFile()是将矩阵、向量保存为C语言数组的子函数,定义如下:

[plain]  view plain copy
print ? 在CODE上查看代码片 派生到我的代码片
  1. function [] = KKK_SaveToCHeaderFile(var,fn)  
  2. fid = fopen(fn,'w');  
  3. var = reshape(var.',1,[]);  
  4. fprintf(fid,'%d,\r\n',var);  
  5. fclose(fid);  


给出测试例程中,A如下:

[plain]  view plain copy
print ? 在CODE上查看代码片 派生到我的代码片
  1. 82  10  16  15  66  
  2. 91  28  98  43  4  
  3. 13  55  96  92  85  
  4. 92  96  49  80  94  
  5. 64  97  81  96  68  


b如下:

[plain]  view plain copy
print ? 在CODE上查看代码片 派生到我的代码片
  1. 76  
  2. 75  
  3. 40  
  4. 66  
  5. 18  


得到的c如下:

9800
15846
16555
23124
22939

运行matlab脚本之后,生成三个文件:A.h,b.h,c.h,这些是作为HLS程序的输入数据和参考结果。下面我们用HLS工具实现上述矩阵X向量的功能。第一步,运行Vivado HLS。

选择第一项,Create New Project,建立新工程MatrixMultiply

 

输入路径和工程名之后,点Next。

添加顶层模块文件。这里我们Top Functions输入MatrixMultiply,然后New File...,新建一个.c文件,命名为MatrixMultiply.c(后缀不要省略!),然后点Next

添加顶层文件测试脚本。这里New一个文件TestMatrixMultiply.c(后缀不要省略!),然后Add前面用Matlab生成的A.h,b.h,c.h,如下图所示:

点Next,选择解决方案配置,如下图所示

其余保持默认,只修改Part Selection部分,改为ZedBoard。改完后,Finish即可进入主界面,如下图所示

 

可以看出,Vivado HLS界面很像很像Xilinx SDK,不同的是前者负责PL部分开发,后者负责PS软件编写,定位不同决定了二者今后的路必然走向分歧。

 

将MatrixMultiply.c内容改为:

 

[cpp]  view plain copy
print ? 在CODE上查看代码片 派生到我的代码片
  1. typedef int data_type;  
  2. #define N 5  
  3.   
  4. void MatrixMultiply(data_type AA[N*N],data_type bb[N],data_type cc[N])  
  5. {  
  6.     int i,j;  
  7.     for(i = 0;i<N;i++)  
  8.     {  
  9.         data_type sum = 0;  
  10.         for(j = 0;j<N;j++)  
  11.         {  
  12.             sum += AA[i*N+j]*bb[j];  
  13.         }  
  14.         cc[i] = sum;  
  15.     }  
  16. }  


 

将TestMatrixMultiply.c内容改为:

[cpp]  view plain copy
print ? 在CODE上查看代码片 派生到我的代码片
  1. <p>#include <stdio.h>  
  2. typedef int data_type;  
  3. #define N 5</p><p>const data_type MatrixA[] = {  
  4. #include "A.h"  
  5. };  
  6. const data_type Vector_b[] = {  
  7. #include "b.h"  
  8. };  
  9. const data_type MatlabResult_c[] = {  
  10. #include "c.h"  
  11. };</p><p>data_type HLS_Result_c[N] = {0};  
  12. void CheckResult(data_type * matlab_result,data_type * your_result);  
  13. int main(void)  
  14. {  
  15.  printf("Checking Results:\r\n");  
  16.  MatrixMultiply(MatrixA,Vector_b,HLS_Result_c);  
  17.  CheckResult(MatlabResult_c,HLS_Result_c);  
  18.  return 0;  
  19. }  
  20. void CheckResult(data_type * matlab_result,data_type * your_result)  
  21. {  
  22.  int i;  
  23.  for(i = 0;i<N;i++)  
  24.  {  
  25.   printf("Idx %d: Error = %d \r\n",i,matlab_result[i]-your_result[i]);  
  26.  }  
  27. }  
  28. </p>  


首先进行C语言仿真验证,点这个按钮:

结果如下:

从C仿真输出看到,仿真结果与matlab计算结果一致,说明我们编写的C程序MatrixMultiply是正确的。

 

接下来进行综合,按C仿真后面那个三角形按钮,得到结果如下:

注意到,计算延迟为186个时钟周期。这是未经过优化的版本,记为版本1。

为了提高FPGA并行计算性能,我们接下来对它进行优化。

 

打开MatrixMultiply.c,点Directives页面,可以看到我们可以优化的对象。

 

 

注意到矩阵和向量相乘是双层for循环结构。我们先展开最内层for循环,步骤如下:

 

右键点击最内侧循环,右键,然后Insert Directive...

弹出对话框如下,Directives选择UNROLL,OK即可,后面所有都保持默认。

 

再次综合后,结果如下

可见效果非常明显,延迟缩短到51个时钟周期。

用同样方法,展开外层循环,综合后结果如下:

计算延迟又降低了1/3!!!

可是代价呢?细心的你可能发现占用资源情况发生了较大变化,DSP48E1由最初的4个变为8个后来又成为76个!!!

FPGA设计中,延迟的降低,即速度提高,必然会导致面积的增大!

 

循环展开是优化的一个角度,另一个角度是从资源出发进行优化。我们打开Analysis视图,如下所示:

 

从分析视图可以看出各个模块的运行顺序,从而为优化提供更为明确的指引。我们发现AA_load导致了延迟,如果所有AA的值都能一次性并行取出,势必会加快计算效率!

回到Synthetic视图,为AA增加Directives:

选择Resources,再点Cores后面的方框,进入Vivado HLS core选择对话框

按上图进行选择。使用ROM是因为在计算矩阵和向量相乘时,AA为常数。确认。

仍然选择AA,增加Directives,如下图:

选择数组分解,mode选择完全complete,综合后结果如下图:

延迟进一步降低,已经降到11个时钟周期了!!!是否已经达到极限了呢???

答案是否定的。我们进入Analysis视图,看一下还有哪些地方可以优化的。经过对比发现bb也需要分解,于是按照上面的方法对bb进行资源优化,也用ROM-2P类型,也做全分解,再次综合,结果如下:

 

 

发现延迟进一步降低到8个时钟周期了!!!

老师,能不能再给力点?

可以的!!!!

我们进入分析视图,发现cc这个回写的步骤阻塞了整体流程,于是我们将cc也进行上述资源优化,只不过资源类型要变为RAM_2P,因为它是需要写入的。综合结果:

 

 

整体延迟已经降低到6个clk周期了!!!

再看Analysis视图:

 

延迟已经被压缩到极限了。。。。

 

老师,还能再给力点嘛?

答案是可以的!!!!

我们前面的所有运算都是基于整形数int,如果将数值精度降低,将大大节省资源。

注意现在DSP48E1需要100个!

看我们如何将资源再降下来。这就需要借助“任意精度”数据类型了。

 

HLS中除了C中定义的char,shrot,int,long,long long 之外,还有任意bit长度的int类型。我们将代码开头的data_type定义改为:

[cpp]  view plain copy
print ? 在CODE上查看代码片 派生到我的代码片
  1. #include <ap_cint.h>  
  2. typedef uint15 data_type;  


 

由于matlab生成的随机数在1~100以内,乘积范围不会超过10000,于是取15bit就能满足要求。

 

首先验证下结果的正确性,用C Simulation试一下。结果如下:

 

看来结果是正确的(当然也不排除数位不够,溢出后的结果相减也是0,需要你自己决定数值位宽)

 

综合一下,结果如下:

 

延迟缩短了一半,DSP48E1减少到原来的1/4!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

 

我和我的小伙伴们都震惊了!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

 

再看Analysis视图

 

 

可以发现我们的资源利用率已经达到极致,时序已经压缩到无以复加,实现了全并行计算,系统时钟完全可以达到100MHz,延迟仅3CLK,约30ns,相比matlab,得到约数百倍加速(matlab进行矩阵——向量相乘时采用浮点计算)。

 

通过本文实验,可以发现利用Vivado HLS实现从最初的C串行实现到全并行实现的步步优化,总结一下优化步骤:

(1)粗优化(循环展开、子函数内联)

(2)访存优化(块存储分散化、多端口存取)

(3)精优化(数值位宽优化、流水线优化)

(4)总线化(利用AXI4、AXI-Stream总线接口,降低整体访存需求)

优化选项如下:

Directive                                      Description
ALLOCATION                      Specifya limit for the number of operations, cores or functions used.This can force the sharing or hardware
                                            resourcesand may increase latency
ARRAY_MAP                       Combinesmultiple smaller arrays into a single large array to helpreduce block RAM resources.
ARRAY_PARTITION            Partitionslarge arrays into multiple smaller arrays or into individualregisters, to improve access to data and remove                                                   block RAMbottlenecks.

ARRAY_RESHAPE              Reshapean array from one with many elements to one with greaterword-width. Useful for improving block RAM

                                           accesseswithout using more block RAM.

CLOCK                               For SystemC designsmultiple named clocks can be specified using the create_clock command and applied to individual                                            SC_MODULEs using this directive.

DATA_PACK                       Packs the data fields ofa struct into a single scalar with a wider word width.

DATAFLOW                        Enables task levelpipelining, allowing functions and loops to execute concurrently. Used to minimize interval.

DEPENDENCE                    Used to provide additional information that can overcome loop-carry dependencies and allow loops to be

                                             pipelined(or pipelined with lower intervals).

EXPRESSION_BALANCE     Allows automatic expressionbalancing to be turned off.
FUNCTION_INSTANTIATE   Allows different instances of the samefunction to be locally optimized.

INLINE                                   Inlines a function, removing all function hierarchy. Used to enable logic optimization across function boundaries and
                                              improve latency/interval by reducingfunction call overhead.

INTERFACE                          Specifieshow RTL ports are created from the function description.
LATENCY                              Allows aminimum and maximum latency constraint to be specified.
LOOP_FLATTEN                   Allows nested loops to be collapsed into a single loop with improved latency.

LOOP_MERGE                      Merge consecutive loops to reduce overalllatency, increase sharing and improve logic optimization.

LOOP_TRIPCOUNT              Used forloops which have variables bounds. Provides an estimatefor the loop iteration count. This has no impact                                                     on synthesis, only on reporting.

OCCURRENCE                     Used whenpipelining functions or loops, to specify that the code in a location is executed at a lesser rate than the                                                     code inthe enclosing function or loop.

PIPELINE                               Reduces the initiationinterval by allowing the concurrent execution of operations within a loop or function.

PROTOCOL                        This commands specifiesa region of the code to be a protocol region. A protocol region can be used to manually                                                       specify an interfaceprotocol.

RESET                                This directive isused to add or remove reset on a specific state variable (global or static).

RESOURCE                        Specify that a specificlibrary resource (core) is used to implement a variable (array, arithmetic operation or
                                            function argument) in the RTL.

STREAM                             Specifies that aspecific array is to be implemented as a FIFO or RAM memory channel during dataflow optimization.

TOP                                    The top-levelfunction for synthesis is specified in the project settings. This directive may be used to specify any
                                           function as the top-level for synthesis. This then allows different solutions within the same project to be specified as the                                            top-level function for synthesis without needing tocreate a new project.

UNROLL                          Unroll for-loops tocreate multiple independent operations rather than a single collection of operations.


利用HLS可以将原来的C算法快速部署到FPGA上,减少直接进行硬件编程的工作量。在很多情况下,优化手段可以和CUDA进行类比,相互借鉴。CUDA其实更接近软件接口,而HLS更接近硬件编程接口,或许今后两者会在新的层次上融合为统一架构语言。


这篇关于ZED Board从入门到精通系列(八)——Vivado HLS实现矩阵相乘的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/579638

相关文章

Python实现精准提取 PDF中的文本,表格与图片

《Python实现精准提取PDF中的文本,表格与图片》在实际的系统开发中,处理PDF文件不仅限于读取整页文本,还有提取文档中的表格数据,图片或特定区域的内容,下面我们来看看如何使用Python实... 目录安装 python 库提取 PDF 文本内容:获取整页文本与指定区域内容获取页面上的所有文本内容获取

基于Python实现一个Windows Tree命令工具

《基于Python实现一个WindowsTree命令工具》今天想要在Windows平台的CMD命令终端窗口中使用像Linux下的tree命令,打印一下目录结构层级树,然而还真有tree命令,但是发现... 目录引言实现代码使用说明可用选项示例用法功能特点添加到环境变量方法一:创建批处理文件并添加到PATH1

Java使用HttpClient实现图片下载与本地保存功能

《Java使用HttpClient实现图片下载与本地保存功能》在当今数字化时代,网络资源的获取与处理已成为软件开发中的常见需求,其中,图片作为网络上最常见的资源之一,其下载与保存功能在许多应用场景中都... 目录引言一、Apache HttpClient简介二、技术栈与环境准备三、实现图片下载与保存功能1.

canal实现mysql数据同步的详细过程

《canal实现mysql数据同步的详细过程》:本文主要介绍canal实现mysql数据同步的详细过程,本文通过实例图文相结合给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的... 目录1、canal下载2、mysql同步用户创建和授权3、canal admin安装和启动4、canal

Nexus安装和启动的实现教程

《Nexus安装和启动的实现教程》:本文主要介绍Nexus安装和启动的实现教程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、Nexus下载二、Nexus安装和启动三、关闭Nexus总结一、Nexus下载官方下载链接:DownloadWindows系统根

SpringBoot集成LiteFlow实现轻量级工作流引擎的详细过程

《SpringBoot集成LiteFlow实现轻量级工作流引擎的详细过程》LiteFlow是一款专注于逻辑驱动流程编排的轻量级框架,它以组件化方式快速构建和执行业务流程,有效解耦复杂业务逻辑,下面给大... 目录一、基础概念1.1 组件(Component)1.2 规则(Rule)1.3 上下文(Conte

MySQL 横向衍生表(Lateral Derived Tables)的实现

《MySQL横向衍生表(LateralDerivedTables)的实现》横向衍生表适用于在需要通过子查询获取中间结果集的场景,相对于普通衍生表,横向衍生表可以引用在其之前出现过的表名,本文就来... 目录一、横向衍生表用法示例1.1 用法示例1.2 使用建议前面我们介绍过mysql中的衍生表(From子句

Mybatis的分页实现方式

《Mybatis的分页实现方式》MyBatis的分页实现方式主要有以下几种,每种方式适用于不同的场景,且在性能、灵活性和代码侵入性上有所差异,对Mybatis的分页实现方式感兴趣的朋友一起看看吧... 目录​1. 原生 SQL 分页(物理分页)​​2. RowBounds 分页(逻辑分页)​​3. Page

Python基于微信OCR引擎实现高效图片文字识别

《Python基于微信OCR引擎实现高效图片文字识别》这篇文章主要为大家详细介绍了一款基于微信OCR引擎的图片文字识别桌面应用开发全过程,可以实现从图片拖拽识别到文字提取,感兴趣的小伙伴可以跟随小编一... 目录一、项目概述1.1 开发背景1.2 技术选型1.3 核心优势二、功能详解2.1 核心功能模块2.

MYSQL查询结果实现发送给客户端

《MYSQL查询结果实现发送给客户端》:本文主要介绍MYSQL查询结果实现发送给客户端方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录mysql取数据和发数据的流程(边读边发)Sending to clientSending DataLRU(Least Rec