Vivado HLS

官方文档https://www.xilinx.com/htmldocs/xilinx2018_2/sdaccel_doc/hls-pragmas-okr1504034364623.html

Vivado HLS 提供了可用于优化设计的 pragma:减少延迟、提高吞吐量性能以及减少生成的 RTL 代码的面积和设备资源利用率。 这些 pragma 可以直接添加到内核的源代码中。Vivado HLS pragma 包括以下指定的优化类型:

Table 1. Vivado HLS Pragmas by Type

Type Attributes
Kernel Optimization pragma HLS allocation pragma HLS expression_balance pragma HLS latency pragma HLS reset pragma HLS resource pragma HLS top
Function Inlining pragma HLS inline pragma HLS function_instantiate
Interface Synthesis pragma HLS interface pragma HLS protocol
Task-level Pipeline pragma HLS dataflow pragma HLS stream
Pipeline pragma HLS pipeline pragma HLS occurrence
Loop Unrolling pragma HLS unroll pragma HLS dependence
Loop Optimization pragma HLS loop_flatten pragma HLS loop_merge pragma HLS loop_tripcount
Array Optimization pragma HLS array_map pragma HLS array_partition pragma HLS array_reshape
Structure Packing pragma HLS data_pack

HLS的综合流程

  • Vivado HLS的设计输入包括Test bench、C/C++和Directives,相应的设计输出有IP(在Vivado的IP Catalog中)、DCP(RTL代码综合后的网表文件)、SysGen(HLS之后的结果可以导入SysGen中使用)。
  • Test bench的作用有两点:一是验证C/C++代码的正确性;二是在与RTL的协同仿真阶段,生成用于RTL级验证的Test bench;
  • 对于一个工程,只可以有一个顶层的函数用于综合,这个函数下面的子函数也可以被综合,通常情况下C/C++综合后的RTL代码的结构和原始的C函数描述的结构一致(除了子函数所需要的逻辑量很小、算法功能很简单时,综合阶段不会单独有这个结构,将HLS INLINE off关掉时就会完全一致)。
  • 并不是所有的C/C++语言风格的代码都能被综合,有两点需要注意:一是动态内存分配,二是操作系统层面
  • 一个C/C++代码最后映射到RTL代码有三大类接口,包括Block-level IO Handshake(握手信号)、C Inputs、C Outputs;

Directives的两种方式:

​ 将每个directive以directives.tcl格式作为一个Tcl命令单独存放,以”#”作为标识;
​ 优势在于:每个solution都有独立的directives,如果这个solution需要重新综合,那么只有这个solution下面的directive会起到作用; 不足之处在于:如果C source code文件需要被给到第三方,那么需要将directives.tcl包含其中,对于一个代码需要获得同样的综合结果,那么同样的directives.tcl必不可少。

​ 将每个directive嵌入到C/C++源码中,以pragma格式出现,”%”作为标识;
​ 优势在于:如果C source code文件需要被给到第三方,不需要单独将directives.tcl交付,对于一个代码需要获得同样的综合结果,也不需要额外的directives.tcl;

​ 不足之处在于:如果一个solution需要重新综合,那么所有的directives都要被执行。

几点小技巧:

​ 为C/C++代码中的for循环单独创建标签,这会使得在创建directives时非常方便;
​ 最好将directives单独存放,不要将其和源码放在一起;
​ Test bench中的main()函数的返回结果值为int类型,仿真通过返回值为0,不通过才是1;
​ 通常情况下RTL代码的层次和原始的C/C++代码层次一致;

常用的#pragma

//hls工具在分析代码时对某些函数块可能不是按照我们预期的做inline on/off处理,所以我们需要自己指定,是否inline,应该是在资源和时序之间权衡。
#pragma HLS inline off
//对于操作符dmul,操作符如下表所示,它的意思是在当前函数内,最多使用16个dmul资源
#pragma HLS ALLOCATION instances=dmul limit=16 operation
//当我们自定义一个函数func时,如下所示pragma,可以限定pragma所在的函数所使用的func资源为limit份
#pragma HLS ALLOCATION instances=func limit=1 function
//指定计算的资源类型
#pragma HLS RESOURCE variable=temp core=AddSub_DSP
//指定array InitPara的存储资源类型,包括RAM_2P_BRAM,RAM_2P_URAM,RAM_2P_LUTRAM,FIFO_LUTRAM等等
#pragma HLS RESOURCE variable=InitPara core=RAM_2P_BRAM
// 最新指定数组资源类型的pragma
#pragma HLS bind_storage variable=InitPara type=ram_2p impl=bram
//指定循环的最大最小次数,用来分析latency,但只影响hls报告的生成。
#pragma HLS loop_tripcount min=10 max=10
//表明循环内变量buff_A每一次迭代计算互不依赖,谨慎使用(有些依赖问题工具不能自动判断处理,需要coder来自行指定,但是如果指定错误可能导致设计异常)
#pragma HLS dependence variable=buff_A inter false
//设计变量stream类型(fifo)的变量的深度,影响资源
#pragma HLS stream variable = testStrm depth = 32
//drective写在外部循环的内部,禁止与内部循环合并(拉平)
#pragma HLS flatten off

pargma HLS dataflow

描述

DATAFLOW pragma 启用任务级流水线,允许函数和循环在其操作中重叠,增加 RTL 实现的并发性,并增加设计的整体吞吐量。 所有操作在 C 描述中按顺序执行。 在没有任何限制资源的指令(例如 pragma HLS 分配)的情况下,Vivado® HLS 力求最大限度地减少延迟并提高并发性。 但是,数据依赖性可能会限制这一点。 例如,访问数组的函数或循环必须在完成之前完成对数组的所有读/写访问。 这可以防止使用数据的下一个函数或循环开始操作。 DATAFLOW 优化使函数或循环中的操作能够在前一个函数或循环完成其所有操作之前开始操作。

Figure: DATAFLOW Pragma

image-20220507182510170

​ 指定 DATAFLOW pragma 时,Vivado HLS 分析顺序函数或循环之间的数据流,并创建通道(基于 pingpong RAM 或 FIFO),允许消费者函数或循环在生产者函数或循环完成之前开始操作。 这允许函数或循环并行运行,从而减少延迟并提高 RTL 的吞吐量。

​ 如果未指定启动间隔(一个函数或循环开始与下一个函数或循环之间的周期数),Vivado HLS 会尝试最小化启动间隔并在数据可用时立即开始操作。

提示:config_dataflow 命令指定数据流优化中使用的默认内存通道和 FIFO 深度。 如需了解更多信息,请参阅 Vivado Design Suite User Guide: High-Level Synthesis (UG902) 中的 config_dataflow 命令。

​ 为了使 DATAFLOW 优化起作用,数据必须通过设计从一项任务流向另一项任务。 以下编码样式会阻止 Vivado HLS 执行 DATAFLOW 优化:

  • 单一生产者-消费者违规

  • 绕过任务

  • 任务之间的反馈

  • 有条件地执行任务

  • 具有多个退出条件的循环

  • 重要提示!:如果存在任何这些编码样式,Vivado HLS 会发出一条消息并且不执行 DATAFLOW 优化。

    ​ 最后,DATAFLOW 优化没有分层实现。 如果子函数或循环包含可能受益于 DATAFLOW 优化的其他任务,则必须将优化应用于循环、子函数或内联子函数。

句法

将编译指示放在区域、函数或循环的边界内的 C 源代码中。

#pragma HLS dataflow

案例

指定循环 wr_loop_j 内的 DATAFLOW 优化。

        wr_loop_j: for (int j = 0; j < TILE_PER_ROW; ++j) {
#pragma HLS DATAFLOW
            wr_buf_loop_m: for (int m = 0; m < TILE_HEIGHT; ++m) {
                wr_buf_loop_n: for (int n = 0; n < TILE_WIDTH; ++n) {
#pragma HLS PIPELINE
                    // should burst TILE_WIDTH in WORD beat
                    outFifo >> tile[m][n];
                }
            }
            wr_loop_m: for (int m = 0; m < TILE_HEIGHT; ++m) {
                wr_loop_n: for (int n = 0; n < TILE_WIDTH; ++n) {
#pragma HLS PIPELINE
                    outx[TILE_HEIGHT*TILE_PER_ROW*TILE_WIDTH*i+TILE_PER_ROW*TILE_WIDTH*m+TILE_WIDTH*j+n] = tile[m][n];
                }
            }

pragma HLS pipeline

描述

PIPELINE pragma 通过允许并发执行操作来减少函数或循环的启动间隔。 流水线函数或循环可以每 N 个时钟周期处理一次新输入,其中 N 是循环或函数的启动间隔 (II)。 PIPELINE pragma 的默认启动间隔为 1,它在每个时钟周期处理一个新输入。 您还可以通过使用编译指示的 II 选项来指定启动间隔。

​ 流水线循环允许循环的操作以并发方式实现,如下图所示。 在该图中,(A) 显示了默认顺序操作,其中每次输入读取之间有 3 个时钟周期 (II=3),并且在执行最后一次输出写入之前需要 8 个时钟周期。

图:循环管道

image-20220507191226852

​ 重要提示!:循环进位依赖项可以防止循环流水线。 您可以使用 DEPENDENCE pragma 来提供可以克服循环进位依赖性并允许对循环进行流水线化(或以较低间隔进行流水线化)的附加信息。 如果 Vivado® HLS 无法创建具有指定 II 的设计,它会:

  • 发出警告。

  • 创建具有尽可能低的 II 的设计。

    然后,您可以使用警告消息分析此设计,以确定必须采取哪些步骤来创建满足所需启动间隔的设计。

句法

将编译指示放在函数或循环主体内的 C 源代码中。

#pragma HLS pipeline II=<int> enable_flush rewind

这里:

  • II=:指定管道所需的启动间隔。 Vivado HLS 试图满足这一要求。 根据数据依赖关系,实际结果可能具有更大的启动间隔。 默认 II 为 1。

  • enable_flush:一个可选关键字,它实现一个管道,如果管道输入处的有效数据变为非活动状态,该管道将刷新并清空。

提示:此功能仅支持流水线函数:不支持流水线循环。

  • rewind:一个可选关键字,它启用倒带或连续循环流水线,在一个循环迭代结束和下一个迭代开始之间没有暂停。 仅当顶层函数中有一个循环(或完美的循环嵌套)时,重绕才有效。 循环前的代码段:

    • 被视为初始化。
    • 在管道中只执行一次。
    • 不能包含任何条件操作(if-else)。

提示:仅流水线循环支持此功能:流水线函数不支持此功能。

案例

在此示例中,函数 foo 以启动间隔为 1 流水线化:

void foo { a, b, c, d} {
  #pragma HLS pipeline II=1
  ...
}

注意:II 的默认值为 1,因此本示例中不需要 II=1。

pragma HLS loop_tripcount

描述

TRIPCOUNT pragma 可以应用于循环以手动指定循环执行的迭代总数。

重要提示!:TRIPCOUNT pragma 仅用于分析,不会影响综合结果。

Vivado® HLS 报告每个循环的总延迟,即执行循环所有迭代的时钟周期数。

因此,循环延迟是循环迭代次数或行程计数的函数。 行程计数可以是一个常数值。 它可能取决于循环表达式中使用的变量的值(例如,x<y),或者取决于循环内使用的控制语句。 在某些情况下,Vivado HLS 无法确定行程计数,并且延迟未知。

这包括用于确定行程计数的变量为:

  • 输入参数,或

  • 通过动态操作计算的变量。

    ​ 在循环延迟未知或无法计算的情况下,TRIPCOUNT pragma 允许您指定循环的最小和最大迭代。 这使该工具可以分析报告中的循环延迟对总设计延迟的影响,并帮助您确定设计的适当优化。

句法

将编译指示放在循环主体内的 C 源代码中:

#pragma HLS loop_tripcount min=<int> max=<int> avg=<int>

where:

  • max=:指定循环迭代的最大次数。

  • min=:指定循环迭代的最小次数。

  • avg=:指定循环迭代的平均次数。

案例

在此示例中,函数foo 中的 loop_1被指定为具有 12 的最小行程计数和 16 的最大行程计数:

void foo (num_samples, ...) {
  int i;
  ...
  loop_1: for(i=0;i< num_samples;i++) {
   #pragma HLS loop_tripcount min=12 max=16
   ...
    result = a + b;
  }
}

See Also

  • Vivado Design Suite User Guide: High-Level Synthesis (UG902)

pragma HLS interface

描述

​ 在基于 C 的设计中,所有输入和输出操作都是通过形式函数参数在零时间内执行的。 在 RTL 设计中,这些相同的输入和输出操作必须通过设计接口中的端口执行,并且通常使用特定的 I/O(输入-输出)协议进行操作。 如需了解更多信息,请参阅《Vivado Design Suite 用户指南:高级综合》(UG902) 中的“管理接口”。

INTERFACE pragma指定如何在接口综合期间从函数定义创建 RTL 端口

RTL 实现中的端口源自以下内容:

  • 指定的任何功能级协议:

    函数级协议,也称为块级 I/O 协议,提供信号来控制函数何时开始操作,并指示函数操作何时结束、何时空闲以及准备好接受新输入。

  • 函数级协议的实现:

    • ap_ctrl_none、ap_ctrl_hs 或 ap_ctrl_chain指定。 ap_ctrl_hs 块级 I/O 协议是默认协议。

    • 都与函数名相关联。

  • 函数参数:

    ​ 每个函数参数都可以指定为具有自己的端口级 (I/O) 接口协议,例如有效握手 (ap_vld) 或确认握手 (ap_ack)。 为顶层函数中的每个参数创建端口级接口协议,如果函数返回值,则函数返回。 创建的默认 I/O 协议取决于 C 参数的类型。 在使用块级协议开始块的操作之后,端口级 I/O 协议用于对数据进出块进行排序。

  • 由顶级函数访问并在其范围之外定义的全局变量:

    ​ 如果访问了一个全局变量,但所有的读写操作对函数来说都是本地的,那么资源是在 RTL 设计中创建的。 RTL 中不需要 I/O 端口。 如果预计全局变量是外部源或目标,请以与标准函数参数类似的方式指定其接口。 请参阅下面的示例。 当在子函数上使用 INTERFACE pragma 时,只能使用 register 选项。 子功能不支持 选项。

句法

将 pragma 放在函数的边界内。

#pragma HLS interface <mode> port=<name> bundle=<string> \
register register_mode=<mode> depth=<int> offset=<string> \
clock=<string> name=<string> \
num_read_outstanding=<int> num_write_outstanding=<int> \
max_read_burst_length=<int> max_write_burst_length=<int>

参数详情见:https://www.xilinx.com/htmldocs/xilinx2018_2/sdaccel_doc/pragma-hls-interface-jit1504034365862.html

示例 1

在此示例中,两个函数参数均使用 AXI4-Stream 接口实现:

void example(int A[50], int B[50]) {
  //Set the HLS native interface types
  #pragma HLS INTERFACE axis port=A
  #pragma HLS INTERFACE axis port=B
  int i;
  for(i = 0; i < 50; i++){
    B[i] = A[i] + 5;
  }
}

示例 2

以下关闭块级 I/O 协议,并分配给函数返回值:

#pragma HLS interface ap_ctrl_none port=return

函数参数InData被指定为使用 ap_vld 接口,并且还指示应该注册输入:

#pragma HLS interface ap_vld register port=InData

这将全局变量 lookup_table 公开为 RTL 设计上的一个端口,带有一个 ap_memory 接口:

pragma HLS interface ap_memory port=lookup_table

示例 3

这个例子定义了顶层转置函数端口的接口标准。 请注意使用 bundle= 选项对信号进行分组。

// TOP LEVEL - TRANSPOSE
void transpose(int* input, int* output) {
	#pragma HLS INTERFACE m_axi port=input offset=slave bundle=gmem0
	#pragma HLS INTERFACE m_axi port=output offset=slave bundle=gmem1

	#pragma HLS INTERFACE s_axilite port=input bundle=control
	#pragma HLS INTERFACE s_axilite port=output bundle=control
	#pragma HLS INTERFACE s_axilite port=return bundle=control

	#pragma HLS dataflow

pragma HLS stream

描述

默认情况下,数组变量实现为 RAM:

  • 顶层函数数组参数实现为 RAM 接口端口。

  • 通用数组被实现为用于读写访问的 RAM。

  • 在涉及 DATAFLOW 优化的子函数中,数组参数是使用RAM pingpong缓冲区通道实现的。

  • 基于循环的 DATAFLOW 优化中涉及的数组被实现为RAM pingpong缓冲区通道。

    如果存储在数组中的数据以顺序方式使用或产生,则更有效的通信机制是使用STREAM pragma指定的流数据,其中使用 FIFO 而不是 RAM。

重要提示!:当顶级函数的参数指定为接口类型 ap_fifo 时,该数组将自动实现为流式传输。

句法

将编译指示放在 C 源代码中所需位置的边界内。

#pragma HLS stream variable=<variable> depth=<int> dim=<int> off 

where:

  • variable=:指定要实现为流接口的数组的名称。

  • depth=:仅与DATAFLOW通道中的数组流相关。 默认情况下,RTL 中实现的 FIFO 的深度与 C 代码中指定的数组大小相同。 此选项允许您修改 FIFO 的大小并指定不同的深度。

    ​ 当数组在 DATAFLOW 区域中实现时,通常使用 depth= 选项来减小 FIFO 的大小。 例如,在 DATAFLOW 区域中,当所有循环和函数都以 II=1 的速率处理数据时,不需要大的 FIFO,因为在每个时钟周期都会产生和消耗数据。 在这种情况下,可以使用 depth= 选项将 FIFO 大小减小到 1,从而显着减小 RTL 设计的面积。

    ​ 提示: config_dataflow -depth 命令提供了流式传输 DATAFLOW 区域中的所有阵列的能力。 此处指定的 depth= 选项覆盖分配变量的 config_dataflow 命令。

  • dim=:指定要流式传输的数组的维度。 默认值为 1 维。对于具有 N 维的数组,指定为从 0 到 N 的整数。

  • off:禁用流数据。 仅与数据流通道中的数组流相关。

    ​ 提示: config_dataflow -default_channel fifo 命令全局意味着设计中所有阵列上的 STREAM pragma。 此处指定的 off 选项会覆盖分配变量的 config_dataflow 命令,并恢复使用基于RAM pingpong 缓冲区的通道的默认设置。

示例 1

以下示例指定要流式传输的数组 A[10],并作为 FIFO 实现:

#pragma HLS STREAM variable=A

示例 2

在此示例中,数组 B 设置为 FIFO 深度为 12 的流式传输:

#pragma HLS STREAM variable=B depth=12

示例 3

阵列 C 已禁用流式传输。 在此示例中假定由 config_dataflow 启用:

#pragma HLS STREAM variable=C off
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2022-2024 lk
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信