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

目录

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 = 32

    hls::stream<hls::vector<uint32_t, 16>> 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(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 = 32
        out.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 = 32
        hls::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 = 32
        out.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 = 32
        out.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 = 32
        out.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 = 32
        out[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 = 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);
}

#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 dataflow
    funcA(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 = 2
        unsigned 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 = 2
        out[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 = 2
        out[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 = 2
        out[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 的适用场景和配置方法,从而在设计中做出最佳选择。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值