Vitis HLS 学习笔记--通道的FIFO/PIPO选择

ops/2025/2/15 16:43:10/

目录

1. 简介

2. 代码详解

2.1 FIFO 通道示例

2.1.1 配置默认通道

2.1.2 kernel 代码 

2.1.3 综合报告

2.1.4 depth = 32 解析

2.1.5 FIFO 通道分类

2.2 PIPO

2.2.1 配置默认通道

2.2.2  kernel 代码 

2.2.3 综合报告

2.2.4 PIPO 通道分类

3. 综合对比

3.1 数据类型和接口

3.2 数据流和数据流控制

3.3 计算函数和计算方式

4. 总结


1. 简介

本文通过调整 Vitis HLS 编译器的默认设置,选择FIFO或者PIPO作为通道缓存。首先,我们来复习一下这两种缓存的区别:

想象一下,你在一家快餐店,FIFO 缓冲器就像是排队等候的顾客队伍。第一个进入队伍的顾客会是第一个得到服务的人。这个系统的好处是,顾客(生产者)一旦排队,厨师(使用者)就可以开始准备他们的食物。但如果厨师做饭的速度赶不上顾客排队的速度,或者反过来,就会造成混乱,有可能导致整个队伍停滞不前,这就是所谓的“死锁”。

现在,让我们来看看乒乓缓冲器。这就像是有两个窗口的快餐店。当一个窗口正在为顾客准备食物时,另一个窗口就在接待新的顾客。一旦一个窗口的食物准备好了,顾客就可以从那里取餐,而厨师则转到另一个窗口继续工作。这样,即使顾客来得很快,也不会有人等得太久,因为总有一个窗口是在准备食物的。

PIPO 缓冲器的工作方式类似于一个自动调节的快餐店,无论顾客来得多快,厨师都能以相同的速度做饭,确保每个人都能及时得到食物,不会有任何拥堵或延误。

在这两种情况下,无论是 FIFO 还是 PIPO,关键点都是顾客(生产者)将订单(数据块)传递给厨师(使用者)。订单可以是一个汉堡包(单个值),也可以是一整个家庭套餐(一组 N 个值)。订单越大,厨师需要的准备空间就越多。

本文内容会参照《Vitis HLS 学习笔记--控制驱动TLP-处理deadlock_vitis hls数据流处理-CSDN博客》中对于 FIFO /PIPO 类型的分类。

2. 代码详解

2.1 FIFO 通道示例

2.1.1 配置默认通道

首先配置 FIFO 缓存,可以通过如下指令进行设定:

# Create a solution
open_solution -reset solution1 -flow_target vitis
config_dataflow -default_channel fifo -fifo_depth 2

或者通过 UI 界面设置:

2.1.2 kernel 代码 

#include <hls_stream.h>
#include <hls_vector.h>extern "C" {void diamond(hls::vector<uint32_t, 16>* vecIn, hls::vector<uint32_t, 16>* 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 = 32hls::stream<hls::vector<uint32_t, 16>> c0, c1, c2, c3, c4, c5;assert(size % 16 == 0);#pragma HLS dataflowload(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(hls::vector<uint32_t, 16>* in, hls::stream<hls::vector<uint32_t, 16>>& out, int size) {
Loop_Ld:for (int i = 0; i < size; i++) {
#pragma HLS performance target_ti = 32
#pragma HLS LOOP_TRIPCOUNT max = 32out.write(in[i]);}
}void compute_A(hls::stream<hls::vector<uint32_t, 16>>& in, hls::stream<hls::vector<uint32_t, 16>>& out1,hls::stream<hls::vector<uint32_t, 16>>& out2, int size) {
Loop_A:for (int i = 0; i < size; i++) {
#pragma HLS performance target_ti = 32
#pragma HLS LOOP_TRIPCOUNT max = 32hls::vector<uint32_t, 16> t = in.read();out1.write(t * 3);out2.write(t * 3);}
}void compute_B(hls::stream<hls::vector<uint32_t, 16>>& in, hls::stream<hls::vector<uint32_t, 16>>& out,int size) {
Loop_B:for (int i = 0; i < size; i++) {
#pragma HLS performance target_ti = 32
#pragma HLS LOOP_TRIPCOUNT max = 32out.write(in.read() + 25);}
}void compute_C(hls::stream<hls::vector<uint32_t, 16>>& in, hls::stream<hls::vector<uint32_t, 16>>& out,int size) {
Loop_C:for (unsigned int i = 0; i < size; i++) {
#pragma HLS performance target_ti = 32
#pragma HLS LOOP_TRIPCOUNT max = 32out.write(in.read() * 2);}
}
void compute_D(hls::stream<hls::vector<uint32_t, 16>>& in1, hls::stream<hls::vector<uint32_t, 16>>& in2,hls::stream<hls::vector<uint32_t, 16>>& out, int size) {
Loop_D:for (unsigned int i = 0; i < size; i++) {
#pragma HLS performance target_ti = 32
#pragma HLS LOOP_TRIPCOUNT max = 32out.write(in1.read() + in2.read());}
}void store(hls::stream<hls::vector<uint32_t, 16>>& in, hls::vector<uint32_t, 16>* out, int size) {
Loop_St:for (int i = 0; i < size; i++) {
#pragma HLS performance target_ti = 32
#pragma HLS LOOP_TRIPCOUNT max = 32out[i] = in.read();}
}

数据流说明:

  • load:读取数组 vecIn -> c0;
  • compute_A:从流 c0 读取数据,计算后写入 c0 × 3 -> c1, c0 ×3 -> c2;
  • compute_B:从流 c1 读取数据,计算后写入 c1 + 25 -> c3;
  • compute_C:从流 c2 读取数据,计算后写入 c2 × 2 -> c4;
  • compute_D:从流 c3 和 c4 读取数据,计算后写入 c3 + c4 -> c5;
  • store:数组 c5 -> vecOut。 

2.1.3 综合报告

查看综合后的报告:

================================================================
== HW Interfaces
================================================================
* M_AXI
+------------+------------+---------------+---------+--------+----------+-----------+--------------+--------------+-------------+-------------+
| Interface  | Data Width | Address Width | Latency | Offset | Register | Max Widen | Max Read     | Max Write    | Num Read    | Num Write   |
|            | (SW->HW)   |               |         |        |          | Bitwidth  | Burst Length | Burst Length | Outstanding | Outstanding |
+------------+------------+---------------+---------+--------+----------+-----------+--------------+--------------+-------------+-------------+
| m_axi_gmem | 512 -> 512 | 64            | 64      | slave  | 0        | 512       | 16           | 16           | 16          | 16          |
+------------+------------+---------------+---------+--------+----------+-----------+--------------+--------------+-------------+-------------+

具有较大的位宽,512bit,符合 hls::vector<uint32_t, 16> 定义,512=32*16。

* SW-to-HW Mapping
+----------+---------------+-----------+----------+------------------------------------+
| Argument | HW Interface  | HW Type   | HW Usage | HW Info                            |
+----------+---------------+-----------+----------+------------------------------------+
| vecIn    | m_axi_gmem    | interface |          |                                    |
| vecIn    | s_axi_control | register  | offset   | name=vecIn_1 offset=0x10 range=32  |
| vecIn    | s_axi_control | register  | offset   | name=vecIn_2 offset=0x14 range=32  |
| vecOut   | m_axi_gmem    | interface |          |                                    |
| vecOut   | s_axi_control | register  | offset   | name=vecOut_1 offset=0x1c range=32 |
| vecOut   | s_axi_control | register  | offset   | name=vecOut_2 offset=0x20 range=32 |
| size     | s_axi_control | register  |          | name=size offset=0x28 range=32     |
+----------+---------------+-----------+----------+------------------------------------+

vecIn 和 vecOut 均是绑定到 m_axi_gmem 的 AXI 主接口。 

2.1.4 depth = 32 解析

void diamond(vecOf16Words* vecIn, vecOf16Words* vecOut, int size) {
#pragma HLS INTERFACE m_axi port = vecIn depth = 32
#pragma HLS INTERFACE m_axi port = vecOut depth = 32hls::stream<vecOf16Words> c0, c1, c2, c3, c4, c5;assert(size % 16 == 0);#pragma HLS dataflowload(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);
}

#pragma HLS INTERFACE m_axi port = vecIn depth = 32 指令用于设置接口属性。这里的 depth=32 是一个指令选项,它指定了AXI接口的深度,也就是FPGA与外部内存交互时可以访问的数据量。这个数值应该与设计打算在单次事务中处理的数据量相匹配。由于 TB 中的 std::vector 容器 test 和 outcome 的大小都被初始化为32,这表明设计打算在一个事务中处理32个向量,因此 depth 被设置为32。

这个深度值对于优化数据传输非常重要,因为它影响了接口生成的FIFO缓冲区的大小,以及FPGA与外部内存之间的突发传输能力。正确设置深度可以帮助提高性能,减少延迟,并确保数据的连续流动。

2.1.5 FIFO 通道分类

FIFO 有三种类型:

  • Streams (including hls::streams and streamed arrays),用户创建。
  • Scalar propagation FIFOs,工具推断。
  • Streams of blocks,用户创建。

对照 Dataflow 报告,本 kernel 的 FIFO 通道有两种类型 Stream 和 ScalarProp,从变量的名字也可以看出,c0、c1、c2、c3、c4、c5是由用户创建的,而其余的变量则由 HLS 工具推断生成。

2.2 PIPO

2.2.1 配置默认通道

首先配置 PIPO 缓存,可以通过如下指令进行设定:

# Create a solution

open_solution -reset solution1 -flow_target vitis

或者通过 UI 界面设置:

2.2.2  kernel 代码 

void diamond(unsigned char vecIn[100], unsigned char vecOut[100]) {unsigned char c1[100], c2[100], c3[100], c4[100];
#pragma HLS dataflowfuncA(vecIn, c1, c2);funcB(c1, c3);funcC(c2, c4);funcD(c3, c4, vecOut);
}void funcA(unsigned char* in, unsigned char* out1, unsigned char* out2) {
Loop0:for (int i = 0; i < 100; i++) {
#pragma HLS pipeline rewind
#pragma HLS unroll factor = 2unsigned char t = in[i] * 3;out1[i] = t;out2[i] = t;}
}void funcB(unsigned char* in, unsigned char* out) {
Loop0:for (int i = 0; i < 100; i++) {
#pragma HLS pipeline rewind
#pragma HLS unroll factor = 2out[i] = in[i] + 25;}
}void funcC(unsigned char* in, unsigned char* out) {
Loop0:for (unsigned char i = 0; i < 100; i++) {
#pragma HLS pipeline rewind
#pragma HLS unroll factor = 2out[i] = in[i] * 2;}
}void funcD(unsigned char* in1, unsigned char* in2, unsigned char* out) {
Loop0:for (int i = 0; i < 100; i++) {
#pragma HLS pipeline rewind
#pragma HLS unroll factor = 2out[i] = in1[i] + in2[i] * 2;}
}

数据流说明:

  • compute_A:计算后写入 vecIn × 3 -> c1, vecIn ×3 -> c2;
  • compute_B:计算后写入 c1 + 25 -> c3;
  • compute_C:计算后写入 c2 × 2 -> c4;
  • compute_D:计算后写入 c3 + c4 × 2 -> vecOut.

2.2.3 综合报告

================================================================
== HW Interfaces
================================================================
* M_AXI
+------------+------------+---------------+---------+--------+----------+-----------+--------------+--------------+-------------+-------------+
| Interface  | Data Width | Address Width | Latency | Offset | Register | Max Widen | Max Read     | Max Write    | Num Read    | Num Write   |
|            | (SW->HW)   |               |         |        |          | Bitwidth  | Burst Length | Burst Length | Outstanding | Outstanding |
+------------+------------+---------------+---------+--------+----------+-----------+--------------+--------------+-------------+-------------+
| m_axi_gmem | 512 -> 512 | 64            | 64      | slave  | 0        | 512       | 16           | 16           | 16          | 16          |
+------------+------------+---------------+---------+--------+----------+-----------+--------------+--------------+-------------+-------------+
* SW-to-HW Mapping
+----------+---------------+-----------+----------+------------------------------------+
| Argument | HW Interface  | HW Type   | HW Usage | HW Info                            |
+----------+---------------+-----------+----------+------------------------------------+
| vecIn    | m_axi_gmem    | interface |          |                                    |
| vecIn    | s_axi_control | register  | offset   | name=vecIn_1 offset=0x10 range=32  |
| vecIn    | s_axi_control | register  | offset   | name=vecIn_2 offset=0x14 range=32  |
| vecOut   | m_axi_gmem    | interface |          |                                    |
| vecOut   | s_axi_control | register  | offset   | name=vecOut_1 offset=0x1c range=32 |
| vecOut   | s_axi_control | register  | offset   | name=vecOut_2 offset=0x20 range=32 |
| size     | s_axi_control | register  |          | name=size offset=0x28 range=32     |
+----------+---------------+-----------+----------+------------------------------------+

报告中 HW Interface 与 Mapping 具有相同的内容,这里不再赘述。

2.2.4 PIPO 通道分类

PIPO 有三种类型:

  • PIPO,用户创建。
  • Task Level FIFOs (TLF),工具推断。
  • Input and output ports to the upper level,用户创建。

对照 Dataflow 报告,本 kernel 的 PIPO 通道类型只有一种:PIPO,c1、c2、c3、c4均由用户创建。变量 vecOut_c 则由 HLS 工具推断生成为 FIFO 类型。

 

3. 综合对比

3.1 数据类型和接口

第一段代码:

  • 使用 hls::vector<uint32_t, 16> 类型来表示向量。
  • 使用 hls::stream 数据结构来处理数据流。
  • 显式指定 AXI 主接口。

第二段代码:

  • 使用基本的 unsigned char 类型的数组,每个数组包含 100 个元素。
  • 没有使用 hls::stream,而是使用普通的数组传递数据。
  • 输入输出接口使用普通的 C-style 指针和数组,没有显式的接口设置。

3.2 数据流和数据流控制

第一段代码:

  • 使用 HLS dataflow pragma (#pragma HLS dataflow) 来启用数据流优化
  • 显式指定使用 hls::stream 作为函数间的通信方式,函数之间的数据通过流进行传递。

第二段代码:

  • 同样使用 HLS dataflow pragma (#pragma HLS dataflow) 来启用数据流优化,但数据传递是通过普通的数组来进行。
  • 函数间的数据通过数组传递,没有使用 hls::stream。

3.3 计算函数和计算方式

第一段代码:

  • 函数更为通用,接受 hls::vector 类型的数据,并且处理过程中使用了 HLS 的各种特性如 pipeline 和 unroll。
  • 每个函数处理 hls::vector<uint32_t, 16> 类型的数据,并将结果写入另一个 hls::vector。

第二段代码:

  • 函数接受基本类型的指针和数组作为输入输出,处理过程中使用 pipeline 和 unroll 进行优化。
  • 每个函数处理 unsigned char 类型的数据,并将结果写入另一个 unsigned char 数组。

4. 总结

本文对比了在 Vitis HLS 编译器中配置 FIFO 和 PIPO 缓冲器的方法和效果。通过两段代码示例,我们展示了使用这两种通道缓存的不同实现方式。

在 FIFO 示例中,代码使用 hls::vector<uint32_t, 16> 和 hls::stream 数据结构,通过 AXI 主接口进行数据传输,并用 #pragma HLS dataflow 启用数据流优化,每个函数通过流进行通信。这种方式更通用,但需要显式指定流和接口设置。

在 PIPO 示例中,代码采用 unsigned char 数组进行数据传递,使用 C-style 指针和数组,没有显式接口设置,通过数组传递数据,同样使用 #pragma HLS dataflow 启用数据流优化。这种方式更简单直接,但缺乏流的灵活性。

PIPO 缓冲器可以自动调节,确保任务之间的重叠执行,而不会出现死锁。而显式手动串流的 FIFO 通道虽然可以更快地开始重叠执行,但需要小心调整队列大小,以避免死锁问题。

综合来看,FIFO 适用于复杂的通用数据处理场景,而 PIPO 更适合简单的数据传递需求。正确选择和配置这两种缓冲器,可以优化系统性能,减少延迟。本文帮助读者理解 FIFO 和 PIPO 的适用场景和配置方法,从而在设计中做出最佳选择。


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

相关文章

Vue首屏白屏如何解决

Vue首屏白屏是一个常见的问题&#xff0c;以下是一些可能的解决方案&#xff0c;它们按照可能的原因和解决方案进行了分类和归纳&#xff1a; 一、路由配置问题 路由模式错误&#xff1a; 原因&#xff1a;如果将路由模式&#xff08;mode&#xff09;错误地设置为history&am…

vue路由跳转之【编程式导航与传参】

vue路由有两种跳转方式 ----> 编程式与声明式&#xff0c;本文重点讲解vue路由的【编程式导航 】【编程式导航传参 ( 查询参数传参 & 动态路由传参 ) 】等内容&#xff0c;并结合具体案例让小伙伴们深入理解 &#xff0c;彻底掌握&#xff01;创作不易&#xff0c;需要的…

比较好的Python课程

最近在学习夜曲编程的Python进阶课程——办公效率化&#xff1b;夜曲编程之前有推出一款学习Python的入门课程&#xff0c;在手机端和电脑端都可以学习的&#xff0c;如果没有时间在手机端学习都很好的。每节课程学习下来&#xff0c;可以收集到Python入门的知识卡片&#xff0…

第十七节:带你梳理Vue2: Vue组件的认识,创建和使用

1. 认识Vue组件 组件系统是 Vue 的另一个重要概念&#xff0c;它是一种抽象的概念&#xff0c;类似于积木中的一块块小积木,通过这些小积木拼接成一个大的模型. 组件系统就是将页面一块小的区域处理为一个组件,我们使用这些小型、独立和可复用的组件构建大型应用。 仔细想想…

基于Pytorch框架的深度学习EfficientNetV2神经网络中草药识别分类系统源码

第一步&#xff1a;准备数据 5种中草药数据&#xff1a;self.class_indict ["百合", "党参", "山魈", "枸杞", "槐花", "金银花"] &#xff0c;总共有900张图片&#xff0c;每个文件夹单独放一种数据 第二步&a…

Mac连接虚拟机(Linux系统)

1.确定虚拟机的IP地址 ifconfig //终端命令&#xff0c;查询ip地址 sudo apt install net-tools 安装完成后再次执行 ifconfig&#xff1a; 2.安装SSH&#xff08;加密远程登录协议&#xff09; (1).安装OpenSSH服务器软件包&#xff1a; sudo apt-get install openssh-ser…

软件架构设计属性之四:性能属性浅析

文章目录 引言一、性能属性的内涵与重要性1.1 性能属性的内涵1.2 性能属性的重要性1.3 性能属性的实践应用 二、性能属性的关键影响因素2.1 系统结构2.2 数据处理2.3 并发处理2.4 硬件和资源2.5 软件和框架2.6 监控和优化 三、性能属性的优化策略与实践3.1 优先级队列与资源调度…

AI+算力:科技新时代的创新引擎

随着人工智能&#xff08;AI&#xff09;技术的飞速发展&#xff0c;“AI算力”的结合应用已成为科技行业的热点话题&#xff0c;甚至诞生出“AI算力最强龙头“的网络热门等式。这个组合不仅可以提高计算效率&#xff0c;还可以为各行各业带来更强大的数据处理和分析能力&#…