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实现终端清屏的几种方式详解

《Python实现终端清屏的几种方式详解》在使用Python进行终端交互式编程时,我们经常需要清空当前终端屏幕的内容,本文为大家整理了几种常见的实现方法,有需要的小伙伴可以参考下... 目录方法一:使用 `os` 模块调用系统命令方法二:使用 `subprocess` 模块执行命令方法三:打印多个换行符模拟

SpringBoot+EasyPOI轻松实现Excel和Word导出PDF

《SpringBoot+EasyPOI轻松实现Excel和Word导出PDF》在企业级开发中,将Excel和Word文档导出为PDF是常见需求,本文将结合​​EasyPOI和​​Aspose系列工具实... 目录一、环境准备与依赖配置1.1 方案选型1.2 依赖配置(商业库方案)二、Excel 导出 PDF

Python实现MQTT通信的示例代码

《Python实现MQTT通信的示例代码》本文主要介绍了Python实现MQTT通信的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录1. 安装paho-mqtt库‌2. 搭建MQTT代理服务器(Broker)‌‌3. pytho

使用zip4j实现Java中的ZIP文件加密压缩的操作方法

《使用zip4j实现Java中的ZIP文件加密压缩的操作方法》本文介绍如何通过Maven集成zip4j1.3.2库创建带密码保护的ZIP文件,涵盖依赖配置、代码示例及加密原理,确保数据安全性,感兴趣的... 目录1. zip4j库介绍和版本1.1 zip4j库概述1.2 zip4j的版本演变1.3 zip4

从入门到进阶讲解Python自动化Playwright实战指南

《从入门到进阶讲解Python自动化Playwright实战指南》Playwright是针对Python语言的纯自动化工具,它可以通过单个API自动执行Chromium,Firefox和WebKit... 目录Playwright 简介核心优势安装步骤观点与案例结合Playwright 核心功能从零开始学习

python生成随机唯一id的几种实现方法

《python生成随机唯一id的几种实现方法》在Python中生成随机唯一ID有多种方法,根据不同的需求场景可以选择最适合的方案,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习... 目录方法 1:使用 UUID 模块(推荐)方法 2:使用 Secrets 模块(安全敏感场景)方法

Spring StateMachine实现状态机使用示例详解

《SpringStateMachine实现状态机使用示例详解》本文介绍SpringStateMachine实现状态机的步骤,包括依赖导入、枚举定义、状态转移规则配置、上下文管理及服务调用示例,重点解... 目录什么是状态机使用示例什么是状态机状态机是计算机科学中的​​核心建模工具​​,用于描述对象在其生命

Spring Boot 结合 WxJava 实现文章上传微信公众号草稿箱与群发

《SpringBoot结合WxJava实现文章上传微信公众号草稿箱与群发》本文将详细介绍如何使用SpringBoot框架结合WxJava开发工具包,实现文章上传到微信公众号草稿箱以及群发功能,... 目录一、项目环境准备1.1 开发环境1.2 微信公众号准备二、Spring Boot 项目搭建2.1 创建

IntelliJ IDEA2025创建SpringBoot项目的实现步骤

《IntelliJIDEA2025创建SpringBoot项目的实现步骤》本文主要介绍了IntelliJIDEA2025创建SpringBoot项目的实现步骤,文中通过示例代码介绍的非常详细,对大家... 目录一、创建 Spring Boot 项目1. 新建项目2. 基础配置3. 选择依赖4. 生成项目5.

Linux下删除乱码文件和目录的实现方式

《Linux下删除乱码文件和目录的实现方式》:本文主要介绍Linux下删除乱码文件和目录的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux下删除乱码文件和目录方法1方法2总结Linux下删除乱码文件和目录方法1使用ls -i命令找到文件或目录