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

相关文章

SpringBoot集成redisson实现延时队列教程

《SpringBoot集成redisson实现延时队列教程》文章介绍了使用Redisson实现延迟队列的完整步骤,包括依赖导入、Redis配置、工具类封装、业务枚举定义、执行器实现、Bean创建、消费... 目录1、先给项目导入Redisson依赖2、配置redis3、创建 RedissonConfig 配

Python的Darts库实现时间序列预测

《Python的Darts库实现时间序列预测》Darts一个集统计、机器学习与深度学习模型于一体的Python时间序列预测库,本文主要介绍了Python的Darts库实现时间序列预测,感兴趣的可以了解... 目录目录一、什么是 Darts?二、安装与基本配置安装 Darts导入基础模块三、时间序列数据结构与

Python使用FastAPI实现大文件分片上传与断点续传功能

《Python使用FastAPI实现大文件分片上传与断点续传功能》大文件直传常遇到超时、网络抖动失败、失败后只能重传的问题,分片上传+断点续传可以把大文件拆成若干小块逐个上传,并在中断后从已完成分片继... 目录一、接口设计二、服务端实现(FastAPI)2.1 运行环境2.2 目录结构建议2.3 serv

C#实现千万数据秒级导入的代码

《C#实现千万数据秒级导入的代码》在实际开发中excel导入很常见,现代社会中很容易遇到大数据处理业务,所以本文我就给大家分享一下千万数据秒级导入怎么实现,文中有详细的代码示例供大家参考,需要的朋友可... 目录前言一、数据存储二、处理逻辑优化前代码处理逻辑优化后的代码总结前言在实际开发中excel导入很

SpringBoot+RustFS 实现文件切片极速上传的实例代码

《SpringBoot+RustFS实现文件切片极速上传的实例代码》本文介绍利用SpringBoot和RustFS构建高性能文件切片上传系统,实现大文件秒传、断点续传和分片上传等功能,具有一定的参考... 目录一、为什么选择 RustFS + SpringBoot?二、环境准备与部署2.1 安装 RustF

Nginx部署HTTP/3的实现步骤

《Nginx部署HTTP/3的实现步骤》本文介绍了在Nginx中部署HTTP/3的详细步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学... 目录前提条件第一步:安装必要的依赖库第二步:获取并构建 BoringSSL第三步:获取 Nginx

MyBatis Plus实现时间字段自动填充的完整方案

《MyBatisPlus实现时间字段自动填充的完整方案》在日常开发中,我们经常需要记录数据的创建时间和更新时间,传统的做法是在每次插入或更新操作时手动设置这些时间字段,这种方式不仅繁琐,还容易遗漏,... 目录前言解决目标技术栈实现步骤1. 实体类注解配置2. 创建元数据处理器3. 服务层代码优化填充机制详

Python实现Excel批量样式修改器(附完整代码)

《Python实现Excel批量样式修改器(附完整代码)》这篇文章主要为大家详细介绍了如何使用Python实现一个Excel批量样式修改器,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一... 目录前言功能特性核心功能界面特性系统要求安装说明使用指南基本操作流程高级功能技术实现核心技术栈关键函

Java实现字节字符转bcd编码

《Java实现字节字符转bcd编码》BCD是一种将十进制数字编码为二进制的表示方式,常用于数字显示和存储,本文将介绍如何在Java中实现字节字符转BCD码的过程,需要的小伙伴可以了解下... 目录前言BCD码是什么Java实现字节转bcd编码方法补充总结前言BCD码(Binary-Coded Decima

SpringBoot全局域名替换的实现

《SpringBoot全局域名替换的实现》本文主要介绍了SpringBoot全局域名替换的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录 项目结构⚙️ 配置文件application.yml️ 配置类AppProperties.Ja