Vitis HLS 学习笔记--scal 函数-探究

ops/2024/9/23 20:02:32/

目录

HLS%E9%87%8D%E5%99%A8-Vitis_Libraries-toc" style="margin-left:0px;">1. Vitis HLS重器-Vitis_Libraries

2. 初识scal()

3. 函数具体实现

3.1 变量命名规则

3.2 t_ParEntries解释

3.3 流类型详解

3.4 双重循环

4. 总结


1. Vitis HLS重器-Vitis_Libraries

在深入探索Vitis HLS(High-Level Synthesis)的旅程中,我们不得不提一个至关重要的里程碑——那就是熟练运用Vitis Libraries。这个库集合了众多领域内关键的函数,并提供了它们的硬件实现版本。这对于那些希望将软件算法高效转换为硬件描述的开发者来说,无疑是一大福音。以BLAS(Basic Linear Algebra Subprograms)库中的scale函数为例,我们可以看到这些库的实用性和强大功能。

scale函数的作用听起来非常直接:计算Y = alpha * X,其中alpha是一个标量,X是一个向量,这个操作将X中的每个元素乘以alpha,得到新的向量Y。乍一看,scale函数的功能似乎异常简单,直白。然而,当我们深入其函数参数时,事情开始变得有些复杂:

template  <typename t_DataType, unsigned int t_ParEntries, typename t_IndexType = unsigned int>
void scal(unsigned int p_n,t_DataType p_alpha,hls::stream<WideType<t_DataType, t_ParEntries>>& p_x,hls::stream<WideType<t_DataType, t_ParEntries>>& p_res)

这段代码中充满了模板参数、数据类型和流对象,令初看之下显得颇为复杂。对于初学者来说,这些参数和类型定义可能会让人感到困惑,特别是对于那些刚接触硬件设计领域的学生或工程师而言。但别担心,今天我们就来一步步解析这些参数,揭开scal函数背后的神秘面纱。

2. 初识scal()

  • t_DataType指的是数据类型,这意味着scal函数能够支持不同的数据类型操作,增加了函数的通用性。
  • t_ParEntries定义了每次操作可以处理的数据量,这直接关联到了算法的并行度和处理效率。
  • t_IndexType通常用作索引的数据类型,默认为unsigned int,这提供了足够的灵活性来适应不同大小的数据集。
  • p_n参数指定了向量X中元素的数量
  • p_alpha是乘法因子alpha。
  • p_x和p_res分别是输入和输出数据的流对象,它们使用了WideType模板,这是一种封装了并行数据条目的类型,允许数据以流的形式进行高效处理。

通过这样的设计,scal函数不仅仅是一个简单的比例计算函数,而是一个高度优化且可定制的并行数据处理单元,能够在硬件级别上实现高效的线性代数运算。这种深度优化的实现方式,虽然在一开始可能让人望而生畏,但一旦掌握,便能大幅提升数据处理任务的性能。

3. 函数具体实现

接下来,我们详细研究每一处细节。函数实现如下:

template <typename t_DataType, unsigned int t_ParEntries, typename t_IndexType = unsigned int>
void scal(unsigned int p_n,t_DataType p_alpha,hls::stream<typename WideType<t_DataType, t_ParEntries>::t_TypeInt>& p_x,hls::stream<typename WideType<t_DataType, t_ParEntries>::t_TypeInt>& p_res) {
#ifndef __SYNTHESIS__assert((p_n % t_ParEntries) == 0);
#endifconst unsigned int l_parEntries = p_n / t_ParEntries;for (t_IndexType i = 0; i < l_parEntries; ++i) {
#pragma HLS PIPELINEWideType<t_DataType, t_ParEntries> l_valX;WideType<t_DataType, t_ParEntries> l_valY;l_valX = p_x.read();for (unsigned int j = 0; j < t_ParEntries; ++j) {l_valY[j] = p_alpha * l_valX[j];}p_res.write(l_valY);}
}

3.1 变量命名规则

  • t_DataType,t_前缀表示template,模板参数
  • p_n,p_前缀表示parameter,函数参数
  • l_abs,l_前缀表示local,函数内部变量(局部变量)

3.2 t_ParEntries解释

const unsigned int l_parEntries = p_n / t_ParEntries;

用途:计算在给定并行度 t_ParEntries 下,需要进行多少次迭代来处理整个输入向量。

  • p_n 是输入向量 X 的元素总数。
  • t_ParEntries 是每次可以并行处理的元素数。
  • 注意p_n / t_ParEntries必须能被整除。

因此,l_parEntries 是迭代的总次数,即必须执行多少次循环迭代来处理整个向量 X,使每个元素都乘以 alpha。

图示说明:

如果向量 p_n 包含9个元素(即 p_n=9),并且设定并行度 t_ParEntries=3,则最多可以有3个并行执行的流水线同时进行计算,通过三次迭代就可以完成整个向量的运算。换句话说,每次迭代处理3个元素,总共需要3次迭代来覆盖所有9个元素。

也可以设置 t_ParEntries=9,这样一来,整个向量的乘法运算可以在单次迭代中完成。

加入迭代间隔(II)为1,这意味着在一个周期内就可以完成所有9个元素的乘法运算。当然,这种配置会消耗更多的硬件资源,因为它需要在同一时刻支持更多的并行乘法操作。

这种权衡是在硬件资源消耗与运算速度之间进行的。选择更高的并行度可以减少所需的总迭代次数,从而加快运算速度,但代价是需要更多的硬件资源来实现这种高并行度。相反,较低的并行度虽然硬件资源消耗更少,但需要更多的迭代次数来完成相同的计算,可能导致整体性能降低。

3.3 流类型详解

hls::stream<typename WideType<t_DataType, t_ParEntries>::t_TypeInt>& p_x

hls::stream<typename WideType<t_DataType, t_ParEntries>::t_TypeInt>& p_res

关键字typename的作用:在模板编程中,编译器并不能自动推断出所有的名字是否代表类型。特别是当类型依赖于模板参数时,这里t_TypeInt就是一个依赖模板参数的类型,我们也称其为依赖类型。typename为了告诉编译器t_TypeInt是一个类型。

进一步我们调查t_TypeInt的定义:

template <typename T, unsigned int t_Width, unsigned int t_DataWidth = sizeof(T) * 8, typename Enable = void>
class WideType {private:…public:static const unsigned int t_TypeWidth = t_Width * t_DataWidth;typedef ap_uint<t_TypeWidth> t_TypeInt;

可以看到,t_TypeInt只是一个别名,代指ap_uint<t_TypeWidth>;

绕了半天,发现p_x,p_res就是ap_uint<m>的hls::stream!

3.4 双重循环

for (t_IndexType i = 0; i < l_parEntries; ++i) {
#pragma HLS PIPELINEWideType<t_DataType, t_ParEntries> l_valX;WideType<t_DataType, t_ParEntries> l_valY;l_valX = p_x.read();for (unsigned int j = 0; j < t_ParEntries; ++j) {l_valY[j] = p_alpha * l_valX[j];}p_res.write(l_valY);
}

这段代码是实现向量缩放操作(即 Y = alpha * X)的核心部分。

内层循环对于硬件实现来说非常关键,因为它被设计为可以完全展开并并行执行。

当外层循环使用了 #pragma HLS PIPELINE 指令时,即使没有显式地使用 #pragma HLS UNROLL 指令,内层循环也可能会默认展开。

l_valX、l_valY变量是Vitis HLS推荐使用的方法。

l_valX = p_x.read()和p_res.write(l_valY)也是hls::stream操作fifo的方法。

4. 总结

虽然Vitis Libraries中的函数在一开始看起来可能令人困惑,但它们提供了强大的工具集,用于构建高效的硬件加速应用程序。通过深入学习和实践,我们可以逐渐解锁这些库的潜力,为自己的项目带来前所未有的性能提升。


http://www.ppmy.cn/ops/8553.html

相关文章

Python机器学习算法库scikit-learn学习之决策树实现方法

Scikit-learn 是一个功能强大的Python机器学习库&#xff0c;它提供了各种算法&#xff0c;包括决策树&#xff08;Decision Tree&#xff09;。决策树是一种直观的算法&#xff0c;用于分类和回归任务。以下是如何使用 scikit-learn 实现决策树的基本步骤&#xff1a; 1. 导入…

java实现将图片转Base64字符,Base64转图片

图片转base64 import java.io.*; import java.util.Base64;public class ImageToBase64Converter {public static void main(String[] args) {String imagePath "path/to/your/image.png"; // 替换为你的图片路径String outputFilePath "out.txt";try {…

Phpstorm环境配置与应用

PhpStorm是一款功能强大的PHP集成开发环境&#xff0c;配置与应用涉及以下步骤&#xff1a; 下载与安装&#xff1a; 访问 PhpStorm 官网下载地址&#xff0c;选择合适的版本进行下载。双击下载的安装包文件进行安装&#xff0c;过程类似于其他IntelliJ IDEA产品。 个性化设…

自定义Centos的终端的命令提示符

背景 当我们使用终端登陆Centos时&#xff0c;就自动打开了ssh终端。这个终端的命令提示符一般是这样的&#xff1a; 这个以#号结束的一行字&#xff0c;就是我们说的命令提示符了。 这个是腾讯云的服务器的提示符&#xff0c;可以看到主机名是VM-4-7-centos。 但是这个看起…

如何防止服务器被攻击

如何防止服务器被攻击 第1步&#xff1a;切断网络; 服务器的攻击来源都必须通过互联网&#xff0c;一旦切断网络&#xff0c;它们就失去了攻击的入口&#xff0c;你可以通过切断网络的方式&#xff0c;以最快的速度切断攻击源&#xff0c;保护服务器所在网络的其他主机服务器。…

Android集成Sentry实践

需求&#xff1a;之前使用的是tencent的bugly做为崩溃和异常监控&#xff0c;好像是要开始收费了&#xff0c;计划使用开源免费的sentry进行替换。 步骤&#xff1a; 1.修改工程文件 app/build.gradle apply plugin: io.sentry.android.gradle sentry {// 禁用或启用ProGua…

盘点50条Redis相关热门话题(一)

Redis在云计算中的应用实践&#xff0c;关键词&#xff1a;云计算&#xff0c;分布式缓存Redis在高并发场景下的性能优化技巧&#xff0c;关键词&#xff1a;高并发&#xff0c;性能优化Redis在微服务架构中的角色与应用&#xff0c;关键词&#xff1a;微服务&#xff0c;分布式…

力扣HOT100 - 24. 两两交换链表中的节点

解题思路&#xff1a; 递归 class Solution {public ListNode swapPairs(ListNode head) {if (head null || head.next null) {return head;}ListNode newHead head.next;head.next swapPairs(newHead.next);newHead.next head;return newHead;} }