Vivado HLS如何高效传递参数?

99ANYc3cd6
预计阅读时长 20 分钟
位置: 首页 参数 正文

下面我将从不同场景出发,详细讲解如何在 Vivado HLS 中传递参数,并提供代码示例。

vivado hls 传参数
(图片来源网络,侵删)

核心概念:参数传递的本质

在 HLS 中,参数传递的本质是在 C/C++/SystemC 代码中定义可配置的常量,当您使用 hls::directive 将函数综合为 IP 核时,这些常量可以被“提升”为 IP 核的可配置参数,在生成 IP 后,您可以在 Vivado Block Design 中或通过 Tcl 脚本为这些参数赋值,从而改变 IP 核的行为,而无需重新综合。


顶层接口参数(最常用)

这是最常见的场景,用于配置 IP 核的接口,例如数据位宽、FIFO 深度等,这些参数通常通过 #pragma HLS INTERFACE 指令与顶层函数的输入/输出参数关联。

配置数据位宽

假设您有一个处理像素的函数,您可能希望它既能处理 8-bit 的灰度图,也能处理 24-bit 的彩色图,您可以将数据类型定义为参数。

C++ 代码 (top_function.h):

vivado hls 传参数
(图片来源网络,侵删)
#ifndef TOP_FUNCTION_H
#define TOP_FUNCTION_H
#include <ap_int.h> // 使用 Xilinx 的任意精度整数类型
// 使用模板来定义数据位宽参数
template <int DATA_WIDTH>
void process_data(
    const ap_int<DATA_WIDTH>  in_data,  // 输入数据
    ap_int<DATA_WIDTH>       out_data  // 输出数据
) {
    // HLS 指令:将 in_data 映射为 AXI-Stream 接口的 'tdata' 信号
    // 并指定其位宽为 DATA_WIDTH
    #pragma HLS INTERFACE axis port=in_data  bundle=DATA_BUS
    #pragma HLS INTERFACE axis port=out_data bundle=DATA_BUS
    // 内部逻辑示例:简单地将数据加1
    out_data = in_data + 1;
}
#endif // TOP_FUNCTION_H

如何使用:

在您的顶层测试文件中,您可以实例化这个模板函数,并指定具体的 DATA_WIDTH

#include "top_function.h"
void top(ap_uint<8>  in_data_8,  ap_uint<8>  &out_data_8,
         ap_uint<24> in_data_24, ap_uint<24> &out_data_24) {
    // 实例化一个处理 8-bit 数据的版本
    process_data<8>(in_data_8, out_data_8);
    // 实例化一个处理 24-bit 数据的版本
    process_data<24>(in_data_24, out_data_24);
}

综合结果: Vivado HLS 会为 process_data<8>process_data<24> 分别生成一个 IP 核,这两个 IP 核的 AXI-Stream 接口 tdata 的位宽分别为 8 和 24。

配置 FIFO 深度

当您使用 m_axi 接口访问外部内存(如 DDR)时,您可能需要调整 AXI-Stream 到 M_AXI 的 FIFO 深度以优化性能。

C++ 代码:

#include <ap_axi_sdata.h>
#include <hls_stream.h>
#define DATA_WIDTH 32
void memory_access(hls::stream<ap_axiu<DATA_WIDTH, 0, 0, 0> >& in_stream,
                   int* out_buffer) {
    // ...
    ap_axiu<DATA_WIDTH, 0, 0, 0> data;
    // 指令:创建一个从 AXI-Stream 到 M_AXI 的 FIFO
    // 并将其深度参数化
    #pragma HLS STREAM variable=in_stream depth=FIFO_DEPTH
    #pragma HLS INTERFACE m_axi port=out_buffer offset=slave bundle=gmem
    for (int i = 0; i < 1024; i++) {
        in_stream.read(data);
        out_buffer[i] = data.data;
    }
}

如何传递参数: 您不能直接在 #pragma 中使用变量,标准做法是使用宏定义。

  1. 在命令行或 Tcl 脚本中定义宏:
    # 在 Vivado HLS Tcl 控制台中
    set_param hls.directive.m_axi_fifo_depth 32 
    # 或者更直接的方式是在综合命令中添加 -DFIFO_DEPTH=32
  2. 在 C++ 代码中:
    // 在文件开头
    #ifndef FIFO_DEPTH
    #define FIFO_DEPTH 16 // 默认值
    #endif

内部算法参数

有时,参数用于控制算法内部的行为,例如滤波器大小、查找表大小、循环次数等。

使用 #define 宏定义

这是最简单直接的方法。

C++ 代码:

#include <ap_int.h>
// 定义滤波器大小为参数
#define FILTER_SIZE 3
void filter_image(const ap_int<8> input_image[512][512],
                  ap_int<8> output_image[512][512]) {
    // 将 FILTER_SIZE 提升为 HLS 参数,这样它就可以在 IP 核配置时修改
    #pragma HLS INTERFACE ap_ctrl_none port=return
    #pragma HLS PIPELINE II=1
    for (int i = 0; i < 512; i++) {
        for (int j = 0; j < 512; j++) {
            // ... 使用 FILTER_SIZE 进行卷积运算 ...
        }
    }
}

如何传递参数: 在综合命令中通过 -D 选项覆盖宏定义。

# 在命令行中
vivado_hls -f script.tcl -DFILTER_SIZE=5

或者在 Vivado HLS GUI 的 "Solution Settings" -> "Compile Options" -> "Other Flags" 中添加 -DFILTER_SIZE=5

使用 hls::param (推荐用于内部参数)

Vivado HLS 提供了 hls::param 类,专门用于创建 HLS 参数,它比宏定义更结构化,并且可以在 HLS 工具中被更好地识别和管理。

C++ 代码:

#include <ap_int.h>
#include <hls/param.h> // 包含 hls::param
// 使用 hls::param 定义一个整数参数
const int hls_param filter_size = 3;
void filter_image_with_param(const ap_int<8> input_image[512][512],
                             ap_int<8> output_image[512][512]) {
    #pragma HLS INTERFACE ap_ctrl_none port=return
    #pragma HLS PIPELINE II=1
    // hls::param 对象可以直接在循环边界等地方使用
    for (int i = 0; i < 512; i++) {
        for (int j = 0; j < 512; j++) {
            // ...
        }
    }
}

如何传递参数: hls::param 的参数值通常通过 Tcl 脚本进行配置,在综合脚本中,您可以使用 set_param 命令。

Tcl 脚本 (script.tcl):

# 打开项目
open_project -force my_project
set_top filter_image_with_param
# 添加源文件
add_files filter_image.cpp
# 创建解决方案
create_solution -flow_target vivado
set_part {xc7z020-clg400-1} # 根据你的FPGA选择
create_clock -period 10
# --- 关键部分:设置参数 ---
# 设置名为 "filter_size" 的 hls::param 参数的值为 5
set_param hls.param.filter_size 5
# 运行综合
csim_design -clean
csynth_design
export_design -format ip_catalog -output ./my_ip
close_project

运行 vivado_hls -f script.tcl 后,生成的 IP 核将使用 filter_size = 5 进行综合。


通过 Vivado GUI 传递参数

对于宏定义参数,您可以直接在 Vivado HLS GUI 中配置,非常方便。

  1. 打开项目并添加源文件:像往常一样创建项目并添加您的 C++ 文件。
  2. 转到 "Solution Settings":在左侧导航栏中,选择您的解决方案,然后点击 "Solution Settings"。
  3. 修改 "Compile Options"
    • 在 "Compile Options" 标签页下,找到 "Other Flags" 字段。
    • 在这里添加您的宏定义,-DFILTER_SIZE=5-DDATA_WIDTH=64
  4. 运行综合:点击 "Run C Synthesis",HLS 将会使用您定义的参数值进行综合。

最佳实践与总结

  1. 区分参数类型

    • 接口参数:使用 #pragma HLS INTERFACE 和模板(如 <int DATA_WIDTH>)。
    • 内部算法参数:优先使用 hls::param,因为它更结构化;对于简单场景,可以使用 #define
  2. 命名清晰:为您的参数使用有意义的名称,如 AXI_DATA_WIDTH, MEM_DEPTH, FILTER_KERNEL_SIZE

  3. 提供默认值:在 C++ 代码中,为您的参数(特别是 hls::param#define)提供合理的默认值,这样即使不通过命令行或脚本传参,代码也能正确编译和综合。

  4. 文档化:在您的代码注释中说明每个参数的用途和有效范围,方便其他开发者(和未来的您)理解。

  5. Tcl 脚本自动化:对于需要频繁更改参数并快速迭代验证的场景,编写 Tcl 脚本是最高效的方式,它可以实现一键配置、综合和导出。

通过灵活运用这些参数传递技术,您可以创建出高度可配置、可复用的 IP 核,从而极大地加速您的 FPGA 硬件开发流程。

-- 展开阅读全文 --
头像
奔腾133参数有哪些关键性能指标?
« 上一篇 前天
94fifty智能篮球,如何提升投篮精准度?
下一篇 » 前天

相关文章

取消
微信二维码
支付宝二维码

最近发表

标签列表

目录[+]