65.9K
CodeProject 正在变化。 阅读更多。
Home

如何在 Vitis HLS 中设计 AXI4 流接口

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2022 年 6 月 10 日

MIT

4分钟阅读

viewsIcon

11105

downloadIcon

112

关于在 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 的设计流程

  1. 编译、仿真和调试 C/C++ 算法
  2. 查看报告以分析和优化设计
  3. 将 C/C++ 算法综合为 RTL 设计
  4. 使用 RTL 协同仿真验证 RTL 设计
  5. 将 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 是我们要加速的函数。
Mixindagin 是此函数的输入。 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_mixinstream_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 日:初始版本
© . All rights reserved.