如何在 Vitis HLS 中设计 AXI4 流接口
关于在 Vitis HLS 中设计流接口的演练和源代码
引言
在许多需要数据流的应用中,设计外部 IP 核使用流接口非常常见。这可能涉及图像和视频处理,或者许多射频应用,如 FDM(频分复用)和 OFDM(正交频分复用)。
有很多方法可以做到这一点,但在本文中,我将分享在 Vitis HLS 中设计 AXI4 流接口的方法。我将其用作 Avnet 的 MiniZed FPGA 开发板上的硬件加速器 IP。对于那些可能不熟悉 RTL 设计,或者正在寻找优化其流接口设计方法的开发者来说,这种方法非常棒。
什么是 Vitis HLS?
Vitis 是 Xilinx 创建的一种高级综合工具 (HLS)。它允许开发者将 C、C++ 和 OpenCL 函数综合到 FPGA 逻辑结构中。Vitis HLS 用于使用 Vivado Design suite 为 Xilinx 设备开发 RTL IP。 如果你想了解更多关于 Vitis HLS 的信息,我建议阅读 Xilinx 提供的资源。
Vitis HLS 工具可以自动化 C/C++ 中实现的大部分代码,以实现低延迟和高吞吐量。
这是 Vitis HLS 的设计流程
- 编译、仿真和调试 C/C++ 算法
- 查看报告以分析和优化设计
- 将 C/C++ 算法综合为 RTL 设计
- 使用 RTL 协同仿真验证 RTL 设计
- 将 RTL 实现打包为编译对象文件,或导出为 RTL IP
为什么使用 Vitis HLS?
你可能想知道,如果可以用 Verilog/VHDL 编写自定义 RTL 算法,为什么要使用 Vitis HLS。
答案很简单:编写 C/C++ 函数更容易。 如果你使用 Verilog/VHDL 的 RTL 开发流程,那么首先你必须了解算法,然后编写 RTL 设计和仿真测试平台。 如果它工作正常,那么你必须编写一个 AXI 协议包装器以及测试平台。
这种方法在每个迭代步骤中都需要时间。 此外,如果你的仿真工作良好,那么这并不一定意味着它会在你的硬件 (FPGA) 上以相同的方式工作。
如果你使用 Vitis HLS 工作流程,你需要在 C/C++ 中编写算法,在同一软件中通过仿真和协同仿真运行它,并在需要时分析/优化设计。
接下来,你只需要为你的算法添加 PRAGMA,以用于 AXI 协议和其他 I/O(如果需要)。 最后,如果你对设计感到满意,你可以导出综合的 RTL IP 以用于 Vivado。
使用 Vitis HLS,你还可以在综合期间设置目标时钟频率,Vitis HLS 将优化你的算法以确保实现时序要求。
通过使用单个应用程序,这种类型的工作流程将节省你大量的调试和优化设计时间。 这就是为什么工程师开始修改他们的 FPGA 设计服务以包括 Vitis HLS 作为算法设计的一个选项,以及你可能想要考虑将其添加到你的专业知识的原因。
Using the Code
以下是一个示例实验,解释了如何在 Vitis HLS 中设计 AXI4 流接口。 目标 FPGA 是来自 Avnet 的 MiniZed FPGA 开发板。
首先,我们创建 Stream_generate_mix.cpp 文件
#include "stream_generate_mix.h"
void fnv_hash(unsigned int*x,unsigned int *y,unsigned int *z){
*z = (*x * 0x01000193) ^ *y;
}
void stream_mixin(
hls::stream<data_t> &mixin,
uint32_t *temp_genmixin)
{
#pragma HLS INLINE off
data_t mixin_buff;
unsigned int mixin_count = 0;
do{
#pragma HLS PIPELINE
mixin_buff = mixin.read();
temp_genmixin[mixin_count] = mixin_buff.data;
mixin_count++;
if(mixin_count == 32){
mixin_count = 0;
}
}while(!mixin_buff.last);
}
void stream_dagin(
hls::stream<data_t> &dagin,
uint32_t *temp_dagin)
{
#pragma HLS INLINE off
data_t dagin_buff;
uint32_t dagin_array[32];
unsigned int dagin_count = 0;
do{
#pragma HLS PIPELINE
dagin_buff = dagin.read();
temp_dagin[dagin_count] = dagin_buff.data;
dagin_count++;
if(dagin_count == 32){
dagin_count = 0;
}
}while(!dagin_buff.last);
}
void generate_mix(uint32_t *mixin, uint32_t *dagin, uint32_t *mixout)
{
generate_mix_loop:for(unsigned int i = 0; i < 32; i++){
#pragma HLS UNROLL
fnv_hash(mixin, dagin, mixout);
}
}
void stream_mixout(
uint32_t *mixout_array,
hls::stream<data_t> &mixout)
{
data_t mixout_buff;
unsigned int mixout_count = 0;
mixout_loop:for(unsigned int i = 0; i < 32; i++){
mixout_buff.data = mixout_array[i];
if(i == 31){
mixout_buff.last = 1;
}
else{
mixout_buff.last = 0;
}
mixout.write(mixout_buff);
}
}
void stream_generate_mix(
hls::stream<data_t> &mixin,
hls::stream<data_t> &dagin,
hls::stream<data_t> &mixout)
{
#pragma HLS INTERFACE mode=ap_ctrl_none port=return
#pragma HLS DATAFLOW
#pragma HLS INTERFACE mode=axis register_mode=both port=mixout register
#pragma HLS INTERFACE mode=axis register_mode=both port=dagin register
#pragma HLS INTERFACE mode=axis register_mode=both port=mixin register
uint32_t mixin_array[32];
uint32_t dagin_array[32];
uint32_t mixout_array[32];
stream_mixin(mixin, mixin_array); // receive stream data in mixin_array
stream_dagin(dagin, dagin_array); // receive stream data in dagin_array
generate_mix(mixin_array, dagin_array, mixout_array); // compute fnv_hash
// and store into mixout array
stream_mixout(mixout_array, mixout); // send mixout_array to downstream
}
Generate_mix
是我们要加速的函数。
Mixin
和 dagin
是此函数的输入。 Mixout
是此函数的输出。
输入和输出都是 32 位大小。 这里的 PRAGMA HLS UNROLL
将展开 for 循环以实现更快的计算。
void generate_mix(uint32_t *mixin, uint32_t *dagin, uint32_t *mixout)
{
generate_mix_loop:for(unsigned int i = 0; i < 32; i++){
#pragma HLS UNROLL
mixout[i] = fnv_hash(mixin[i], dagin[i]);
}
}
Stream_mixin
和 stream_dagin
是 2 个独立的函数。 这两个函数被实现为 AXI4 流数据输入函数,从流接口获取输入数据。
mixin 流输入数据将数据存储到 mixin_array
中。 Stream_dagin
输入获取数据并将其存储在 dagin_array
中。 收到数据后,generate_mix
函数进行计算并将数据存储在 mixout_array
中。
一旦输出数据准备就绪,stream_mixout
函数就会通过 AXI-4 流接口将数据从 mixout_array
发送到下游模块。
在 Vitis HLS 中添加 AXI 接口非常简单有效,你可以通过使用 PRAGMA
来做到这一点。
Stream_generate_mix
函数是顶层模块,为了实现最大吞吐量 DATAFLOW
,添加了 PRAGMA
以允许每个函数并行工作。
现在我们添加 testbench
#include "stream_generate_mix.h"
int main(){
hls::stream<data_t> mixin;
hls::stream<data_t> dagin;
hls::stream<data_t> mixout;
data_t mixin_stream;
data_t dagin_stream;
data_t dataout;
mixin_stream.data = 0xa8184851; mixin_stream.last = 0; mixin.write(mixin_stream);
mixin_stream.data = 0x154f4b4f; mixin_stream.last = 0; mixin.write(mixin_stream);
mixin_stream.data = 0x19975771; mixin_stream.last = 0; mixin.write(mixin_stream);
mixin_stream.data = 0x7cef52db; mixin_stream.last = 0; mixin.write(mixin_stream);
mixin_stream.data = 0x5102240a; mixin_stream.last = 0; mixin.write(mixin_stream);
mixin_stream.data = 0x42dddb99; mixin_stream.last = 0; mixin.write(mixin_stream);
mixin_stream.data = 0xb215e865; mixin_stream.last = 0; mixin.write(mixin_stream);
mixin_stream.data = 0x4eca0185; mixin_stream.last = 0; mixin.write(mixin_stream);
mixin_stream.data = 0xc1c2dfc9; mixin_stream.last = 0; mixin.write(mixin_stream);
mixin_stream.data = 0x6ece31eb; mixin_stream.last = 0; mixin.write(mixin_stream);
mixin_stream.data = 0x3c6209d0; mixin_stream.last = 0; mixin.write(mixin_stream);
mixin_stream.data = 0xbbc62b1d; mixin_stream.last = 0; mixin.write(mixin_stream);
mixin_stream.data = 0x74d075ae; mixin_stream.last = 0; mixin.write(mixin_stream);
mixin_stream.data = 0xff1ffd18; mixin_stream.last = 0; mixin.write(mixin_stream);
mixin_stream.data = 0x204b4b69; mixin_stream.last = 0; mixin.write(mixin_stream);
mixin_stream.data = 0x8c93affd; mixin_stream.last = 0; mixin.write(mixin_stream);
mixin_stream.data = 0xa8184851; mixin_stream.last = 0; mixin.write(mixin_stream);
mixin_stream.data = 0x154f4b4f; mixin_stream.last = 0; mixin.write(mixin_stream);
mixin_stream.data = 0x19975771; mixin_stream.last = 0; mixin.write(mixin_stream);
mixin_stream.data = 0x7cef52db; mixin_stream.last = 0; mixin.write(mixin_stream);
mixin_stream.data = 0x5102240a; mixin_stream.last = 0; mixin.write(mixin_stream);
mixin_stream.data = 0x42dddb99; mixin_stream.last = 0; mixin.write(mixin_stream);
mixin_stream.data = 0xb215e865; mixin_stream.last = 0; mixin.write(mixin_stream);
mixin_stream.data = 0x4eca0185; mixin_stream.last = 0; mixin.write(mixin_stream);
mixin_stream.data = 0xc1c2dfc9; mixin_stream.last = 0; mixin.write(mixin_stream);
mixin_stream.data = 0x6ece31eb; mixin_stream.last = 0; mixin.write(mixin_stream);
mixin_stream.data = 0x3c6209d0; mixin_stream.last = 0; mixin.write(mixin_stream);
mixin_stream.data = 0xbbc62b1d; mixin_stream.last = 0; mixin.write(mixin_stream);
mixin_stream.data = 0x74d075ae; mixin_stream.last = 0; mixin.write(mixin_stream);
mixin_stream.data = 0xff1ffd18; mixin_stream.last = 0; mixin.write(mixin_stream);
mixin_stream.data = 0x204b4b69; mixin_stream.last = 0; mixin.write(mixin_stream);
mixin_stream.data = 0x8c93affd; mixin_stream.last = 1; mixin.write(mixin_stream);
dagin_stream.data = 0x2d78ca64; dagin_stream.last = 0; dagin.write(dagin_stream);
dagin_stream.data = 0x7013973a; dagin_stream.last = 0; dagin.write(dagin_stream);
dagin_stream.data = 0x2ba38005; dagin_stream.last = 0; dagin.write(dagin_stream);
dagin_stream.data = 0xd65b0e87; dagin_stream.last = 0; dagin.write(dagin_stream);
dagin_stream.data = 0x4ae28b63; dagin_stream.last = 0; dagin.write(dagin_stream);
dagin_stream.data = 0x45562bcf; dagin_stream.last = 0; dagin.write(dagin_stream);
dagin_stream.data = 0x3f0359e2; dagin_stream.last = 0; dagin.write(dagin_stream);
dagin_stream.data = 0xb13a0b2e; dagin_stream.last = 0; dagin.write(dagin_stream);
dagin_stream.data = 0xfccc25ea; dagin_stream.last = 0; dagin.write(dagin_stream);
dagin_stream.data = 0x0f28bfa0; dagin_stream.last = 0; dagin.write(dagin_stream);
dagin_stream.data = 0x08f15dab; dagin_stream.last = 0; dagin.write(dagin_stream);
dagin_stream.data = 0xe2432196; dagin_stream.last = 0; dagin.write(dagin_stream);
dagin_stream.data = 0x6c2a1198; dagin_stream.last = 0; dagin.write(dagin_stream);
dagin_stream.data = 0x310d15a5; dagin_stream.last = 0; dagin.write(dagin_stream);
dagin_stream.data = 0x81abef7b; dagin_stream.last = 0; dagin.write(dagin_stream);
dagin_stream.data = 0x515dca4b; dagin_stream.last = 0; dagin.write(dagin_stream);
dagin_stream.data = 0x858a4a1a; dagin_stream.last = 0; dagin.write(dagin_stream);
dagin_stream.data = 0x12f6ccc2; dagin_stream.last = 0; dagin.write(dagin_stream);
dagin_stream.data = 0x476434c0; dagin_stream.last = 0; dagin.write(dagin_stream);
dagin_stream.data = 0xbf7cd5b4; dagin_stream.last = 0; dagin.write(dagin_stream);
dagin_stream.data = 0xc3430958; dagin_stream.last = 0; dagin.write(dagin_stream);
dagin_stream.data = 0xc7129b62; dagin_stream.last = 0; dagin.write(dagin_stream);
dagin_stream.data = 0x46e72eb9; dagin_stream.last = 0; dagin.write(dagin_stream);
dagin_stream.data = 0x2f931315; dagin_stream.last = 0; dagin.write(dagin_stream);
dagin_stream.data = 0x98bf46c7; dagin_stream.last = 0; dagin.write(dagin_stream);
dagin_stream.data = 0x6fc53dca; dagin_stream.last = 0; dagin.write(dagin_stream);
dagin_stream.data = 0x735be2c2; dagin_stream.last = 0; dagin.write(dagin_stream);
dagin_stream.data = 0x7d6eba54; dagin_stream.last = 0; dagin.write(dagin_stream);
dagin_stream.data = 0xc68ae2a4; dagin_stream.last = 0; dagin.write(dagin_stream);
dagin_stream.data = 0xbedc3876; dagin_stream.last = 0; dagin.write(dagin_stream);
dagin_stream.data = 0x94d4ad9d; dagin_stream.last = 0; dagin.write(dagin_stream);
dagin_stream.data = 0xeac2ff68; dagin_stream.last = 1; dagin.write(dagin_stream);
stream_generate_mix(mixin, dagin, mixout);
for(unsigned int i = 0; i < 32; i++){
dataout = mixout.read();
printf("mixout[%d]:%x \r",i,dataout.data);
}
}
最后,我们添加头文件
#include <iostream>
#include "stdio.h"
#include "string.h"
#include "hls_stream.h"
#include "ap_axi_sdata.h"
#include "ap_int.h"
typedef ap_axis<32,0,0,1> data_t;
void stream_generate_mix(
hls::stream<data_t> &mixin,
hls::stream<data_t> &dagin,
hls::stream<data_t> &mixout);
一旦生成并导出 IP,就需要将其添加到 Vivado block design 中。 这是 stream_generate_mix
IP 的图像
这是器件利用率
我希望这能帮助你理解如何在 Vitis HLS 中实现具有更低延迟和更高吞吐量的 AXI-4 流接口。
历史
- 2022 年 6 月 10 日:初始版本