Vitis HLS 学习笔记--抽象并行编程模型-控制驱动与数据驱动

news2025/1/13 15:32:19

目录

1. 简介

2. Takeaways

3. Data-driven Task-level Parallelism

3.1 simple_data_driven 示例

3.2 分析 hls::task 类

3.3 分析通道(Channel)

3.4 注意死锁

4. Control-driven Task-level Parallelism

4.1 理解控制驱动的 TLP

4.2 simple_control_driven 示例

4.3 分析示例

4.4 DATAFLOW 规范形式

4.5 配置 DATAFLOW 的通道

5. 总结


1. 简介

当我们谈论任务级并行度(TLP)时,我们实际上是在讨论如何在应用程序中同时执行多个任务,以提高效率和性能。这就像是一个厨师在厨房里同时烹饪多道菜,而不是一道一道地做,这样可以更快地准备一顿大餐。

Vitis HLS 提供了两种典型的 TLP 模型:控制驱动的任务和数据驱动的任务。

数据驱动的模型就像是一个自动化的流水线,它不断地处理数据,不需要外部的指令或干预。只要有数据输入,它就会工作。这适用于那些不需要与外部存储器交互,且各个函数之间没有数据依赖关系的应用。简单来说,如果你的程序是一系列独立的步骤,每个步骤处理的数据都不会影响其他步骤,那么这种模型就很合适。

控制驱动的模型更像是有一个指挥官,根据需要调度任务和管理数据。如果你的应用程序的不同部分需要相互通信,或者一个任务的输出是另一个任务的输入,那么你就需要这种模型。Vitis HLS是一个工具,它可以帮助确定哪些任务可以同时进行,以及如何最有效地安排它们。

2. Takeaways

如果 HLS 设计为纯数据驱动的设计,且无需与软件应用进行任何交互,那么可以使用数据驱动的 TLP 模型来建模。

此类设计的典型应用场景有:

  • 基于简单规则的“防火墙”,且“规则”编译到内核中。
  • 快速傅里叶变换,且配置数据编译到内核中。
  • FIR 滤波器,且系数编译到内核中。

如果设计需要往来外部存储器执行数据传输,那么可使用控制驱动的 TLP 模型。此类设计的示例有:

  • 网络路由器,其中路由表必须完全更新后才能执行内核。
  • 使用散列映射向服务器发送数据的负载均衡器,这类负载均衡器必须同时更新服务器列表、服务器映射以及对应的 IP 地址。

大部分设计将混用控制驱动与数据驱动的模型,需要对外部存储器进行部分访问,支持 HLS 内的并行任务与流水打拍任务之间的串流。

  • 本文探讨的是函数级建模的方法(任务通道最优化或数据流最优化)。
  • 达成良好的吞吐量的另一个关键要素是,指令级并行度
  • 指令级并行度指的是在循环、函数甚至阵列内部有效执行并行运算的能力。

3. Data-driven Task-level Parallelism

3.1 simple_data_driven 示例

#include "hls_task.h"

void splitter(hls::stream<int>& in, hls::stream<int>& odds_buf,
              hls::stream<int>& evens_buf) {
    int data = in.read();
    if (data % 2 == 0)
        evens_buf.write(data);
    else
        odds_buf.write(data);
}

void odds(hls::stream<int>& in, hls::stream<int>& out) {
    out.write(in.read() + 1);
}

void evens(hls::stream<int>& in, hls::stream<int>& out) {
    out.write(in.read() + 2);
}

void odds_and_evens(hls::stream<int>& in, hls::stream<int>& out1,
                    hls::stream<int>& out2) {
    hls_thread_local hls::stream<int, 5> s1; // channel connecting t1 and t2
    hls_thread_local hls::stream<int, 5> s2; // channel connecting t1 and t3

    // t1 infinitely runs func1, with input in and outputs s1 and s2
    hls_thread_local hls::task t1(splitter, in, s1, s2);

    // t2 infinitely runs func2, with input s1 and output out1
    hls_thread_local hls::task t2(odds, s1, out1);

    // t3 infinitely runs func3, with input s2 and output out2
    hls_thread_local hls::task t3(evens, s2, out2);
}

3.2 分析 hls::task 类

对象声明:

  • 在源代码中,hls::task 用于声明新对象,并配合 hls_thread_local 限定符使用。
  • 这个限定符的关键作用是确保在函数(如 odds_and_evens)的多次实例化调用中,对象及其底层线程保持活跃状态。
  • 在数据驱动模型中,无论是 C 语言仿真还是 RTL 仿真,hls_thread_local 都保证了一致的行为。
  • 在 RTL 中,函数启动后将持续运行。
  • 为了在 C 语言仿真中复现相同的行为,hls_thread_local 确保每个任务只启动一次,并且在多次调用中维持相同状态。

任务对象:

  • 任务对象负责隐式地管理持续运行的函数线程。
  • 向这些对象传递参数时,只能使用 hls::stream 或 hls::stream_of_blocks 类型。

系统不支持传递其他类型的参数。例如,不能直接将 even 这样的常量值作为函数的参数。如果需要在任务执行过程中使用常量,建议的做法是将相关函数设计为模板函数,并以模板参数的形式传递常量值。

任务主体:

  • 提供的函数(例如示例中的 splitter/odds/evens)被称为任务主体。
  • 这些函数被隐式无限循环包围,以确保任务保持运行并等待输入。

流水线循环:

  • 提供的函数包含流水线循环。但是,为了防止死锁,需要将其设置为可刷新的流水线(FLP)。
  • 工具会自动选择适用于给定流水线函数或循环的正确流水线样式。

3.3 分析通道(Channel)

通道由特殊模板化的 hls::stream(或 hls::stream_of_blocks)C++ 类进行建模。

通道具有以下属性:

  • 在数据驱动的 TLP 模型中,hls::stream<type,depth> 对象的行为类似于具有指定深度的 FIFO。默认情况下,这些串流的深度为 2,但用户可以覆盖该值。
  • 对这些串流的读取和写入是按顺序执行的。一旦从 hls::stream<> 中读取数据项,就无法再次读取该数据项。
  • 串流可以在局部或全局范围内定义。全局作用域内定义的串流遵循与其他全局变量相同的规则。
  • 对于这些串流(例如示例中的 s1 和 s2),hls_thread_local 限定符也是必需的。它确保在实例化函数(例如示例中的 odds_and_evens)的多次调用之间,相同的串流保持活动状态。

3.4 注意死锁

读取空串流属于阻塞读取,可能引发死锁,以下情况需要注意:

  • 设计本身内部的进程的生产和耗用率不平衡,发生死锁。
  • 测试激励文件提供的数据太少,不足以生成该测试激励文件在检查计算结果时所需的所有输出,发生死锁。

在 C 语言仿真期间:某个进程周期或者从顶层输入启动的进程链尝试读取空的通道,会导致死锁。

在 C/RTL 协同仿真期间以及在硬件 (HW) 中运行时:写入已满的通道或者读取空的通道,会导致死锁。

当设计包含 hls::task 时,Vitis HLS 工具会自动例化死锁检测器,检测到死锁后停止 C 语言仿真。使用 Vitis HLS GUI 可观察仿真的 hls::tasks 尝试读取空的通道时,所有发生阻塞的具体位置。

4. Control-driven Task-level Parallelism

4.1 理解控制驱动的 TLP

控制驱动的任务级并行(TLP) 是一种用于建模并行性的方法,它依赖于 C++ 中的顺序语义,而不是连续运行的线程。这种模型适用于以下情况:

  • 按并发流水方式执行的函数:例如,在循环内部可以同时执行多个函数。
  • 搭配实参执行的函数:这些实参不是通道,而是 C++ 中的标量或数组变量。这两种方式都适用于本地存储器和片外DDR存储器

Vitis HLS 在保留原始 C++ 顺序执行行为的同时,引入了并行度的概念,具有以下特点:

  • 后续函数可以在前一个函数完成之前启动。
  • 函数可以在完成之前重新启动。
  • 可以同时启动两个或两个以上的顺序函数。

控制驱动的 TLP ,也称数据流模型,它使用一系列顺序执行的函数来创建一个并行处理的流水线结构。这个结构允许多个任务同时进行:

任务级流水线架构:就像一个工厂流水线上的不同工位同时工作一样,数据流模型允许多个函数(任务)同时执行,每个函数处理不同的数据部分。

推断并行任务和通道:工具会自动识别哪些任务可以并行执行,并建立它们之间的通信通道。

DATAFLOW 指令:设计人员通过这些指令告诉工具哪些区域(函数体或循环主体)应该以数据流的方式来处理。

通道类型:设计人员可以选择不同类型的通道,比如 FIFO(hls::stream 或 #pragma HLS STREAM)或 PIPO(hls::stream_of_blocks),这些通道决定了数据的传输方式。

4.2 simple_control_driven 示例

#include <hls_stream.h>
#include <hls_vector.h>

// Each vector will be 64 bytes (16 x 4 bytes)
typedef hls::vector<uint32_t, NUM_WORDS> vecOf16Words;
typedef unsigned int data_t;

extern "C" {

void diamond(vecOf16Words* vecIn, vecOf16Words* vecOut, int size) {
// The depth setting is required for pointer to array in the interface.
#pragma HLS INTERFACE m_axi port = vecIn depth = 32
#pragma HLS INTERFACE m_axi port = vecOut depth = 32

    hls::stream<vecOf16Words> c0, c1, c2, c3, c4, c5;
    assert(size % 16 == 0);

#pragma HLS dataflow
    load(vecIn, c0, size);
    compute_A(c0, c1, c2, size);
    compute_B(c1, c3, size);
    compute_C(c2, c4, size);
    compute_D(c3, c4, c5, size);
    store(c5, vecOut, size);
}
}

void load(vecOf16Words* in, hls::stream<vecOf16Words>& out, int size) {
Loop_Ld:
    for (int i = 0; i < size; i++) {
#pragma HLS performance target_ti = 32
#pragma HLS LOOP_TRIPCOUNT max = 32
        out.write(in[i]);
    }
}

void compute_A(hls::stream<vecOf16Words>& in, hls::stream<vecOf16Words>& out1,
               hls::stream<vecOf16Words>& out2, int size) {
Loop_A:
    for (int i = 0; i < size; i++) {
#pragma HLS performance target_ti = 32
#pragma HLS LOOP_TRIPCOUNT max = 32
        vecOf16Words t = in.read();
        out1.write(t * 3);
        out2.write(t * 3);
    }
}

void compute_B(hls::stream<vecOf16Words>& in, hls::stream<vecOf16Words>& out,
               int size) {
Loop_B:
    for (int i = 0; i < size; i++) {
#pragma HLS performance target_ti = 32
#pragma HLS LOOP_TRIPCOUNT max = 32
        out.write(in.read() + 25);
    }
}

void compute_C(hls::stream<vecOf16Words>& in, hls::stream<vecOf16Words>& out,
               int size) {
Loop_C:
    for (data_t i = 0; i < size; i++) {
#pragma HLS performance target_ti = 32
#pragma HLS LOOP_TRIPCOUNT max = 32
        out.write(in.read() * 2);
    }
}
void compute_D(hls::stream<vecOf16Words>& in1, hls::stream<vecOf16Words>& in2,
               hls::stream<vecOf16Words>& out, int size) {
Loop_D:
    for (data_t i = 0; i < size; i++) {
#pragma HLS performance target_ti = 32
#pragma HLS LOOP_TRIPCOUNT max = 32
        out.write(in1.read() + in2.read());
    }
}

void store(hls::stream<vecOf16Words>& in, vecOf16Words* out, int size) {
Loop_St:
    for (int i = 0; i < size; i++) {
#pragma HLS performance target_ti = 32
#pragma HLS LOOP_TRIPCOUNT max = 32
        out[i] = in.read();
    }
}

4.3 分析示例

void diamond(data_t vecIn[100], data_t vecOut[100])
{
  data_t c1[100], c2[100], c3[100], c4[100];
#pragma HLS dataflow
  funcA(vecIn, c1, c2);
  funcB(c1, c3);
  funcC(c2, c4);
  funcD(c3, c4, vecOut);
}

在以上示例中有 4 个函数:funcA,funcB,funcC 和 funcD。funcB 与 funcC 之间不存在任何数据依赖关系,因此可以并行执行:

funcA 会从非本地存储器 (vecIn) 读取,需首先执行。同样,funcD 写入非本地存储器 (vecOut),因此最后执行。

简单回顾:

  • 通过 DATAFLOW 指令划定数据流区域。将某个特定区域(例如,函数体或循环主体)识别为要应用数据流模型的区域。
  • 创建各通道。HLS 工具解析区域内函数体或循环体,并基于 C++ 变量(例如,标量、阵列或用户定义的通道,如 hls::streams 或 hls::stream_of_blocks)创建各通道,这些变量用于对数据流区域内的数据流动进行建模。
  • 非标量变量。对于标量变量而言可能只是简单的 FIFO,而对于非标量变量,则可能是乒乓 (PIPO) 缓冲器,此类非标量变量有阵列、块串流(前提是需将 FIFO 和 PIPO 行为与块的显式锁定加以组合)等。

4.4 DATAFLOW 规范形式

为了增强数据流的可预测性,应遵循特定的代码编写规范。下面简明地概述了这些规范:

  • 函数规范:
    1. 使用#pragma HLS dataflow指令以启用数据流优化。
    2. 定义的子函数可作为数据流的一部分,但不包括变量初始化和表达式的值传递。
    3. 确保按照规范格式编写代码,以便Vitis HLS能够有效实现数据流。如有偏差,使用GUI的数据流查看器和协同仿真时间轨迹进行验证。

示例代码:

void dataflow(Input0, Input1, Output0, Output1)
{
#pragma HLS dataflow
    UserDataType C0, C1, C2; // UserDataType can be scalars or arrays
    func1(Input0, Input1, C0, C1); // read Input0, read Input1, write C0, write C1
    func2(C0, C1, C2); // read C0, read C1, write C2
    func3(C2, Output0, Output1); // read C2, write Output0, write Output1
}
  • 循环内的数据流:
    1. 循环应仅包含一个函数调用,无其他代码。
    2. 循环变量应从0开始,以1递增,且上限为非负数。
    3. 数据流指令应位于循环体内。

示例代码:

void dataflow(Input0, Input1, Output0, Output1)
{
    for (int i = 0; i < N; i++)
    {
    #pragma HLS dataflow
        UserDataType C0, C1, C2; // UserDataType can be scalars or arrays
        func1(Input0, Input1, C0, C1); // read Input0, read Input1, write C0, write C1
        func2(C0, C0, read C1, C2); // read C0, read C0, read C1, write C2
        func3(C2, Output0, Output1); // read C2, write Output0, write
        Output1
    }
}

4.5 配置 DATAFLOW 的通道

  • 对于标量,Vitis HLS 将自动推断 FIFO 作为通道类型。
  • 对于阵列,且始终顺序访问数据,PIPO/FIFO 均可作为通道。PIPO 从不发生死锁,但需要耗用更多存储器。FIFO 所需存储器较少,但存储深度配置不正确,则存在发生死锁的风险。
  • 对于阵列,且需任意顺序访问数据,只能 PIPO 来实现(默认大小是原始阵列的两倍)。
void top ( ... ) {
#pragma HLS dataflow

    int A[1024];
    #pragma HLS stream type=pipo variable=A depth=3

    producer(A, B, …); // producer writes A and B
    middle(B, C, ...); // middle reads B and writes C
    consumer(A, C, …); // consumer reads A and C
}

5. 总结

任务级并行度(TLP)通过同时执行多个任务提升应用效率和性能。Vitis HLS 提供了数据驱动和控制驱动两种 TLP 模型。数据驱动模型像自动化流水线,适用于无外部存储器交互且函数间无数据依赖的应用;控制驱动模型则更像一个指挥官,适合不同部分需相互通信的应用。数据驱动模型中,任务通过 hls::task 声明,通道通过 hls::stream 模拟,注意避免死锁。控制驱动模型使用 C++ 顺序语义创建并行处理流水线,任务级并行执行函数,需使用 DATAFLOW 编译指示。两者结合,实现高效并行计算。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

59 多次 mmap 虚拟地址的关系

前言 这是来自于网友的一篇帖子 然后 我们这里来探究一下这个问题 主要是 多次连续的 mmap 获取到的 虚拟地址区域 是否连续 以及 衍生出的一些其他的问题 从 mmap 的实现 我们可以知道, mmap 的空间是 自顶向下 分配的, 因此 两块空间应该是连续的, 第一块在上面, 第二块…

二百三十七、Hive——DWS层生成每个清洗字段的异常情况记录

一、目的 在Hive中对每种业务数据的清洗字段的异常数据进行记录 例如这张图&#xff0c;上面是原始数据&#xff0c;下面是每台雷达每天的异常字段的记录 二、实施步骤 &#xff08;一&#xff09;建表 create table if not exists dws_data_clean_record_queue(data_ty…

Go团队:Go是什么

2024年的Google I/O大会[1]如期而至。 这届大会的核心主旨毫无疑问是坚定不移的以AI为中心&#xff1a;Google先是发布了上下文长度将达到惊人的200万token的Gemini 1.5 Pro[2]&#xff0c;然后面对OpenAI GPT-4o的挑衅&#xff0c;谷歌在大会上直接甩出大杀器Project Astra[3]…

关于pytest中用例名称使用中文乱码的解决

场景&#xff1a;使用pytest.mark.parametrize装饰器为用例自定义名称时&#xff0c;运行显示乱码。如下图所示&#xff1a; 解决方案&#xff1a; 1.在根目录 pytest.ini中增加一行代码 [pytest] disable_test_id_escaping_and_forfeit_all_rights_to_community_supportTrue…

学习使用博客记录生活

学习使用博客记录生活 新的改变 今天新的开始&#xff0c;让我用图片开始记录吧 看这个背景图片怎么样

微信小程序实现容器图片流式布局功能,配合小程序原生框架使用。

小程序实现容器图片流式布局功能&#xff0c;因为目前论坛上也有很多博主出过类似的文章&#xff0c;这里我就以一个小白角度去讲一下如何实现的吧。给作者一点点鼓励&#xff0c;先点个赞赞吧&#x1f44d;&#xff0c;蟹蟹&#xff01;&#xff01; 目标 实现下方效果图 技术…

基金/证券项目如何进行非交易日数据补全(实战)

一些大数据开发的项目&#xff0c;特别是基金/证券公司的项目&#xff0c;都经常会涉及到交易日与非交易日的概念。 如果要让你对一张交易日跑批的主表&#xff0c;怎么去补全非交易日的数据呢&#xff1f; 在遇到这种情况的时候&#xff0c;我们要去怎么处理&#xff1f;来&…

模板编译之入口分析

Vue 是一个渐进式 JavaScript 框架&#xff0c;提供了简单易用的模板语法&#xff0c;帮助开发者以声明式的方式构建用户界面。Vue 的模板编译原理是其核心之一&#xff0c;它将模板字符串编译成渲染函数&#xff0c;并在运行时高效地更新 DOM。本文将深入探讨 Vue 模板编译的原…

图片分类模型训练及Web端可视化预测(下)——Web端实现可视化预测

Web端实现可视化预测 基于Flask搭建Web框架&#xff0c;实现HTML登录页面&#xff0c;编写图片上传并预测展示页面。后端实现上一篇文章所训练好的模型&#xff0c;进行前后端交互&#xff0c;选择一张图片&#xff0c;并将预测结果展示到页面上。 文章目录 Web端实现可视化预测…

前端基于word模板导出word文档

项目环境 vue2 js vue-cli等 依赖包都可以在npm官网找到对应文档 npm官网(英文) 1、依赖 安装依赖 docxtemplater npm i docxtemplaterfile-saver npm i file-saverjszip-utils npm i jszip-utilsjszip npm i jszip在对应页面或模块中引入依赖 import Docxtemplater …

探索AI写作工具:五款推荐

在现实生活中&#xff0c;除了专业的文字工作者&#xff0c;各行各业都避免不了需要写一些东西&#xff0c;比如策划案、论文、公文、讲话稿、总结计划……等等。而随着科技的进步&#xff0c;数字化时代的深入发展&#xff0c;AI已经成为日常工作中必不可少的工具了&#xff0…

智慧之选:开源与闭源大模型的未来探索

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

有一个3x4的矩阵,要求编写程序求出其中值最大的那个元素,以及其所在的行号和列号

解题思路&#xff1a; 先考虑解此问题的思路。从若干数中求最大数的方法很多&#xff0c;现在采用"打擂台"的算法。如果有若干人比武&#xff0c;先有一人站在台上&#xff0c;再上去一人与其交手&#xff0c;败者下台&#xff0c;胜者留在台上。第3个人再上…

selenium安装出错

selenium安装步骤&#xff08;法1&#xff09;&#xff1a; 安装失败法1 第一次实验&#xff0c;失败 又试了一次&#xff0c;失败 安装法2-失败&#xff1a; ERROR: Could not install packages due to an EnvironmentError: [WinError 5] 拒绝访问。: c:\\programdata\\a…

【C++题解】1697. 请输出n~1之间所有的整数

问题:1697. 请输出n~1之间所有的整数 类型&#xff1a;循环 题目描述&#xff1a; 从键盘读入一个整数 n &#xff0c;请输出 n∼1 之间所有的整数&#xff0c;每行输出 1 个。 比如&#xff0c;假设读入 n5 &#xff0c;输出结果如下&#xff1a; 5 4 3 2 1 输入&#xff1…

UE5 像素流与web 交互

总结下虚幻与网页的交互&#xff0c;这里将ue5 与js 交互传递参数记录下&#xff0c;其它的博主写的就是缺胳膊少腿的要么就是封闭收费&#xff0c;这个是在官方可以查询到。这里记录下&#xff1a; 点个关注不迷路&#xff1a; 具体的使用如下&#xff1a; 在你的游戏玩家类…

推荐几款新手学习编程的网站

免费在线开发平台 介绍一款编程平台&#xff0c;专为学生和开发者量身打造&#xff01;平台拥有近4000道编程题目&#xff0c;支持多种编程语言&#xff08;包括C、C、JavaScript、TypeScript、Go、Rust、PHP、Java、Ruby、Python3和C#&#xff09;&#xff0c;为您提供全面的学…

【Oracle篇】rman标准化全库备份策略:完整备份or增量备份(第三篇,总共八篇)

&#x1f4ab;《博主介绍》&#xff1a;✨又是一天没白过&#xff0c;我是奈斯&#xff0c;DBA一名✨ &#x1f4ab;《擅长领域》&#xff1a;✌️擅长Oracle、MySQL、SQLserver、阿里云AnalyticDB for MySQL(分布式数据仓库)、Linux&#xff0c;也在扩展大数据方向的知识面✌️…

YoloV8改进策略:蒸馏改进|CWDLoss|使用蒸馏模型实现YoloV8无损涨点|特征蒸馏

摘要 在本文中&#xff0c;我们成功应用蒸馏策略以实现YoloV8小模型的无损性能提升。我们采用了CWDLoss作为蒸馏方法的核心&#xff0c;通过对比在线和离线两种蒸馏方式&#xff0c;我们发现离线蒸馏在效果上更为出色。因此&#xff0c;为了方便广大读者和研究者应用&#xff…

Kubernetes Service 之原理与 ClusterIP 和 NodePort 用法

Kubernetes Service 之原理与 ClusterIP 和 NodePort 用法 Service 定义 在 Kubernetes 中&#xff0c;由于Pod 是有生命周期的&#xff0c;如果 Pod 重启它的 IP 可能会发生变化以及升级的时候会重建 Pod&#xff0c;我们需要 Service 服务去动态的关联这些 Pod 的 IP 和端口…