hls::stream在HLS DATAFLOW中的真正作用
hls::stream在HLS DATAFLOW中的真正作用

澄清误解
这是许多HLS初学者的常见误解。hls::stream不是专门为PS-PL(处理系统和可编程逻辑)通信设计的,而是Vitis HLS提供的一种通用数据流抽象。在IP核内部使用hls::stream不仅是正确的做法,而且是实现高效DATAFLOW优化的关键技术。
hls::stream的真正用途
hls::stream是一种高级抽象,用于模拟硬件中的FIFO(先进先出)缓冲区。其主要用途包括:
- IP核内部的模块间通信:连接DATAFLOW区域中的不同计算单元
- 建立硬件流水线:支持任务级并行处理
- 提供数据流抽象:在C/C++代码中模拟硬件流的行为
在硬件实现中,hls::stream通常被综合为FIFO缓冲区,这是一种高效的硬件结构,非常适合流水线设计。
与AXI-Stream的关系(可能导致混淆的原因)
您的误解可能来源于hls::stream可以通过特定的接口指令映射到AXI-Stream接口:
void my_function(hls::stream<data_t> &input, hls::stream<data_t> &output) {
#pragma HLS INTERFACE axis port=input
#pragma HLS INTERFACE axis port=output
// ...
}
但这只是hls::stream的一种用法,而非其主要用途。大多数情况下,hls::stream用于IP核内部的模块间通信,而不会映射到外部接口。
为什么在IP核内部使用hls::stream是正确的
1. 解耦计算阶段
在DATAFLOW优化中,我们将算法分解为多个可以并行执行的任务。这些任务需要一种高效的通信机制,hls::stream正好满足这一需求:
void image_filter(pixel_t input[1024], pixel_t output[1024]) {
hls::stream<pixel_t> stream1, stream2;
#pragma HLS DATAFLOW
read_input(input, stream1); // 生产者
apply_filter(stream1, stream2); // 中间处理
write_output(stream2, output); // 消费者
}
在这个例子中,三个函数通过流连接,形成一个流水线。每个函数可以在不等待其他函数完全完成的情况下处理数据,大大提高了吞吐量。
2. 减少内存使用和访问
与使用数组相比,hls::stream可以显著减少内存使用和访问:
// 使用数组 - 需要完整的中间缓冲区
void process_with_array(data_t input[1024], data_t output[1024]) {
data_t buffer[1024]; // 需要存储1024个元素
for(int i = 0; i < 1024; i++)
buffer[i] = input[i] * 2; // 第一阶段
for(int i = 0; i < 1024; i++)
output[i] = buffer[i] + 10; // 第二阶段
}
// 使用stream - 只需要小型FIFO缓冲区
void process_with_stream(data_t input[1024], data_t output[1024]) {
hls::stream<data_t> stream;
#pragma HLS STREAM variable=stream depth=8 // 只需要8个元素的FIFO
#pragma HLS DATAFLOW
// 这两个循环可以并行执行
for(int i = 0; i < 1024; i++)
stream.write(input[i] * 2); // 第一阶段
for(int i = 0; i < 1024; i++)
output[i] = stream.read() + 10; // 第二阶段
}
在数组版本中,我们需要一个完整的1024元素缓冲区。而在流版本中,只需要一个小型FIFO(例如8个元素),就可以实现相同的功能,同时支持并行执行。
3. 提高吞吐量
使用hls::stream可以实现真正的流水线并行处理,显著提高吞吐量:
// 无流水线 - 顺序执行
void sequential_process(data_t input[1024], data_t output[1024]) {
// 必须完全完成第一步才能开始第二步
data_t temp[1024];
for(int i = 0; i < 1024; i++)
temp[i] = stage1(input[i]);
for(int i = 0; i < 1024; i++)
output[i] = stage2(temp[i]);
}
// 使用stream创建流水线
void pipelined_process(data_t input[1024], data_t output[1024]) {
hls::stream<data_t> stream;
#pragma HLS DATAFLOW
// 这两个函数可以并行执行
produce_data(input, stream); // 处理一部分数据后就开始传输
consume_data(stream, output); // 一旦有数据可用就开始处理
}
在流水线版本中,produce_data函数处理完一部分数据后,就可以将结果传递给consume_data函数,而不必等待所有数据处理完成。这样,两个函数可以同时工作,大大提高了吞吐量。
4. 隐式同步和控制
hls::stream提供了隐式同步机制,简化了设计:
- 读操作会阻塞:如果流为空,读操作会等待直到有数据可用
- 写操作会阻塞:如果流已满,写操作会等待直到有空间可用
- 无需显式同步:不需要额外的同步机制,减少了代码复杂性
这种行为与硬件FIFO非常相似,使得代码更加直观和易于理解。
与其他方法的比较
为什么使用hls::stream而不是其他方法?让我们比较一下:
| 方法 | 优点 | 缺点 |
|---|---|---|
| 共享数组 | 简单直观 | 需要显式同步;难以实现并行;资源使用效率低 |
| 乒乓缓冲区 | 支持一定程度的并行 | 需要手动管理切换;实现复杂;资源使用较多 |
| hls::stream | 隐式同步;高效并行;资源使用少;硬件友好 | 需要理解流的概念;可能需要调整缓冲区大小 |
实际例子:简单图像处理流水线
下面是一个简化的图像处理流水线示例,展示了hls::stream在IP核内部的实际应用:
#include "hls_stream.h"
typedef unsigned char pixel_t;
const int SIZE = 1024;
// 顶层函数
void image_filter(pixel_t input[SIZE], pixel_t output[SIZE]) {
hls::stream<pixel_t> stream1;
hls::stream<pixel_t> stream2;
// 设置适当的缓冲区大小
#pragma HLS STREAM variable=stream1 depth=16
#pragma HLS STREAM variable=stream2 depth=16
#pragma HLS DATAFLOW
// 阶段1: 读取输入数据并乘以2
for (int i = 0; i < SIZE; i++) {
#pragma HLS PIPELINE II=1
stream1.write(input[i] * 2);
}
// 阶段2: 应用滤波器(简单的3点平均)
pixel_t buffer[3] = {0, 0, 0};
for (int i = 0; i < SIZE; i++) {
#pragma HLS PIPELINE II=1
// 更新缓冲区
buffer[0] = buffer[1];
buffer[1] = buffer[2];
buffer[2] = stream1.read();
// 计算平均值
pixel_t result = (buffer[0] + buffer[1] + buffer[2]) / 3;
// 写入输出流
stream2.write(result);
}
// 阶段3: 写回结果
for (int i = 0; i < SIZE; i++) {
#pragma HLS PIPELINE II=1
output[i] = stream2.read();
}
}
在这个例子中:
- 三个处理阶段通过
hls::stream连接,形成流水线 - 每个阶段可以并行工作,提高吞吐量
- 使用小型FIFO缓冲区而不是大型数组存储中间结果
- 流的深度可以根据需要调整,以平衡资源使用和性能
总结
hls::stream是Vitis HLS中一个强大的抽象,主要用于:
- IP核内部的模块间通信,特别是在DATAFLOW优化中
- 创建硬件流水线,提高吞吐量
- 减少内存使用,通过FIFO替代大型缓冲区
- 解耦计算阶段,提高模块化和可维护性
它不是专门为PS-PL通信设计的,尽管在某些情况下可以映射到AXI-Stream接口用于外部通信。
在DATAFLOW优化中使用hls::stream是完全正确且推荐的做法,它是实现高性能HLS设计的关键工具之一。通过在IP核内部使用hls::stream,可以显著提高设计的吞吐量和资源利用率,这也是为什么它在HLS最佳实践中被广泛推荐。
更多推荐
所有评论(0)