HLS指令使用指南目录

  • 写在前面
  • 内核优化
    • pragma HLS allocation
      • 说明
      • 语法
      • 示例
    • pragma HLS clock
      • 说明
      • 语法
      • 示例
    • pragma HLS expression_balance
      • 说明
      • 语法
      • 示例
    • pragma HLS latency
      • 说明
      • 语法
      • 示例
    • pragma HLS reset
      • 说明
      • 语法
      • 示例
    • pragma HLS resource
      • 说明
      • 语法
      • 示例
    • pragma HLS top
      • 说明
      • 语法
      • 示例
  • 函数内联
    • pragma HLS inline
      • 说明
      • 语法
      • 示例
    • pragma HLS function_instantiate
      • 说明
      • 语法
      • 示例
  • 接口综合
    • pragma HLS interface
      • 描述
      • 语法
      • 示例
    • pragma HLS protocol
      • 说明
      • 语法
      • 示例
  • 任务级流水
    • pragma HLS dataflow
      • 说明
      • 语法
      • 示例
    • pragma HLS stream
      • 说明
      • 语法
      • 示例
  • 流水操作
    • pragma HLS pipeline
      • 说明
      • 语法
      • 示例
    • pragma HLS occurrence
      • 说明
      • 语法
      • 示例
  • 循环展开
    • pragma HLS unroll
      • 说明
      • 语法
      • 示例
    • pragma HLS dependence
      • 说明
      • 语法
      • 示例
  • 循环优化
    • pragma HLS loop_flatten
      • 说明
      • 语法
      • 示例
    • pragma HLS loop_merge
      • 说明
      • 语法
      • 示例
    • pragma HLS loop_tripcount
      • 说明
      • 语法
      • 示例
  • 数组优化
    • pragma HLS array_map
      • 说明
      • 语法
      • 示例
    • pragma HLS array_partition
      • 说明
      • 语法
      • 示例
    • pragma HLS array_reshape
      • 说明
      • 语法
      • 示例
  • 结构体打包
    • pragma HLS data_pack
      • 说明
      • 语法
      • 示例

写在前面

本文记录了HLS的所使用的大部分指令,参考UG1270,对每个指令进行讲解,并有相关示例。

内核优化

pragma HLS allocation

说明

指定实例限制以限制已实现内核中的资源分配。这定义并可以限制用于实现特定功能、循环、操作或内核的 RTL 实例和硬件资源的数量。 ALLOCATION pragma 在函数体、循环或代码区域内指定。

例如,如果 C 源代码有四个函数 foo_sub 的实例,则 ALLOCATION pragma 可以确保最终 RTL 中只有一个 foo_sub 实例。 C 函数的所有四个实例都使用相同的 RTL 块实现。 这会减少功能使用的资源,但会对性能产生负面影响。

C 代码中的操作,例如加法、乘法、数组读取和写入,可以受 ALLOCATION 编译指示的限制。 在综合期间映射到的内核可以以与运算符相同的方式进行限制。 您可以选择限制组合乘法器内核的数量,而不是限制乘法运算的总数,从而强制使用流水线乘法器执行任何剩余的乘法(反之亦然)。

ALLOCATION pragma 适用于它在其中指定的范围:函数、循环或代码区域。 但是,您可以使用 config_bind 命令的 -min_op 参数在整个设计中全局最小化运算符。

语法

将 pragma 放在将应用的函数、循环或区域的主体内。

#pragma HLS allocation instances=<list>  limit=<value> <type>

其中:

  • instances=<list>:指定函数、运算符或内核的名称。

  • limit=<value>:可选地指定要在内核中使用的实例的限制。

  • <type>:指定分配应用于用于创建设计(例如加法器、乘法器、流水线乘法器和块 RAM)的功能、操作或内核(硬件组件)。 类型指定为以下之一:

    类型 说明
    function 指定分配适用于instance= 列表中列出的函数。 该函数可以是原始 C 或 C++ 代码中的任何函数: - 由 pragma HLS inline 或 set_directive_inline 命令内联,或由 Vivado HLS 自动内联。
    operation 指定分配适用于instance= 列表中列出的操作。
    core 指定 ALLOCATION 适用于内核,这些内核是用于创建设计的特定硬件组件(例如加法器、乘法器、流水线乘法器和块 RAM)。 要使用的实际核心在instance= 选项中指定。 对于内核,您可以指定应使用哪个工具,或者您可以为指定的内核定义限制。

示例

示例1

给定具有多个函数 foo 实例的设计,此示例将硬件内核的 RTL 中的 foo 实例数限制为 2。

#pragma HLS allocation instances=foo limit=2 function

示例2

将函数 my_func 的实现中使用的乘法器操作数限制为 1。此限制不适用于 my_func 之外的任何乘法器,或可能驻留在 my_func 子函数中的乘法器。

void my_func(data_t angle) {
#pragma HLS allocation instances=mul limit=1 operation
...
}

pragma HLS clock

说明

将命名时钟应用于指定函数。

C 和 C++ 设计仅支持单个时钟。 create_clock 指定的时钟周期应用于设计中的所有功能。SystemC 设计支持多个时钟。 可以使用 create_clock 命令指定多个命名时钟,并使用 pragma HLS 时钟应用于各个 SC_MODULE。每个 SC_MODULE 都使用单个时钟进行综合。

语法

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

#pragma HLS clock domain=<clock>

其中,domain=<clock>,指定时钟名称。

示例

示例1

假设一个 SystemC 设计,其中顶层 foo_top 具有时钟端口 fast_clock 和 slow_clock。 但是, foo_top 在其函数中仅使用 fast_clock。 子块 foo_sub 仅使用 slow_clock。在此示例中,在启动 Vivado HLS 工具时指定的 script.tcl 文件中指定了以下 create_clock 命令:

create_clock -period 15 fast_clk
create_clock -period 60 slow_clk

然后在 C 源文件中指定以下编译指示以将时钟分配给指定的函数 foo_sub 和 foo_top:

foo_sub (p, q) {
#pragma HLS clock domain=slow_clock
...
}
void foo_top { a, b, c, d} {
#pragma HLS clock domain=fast_clock
...

pragma HLS expression_balance

说明

有时,基于 C 的规范是用一系列操作编写的,从而导致 RTL 中的一长串操作。 如果时钟周期较短,这会增加设计中的延迟。
默认情况下,Vivado HLS 使用关联和交换属性重新排列操作。
这种重新排列创建了一个平衡树,可以缩短链,潜在地以额外硬件为代价减少设计中的延迟。EXPRESSION_BALANCE 编译指示允许在指定范围内禁用或明确启用此表达式平衡。

语法

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

#pragma HLS expression_balance off

其中,off:在此位置关闭表达式平衡。将此选项排除在编译指示之外会启用表达式平衡,这是默认模式。

示例

示例1

此示例在函数 my_Func 中显式启用表达式平衡:

void my_func(char inval, char incr) {
#pragma HLS expression_balance

示例2

在函数 my_Func 中禁用表达式平衡:

 void my_func(char inval, char incr) { #pragma HLS expression_balance off

pragma HLS latency

说明

为函数、循环和区域的完成指定最小或最大延迟值或两者。 延迟定义为产生输出所需的时钟周期数。函数延迟是函数计算所有输出值并返回所需的时钟周期数。 循环延迟是执行循环的所有迭代的周期数。
Vivado HLS 始终尝试将设计中的延迟降至最低。 当指定 LATENCY pragma 时,工具行为如下:

  • 延迟大于最小值或小于最大值:满足约束。 没有执行进一步的优化。
  • 延迟小于最小值:如果 Vivado HLS 可以实现小于最小指定延迟,它会将延迟扩展到指定值,从而可能增加共享。
  • 延迟大于最大值:如果 Vivado HLS 无法在最大限制内进行调度,则会增加实现指定约束的工作量。 如果它仍然无法满足最大延迟,它会发出警告,并生成超过最大延迟的最小可实现延迟的设计。

语法

将pragma放在必须管理延迟的函数、循环或代码区域的边界内。

#pragma HLS latency min=<int> max=<int>

其中:

  • min=:可选地指定函数、循环或代码区域的最小延迟。
  • max=:可选地指定函数、循环或代码区域的最大延迟。

尽管最小值和最大值都是可选的,但必须指定一个。

示例

示例1

函数foo的最小延迟为4,最大延迟为8:

int foo(char x, char a, char b, char c) { #pragma HLS latency min=4 max=8 char y; y = x*a+b+c; return y
}

示例2

以下示例中的指定循环_1的最大延迟为12。将pragma放置在循环体中,如图所示:

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

示例3

以下示例通过指定零延迟创建代码区域并对需要在同一时钟周期内更改的信号进行分组:

// create a region { } with a latency = 0
{#pragma HLS LATENCY max=0 min=0*data = 0xFF;*data_vld = 1;
}

pragma HLS reset

说明

添加或删除特定状态变量(全局或静态)的重置。复位端口在FPGA中用于在任何时候应用复位信号时将连接到复位端口的寄存器和块RAM恢复为初始值。RTL重置端口的存在和行为由config_RTL配置文件控制。重置设置包括设置重置极性的能力,并指定重置是同步还是异步,但更重要的是,它通过重置选项控制在应用重置信号时重置哪些寄存器。

通过 RESET pragma 可以更好地控制复位。 如果变量是静态或全局变量,则 RESET 编译指示用于显式添加重置,或者可以通过关闭编译指示将变量从重置中移除。 当设计中存在静态或全局数组时,这可能特别有用。

语法

将pragma放在变量生命周期边界内的C源代码中。

#pragma HLS reset variable=<a> off

其中:

  • variable=<a>:指定应用pragma的变量。
  • off:表示未为指定变量生成重置。

示例

示例1

此示例将reset添加到函数foo中的变量a,即使全局重置设置为none或control:

void foo(int in[3], char a, char b, char c, int out[3]) {
#pragma HLS reset variable=a

示例2

即使全局重置设置为state或all,也会从函数foo中的变量a中移除重置。

void foo(int in[3],char a,char b,char c,int out[3]){#pragma HLS reset variable=a off

pragma HLS resource

说明

指定特定的库资源(核心)用于在RTL中实现变量(数组、算术运算或函数参数)。如果未指定资源pragma,Vivado HLS将确定要使用的资源。Vivado HLS使用硬件内核实现代码中的操作。当库中的多个核心可以实现该操作时,可以指定要与资源pragma一起使用的核心。要生成可用核心的列表,请使用list_core命令。

list_core命令用于获取库中可用核心的详细信息。list_core只能在Vivado HLS Tcl命令界面中使用,必须使用set_part命令指定Xilinx设备。如果未选择设备,则list_core命令不起任何作用。

例如,要指定库中用于实现数组的内存元素,请使用资源pragma。这使您可以控制阵列是作为单端口RAM还是双端口RAM实现。这种用法对于顶级函数接口上的数组非常重要,因为与数组关联的内存类型决定了RTL中所需的端口。

可以使用latency=选项指定内核的延迟。对于接口上的块RAM,latency=选项允许您在接口上建模片外非标准SRAM,例如支持延迟为2或3的SRAM。对于内部操作,latency=选项允许使用更多流水线阶段来实现操作。这些额外的流水阶段可以帮助解决RTL合成过程中的时间问题。要使用latency=选项,操作必须具有可用的多级内核。Vivado HLS为所有基本算术运算(加、减、乘、除)、所有浮点运算和所有块RAM提供多级内核。

为获得最佳结果,赛灵思建议您对 C 使用 -std=c99,对 C 和 C++ 使用 -fno-builtin。 要指定 C 编译选项,例如 -std=c99,请使用带有 -cflags 选项的 Tcl 命令 add_files。 或者,使用项目设置中的编辑 CFLAG 按钮 对话框。

语法

将pragma放在定义变量的函数体中的C源代码中。

#pragma HLS resource variable=<variable> core=<core> latency=<int>

其中,

  • variable=<variable>:指定要将资源pragma分配到的数组、算术运算或函数参数的必需参数。
  • core=<core>:指定核心的必需参数,如技术库中所定义。
  • latency=<int>:指定核心的延迟。

示例

示例1

在下面的示例中,指定了一个2级流水线乘法器来实现函数foo的变量c的乘法。由Vivado HLS决定变量d使用哪个内核。

int foo (int a, int b) {int c, d;
#pragma HLS RESOURCE variable=c latency=2
c = a*b;
d = a*c;
return d;
}

示例2

在下面的示例中,变量coefs[128]是顶级函数foo_top的参数。此示例指定使用库中的核心RAM_1P实现系数:

#pragma HLS resource variable=coeffs core=RAM_1P

在RTL中创建的用于访问系数值的端口在RAM_1P核心中定义。

pragma HLS top

说明

将名称附加到函数,然后可以与 set_top 命令一起使用该名称来合成该函数以及从指定顶级调用的任何函数。 这通常用于在 C/C++ 中合成类的成员函数。在活动解决方案中指定编译指示,然后使用具有新名称的 set_top 命令。

语法

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

#pragma HLS top name=<string>

其中, name=<string>,指定 set_top 命令使用的名称。

示例

函数 foo_long_name 被指定为顶级函数,并重命名为 DESIGN_TOP。在代码中放置 pragma 之后,set_top 命令仍然必须从 Tcl 命令行发出,或者从 GUI 项目设置中指定的顶层发出。

void foo_long_name () {
#pragma HLS top name=DESIGN_TOP
...
}
set_top DESIGN_TOP

函数内联

pragma HLS inline

说明

删除作为层次结构中单独实体的函数。 内联后,函数被分解到调用函数中,不再作为 RTL 中单独的层次结构出现。 在某些情况下,内联函数允许函数内的操作与周围的操作更有效地共享和优化。 内联函数不能共享。 这会增加实现 RTL 所需的面积。
INLINE 编译指示适用于它定义的范围,具体取决于它的指定方式:

  • INLINE:没有参数,pragma 意味着指定它的函数应该向上内联到任何调用函数或区域。
  • INLINE OFF:指定在其中指定的函数不应向上内联到任何调用函数或区域中。 这将禁用可能自动内联或作为区域或递归的一部分内联的特定函数的内联。
  • INLINE REGION:这将编译指示应用于区域或分配它的函数体。它向下应用,内联区域或函数的内容,但不通过层次结构递归内联。
  • INLINE RECURSIVE:这将编译指示应用于区域或分配它的函数体。它向下应用,递归地内联区域或函数的内容。

默认情况下,内联仅在函数层次结构的下一级执行,而不是在子函数上执行。但是,递归选项允许您通过层次结构的级别指定内联。

语法

将编译指示放在函数体或代码区域内的 C 源代码中。

#pragma HLS inline <region | recursive | off>

其中,

  • region:可选地指定指定区域(或包含在函数体中)的所有函数都将被内联,适用于该区域的范围。
  • recursive:默认情况下,只执行一级函数内联,指定函数内的函数不内联。 recursive 选项以递归方式内联指定函数或区域内的所有函数。
  • off:禁用函数内联以防止内联指定的函数。 例如,如果在函数中指定了递归,则此选项可以防止特定调用的函数在所有其他函数都被内联时被内联。Vivado HLS 自动内联小函数,使用带有 off 选项的 INLINE pragma 可用于防止这种自动内联。

示例

示例1

本示例内联指定区域内的所有函数,在本例中为 foo_top 的主体,但不内联这些函数内的任何低级函数。

void foo_top { a, b, c, d} {
#pragma HLS inline region
...

示例2

以下示例将 foo_top 主体内的所有函数内联,通过函数层次结构向下递归内联,但函数 foo_sub 未内联。 递归编译指示放在函数 foo_top 中。 禁用内联的编译指示放在函数 foo_sub 中:

foo_sub (p, q) {
#pragma HLS inline off
int q1 = q + 10;
foo(p1,q);// foo_3
...
}
void foo_top { a, b, c, d} {#pragma HLS inline region recursive...foo(a,b);//foo_1foo(a,c);//foo_2foo_sub(a,d);...
}

INLINE 向下应用于函数 foo_top 的内容,但向上应用于调用 foo_sub 的代码。

示例3

本示例将 copy_output 函数内联到任何调用 copy_output 的函数或区域中。

void copy_output(int *out, int out_lcl[OSize * OSize], int output) {
#pragma HLS INLINE// Calculate each work_item's result update locationint stride = output * OSize * OSize;// Work_item updates output filter/image in DDRwriteOut: for(int itr = 0; itr < OSize * OSize; itr++) {#pragma HLS PIPELINEout[stride + itr] = out_lcl[itr];
}

pragma HLS function_instantiate

说明

FUNCTION_INSTANTIATE指令是一种优化技术,它具有维护函数层次结构的区域优势,但提供了一个额外的强大选项:对函数的特定实例执行有针对性的局部优化。 这可以简化围绕函数调用的控制逻辑,并有可能提高延迟和吞吐量。
默认情况下:

  • 函数在 RTL 中保留为单独的层次结构块。
  • 同一层次结构中的所有函数实例都使用单个 RTL 实现(块)。

FUNCTION_INSTANTIATE 编译指示用于为函数的每个实例创建唯一的 RTL 实现,允许根据函数调用对每个实例进行本地优化。 该编译指示利用了当函数被调用时函数的某些输入可能是常量值的事实,并使用它来简化周围的控制结构并生成更小更优化的功能块。

char foo_sub(char inval, char incr) {
#pragma HLS function_instantiate variable=incr
return inval + incr;
}
void foo(char inval1, char inval2, char inval3,
char *outval1, char *outval2, char * outval3) {
*outval1 = foo_sub(inval1, 1);
*outval2 = foo_sub(inval2, 2);
*outval3 = foo_sub(inval3, 3);
}

如果没有 FUNCTION_INSTANTIATE pragma,代码将导致 foo 中函数的所有三个实例的函数 foo_sub 的单个 RTL 实现。 函数 foo_sub 的每个实例都以相同的方式实现。 这对于函数重用和减少函数的每个实例调用所需的区域来说很好,但意味着函数内部的控制逻辑必须更加复杂,以解决每次调用 foo_sub 的变化。

在上面的代码示例中,FUNCTION_INSTANTIATE 编译指示导致函数 foo_sub 的三种不同实现,每种实现都针对 incr 参数独立优化,减少了面积并提高了函数的性能。 在 FUNCTION_INSTANTIATE 优化之后,foo_sub 被有效地转换为三个独立的函数,每个函数都针对 incr 的指定值进行了优化。

语法

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

#pragma HLS function_instantiate variable=<variable>

其中,variable=<variable>定义了用作常量的函数参数的必需参数。

示例

示例1

在以下示例中,放置在函数 swInt) 中的 FUNCTION_INSTANTIATE 编译指示允许函数 swInt 的每个实例相对于 maxv 函数参数进行独立优化:

void swInt(unsigned int *readRefPacked, short *maxr, short *maxc, short
*maxv){
#pragma HLS function_instantiate variable=maxvuint2_t d2bit[MAXCOL];uint2_t q2bit[MAXROW];
#pragma HLS array partition variable=d2bit,q2bit cyclic factor=FACTORintTo2bit<MAXCOL/16>((readRefPacked + MAXROW/16), d2bit);intTo2bit<MAXROW/16>(readRefPacked, q2bit);sw(d2bit, q2bit, maxr, maxc, maxv);
}

接口综合

pragma HLS interface

描述

在基于C 的设计中,所有输入和输出操作都是通过形式函数参数在零时间内执行的。 在 RTL 设计中,这些相同的输入和输出操作必须通过设计接口中的端口执行,并且通常使用特定的 I/O(输入输出)协议进行操作。

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

RTL 实现中的端口源自:

  • 任何指定的功能级协议。
  • 函数参数。
  • 由顶级函数访问并在其作用域外定义的全局变量。

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

  • <mode> 值 ap_ctrl_none、ap_ctrl_hs 或 ap_ctrl_chain 指定。 ap_ctrl_hs 块级 I/O 协议是默认的。
  • 与函数名称相关联。

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

如果访问了全局变量,但所有读写操作都是设计本地的,则在设计中创建资源。RTL 中不需要 I/O 端口。 如果预期全局变量是外部源或目标,请以与标准函数参数类似的方式指定其接口。

当 INTERFACE pragma 用于子函数时,只能使用 register 选项。子函数不支持 <mode> 选项。Vivado HLS 会自动确定任何子函数使用的 I/O 协议。 您无法控制这些端口,除非指定端口是否已注册。

语法

将编译指示放在函数的边界内。

#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>

其中,

  • <mode>:指定函数参数、函数使用的全局变量或块级控制协议的接口协议模式。 模式可以指定为以下之一:
参数 介绍
ap_none 无协议。 该接口是一个数据端口。Vivado HLS 使用模式 ap_none 实现输入参数或任何读/写参数的输入部分。
ap_stable 无协议。 该接口是一个数据端口。 Vivado HLS 假设数据端口在复位后始终稳定,这允许进行内部优化以移除不必要的寄存器。
ap_vld 使用关联的有效端口实现数据端口,以指示数据何时对读取或写入有效。
ap_ack 使用关联的确认端口实现数据端口,以确认数据已被读取或写入。
ap_hs 实现数据端口与相关的有效和确认端口,以提供双向握手,以指示数据何时对读写有效,并确认数据已被读取或写入。
ap_ovld 使用关联的有效端口实现输出数据端口,以指示数据何时可用于读取或写入。
ap_fifo 使用数据输入和输出端口以及相关联的低电平有效 FIFO 空和满端口来实现具有标准 FIFO 接口的端口。只能在读取参数或写入参数上使用此接口。 ap_fifo 模式不支持双向读/写参数。
ap_bus 将指针和传递引用端口实现为总线接口。
ap_memory 将数组参数实现为标准 RAM 接口。 如果您在 Vivado IP 集成器中使用 RTL 设计,则存储器接口显示为离散端口。
bram 将数组参数实现为标准 RAM 接口。 如果您在 Vivado IP 集成器中使用 RTL 设计,则存储器接口显示为单个端口。
axis 将所有端口实现为 AXI4-Stream 接口。
s_axilite 将所有端口实现为 AXI4-Lite 接口。 Vivado HLS 在导出 RTL 过程中生成一组关联的 C 驱动程序文件。
m_axi 将所有端口实现为 AXI4 接口。 可以使用 config_interface 命令指定 32 位(默认)或 64 位地址端口并控制任何地址偏移。
ap_ctrl_none 无块级 I/O 协议。使用 ap_ctrl_none 模式可能会阻止使用 C/RTL 协同仿真功能验证设计。
ap_ctrl_hs 实现一组块级控制端口以启动设计操作并指示设计何时空闲、完成并准备好接收新的输入数据。ap_ctrl_hs 模式是默认的块级 I/O 协议。
ap_ctrl_chain 实现一组块级控制端口以启动设计操作、继续操作,并指示设计何时空闲、完成并准备好接收新的输入数据。ap_ctrl_chain 接口模式与 ap_ctrl_hs 类似,但提供额外的输入信号 ap_continue 以施加背压。 Xilinx 建议在将 Vivado HLS 块链接在一起时使用 ap_ctrl_chain 块级 I/O 协议。
  • port=<name>:指定 INTERFACE pragma 应用的函数参数、函数返回或全局变量的名称。块级 I/O 协议(ap_ctrl_none、ap_ctrl_hs 或 ap_ctrl_chain)可以分配给函数返回值的端口。

  • bundle=<string>:将函数参数分组到 AXI 接口端口中。 默认情况下,Vivado HLS 将所有指定为 AXI4-Lite (s_axilite) 接口的函数参数分组到单个 AXI4-Lite 端口中。 类似地,指定为 AXI4 (m_axi) 接口的所有函数参数都分组到单个 AXI4 端口中。 此选项将具有相同 bundle=<string> 的所有接口端口显式分组到相同的 AXI 接口端口中,并将 RTL 端口命名为 <string> 指定的值。

  • register:注册信号和任何相关协议信号的可选关键字,并使信号至少持续到函数执行的最后一个周期。 此选项适用于以下接口模式:

    ○ ap_none ○ ap_ack ○ ap_vld ○ ap_ovld
    ○ ap_hs ○ ap_stable ○ axis ○ s_axilite

  • register_mode= <forward|reverse|both|off>:与 register 关键字一起使用,此选项指定寄存器是否放置在正向路径(TDATA 和 TVALID)、反向路径 (TREADY)、两条路径(TDATA、TVALID、 和 TREADY),或者如果没有任何端口信号要寄存(关闭)。 默认 register_mode 是 both。 AXI-Stream(axis)侧通道信号被认为是数据信号,只要寄存TDATA 就会寄存。

  • depth=<int>:指定测试台要处理的最大样本数。 此设置指示 Vivado HLS 为 RTL 协同仿真创建的验证适配器中所需的 FIFO 的最大大小。虽然深度通常是一个选项,但只有m_axi 接口需要配置。

  • offset=<string>:控制 AXI4-Lite (s_axilite) 和 AXI4 (m_axi) 接口中的地址偏移。

  • ○ 对于 s_axilite 接口,<string> 指定寄存器映射中的地址。
    ○ 对于 m_axi 接口,<string> 指定以下值之一:

    • direct:生成标量输入偏移端口。

    • slave:生成偏移端口并自动将其映射到 AXI4-Lite 从接口。

    • off:不生成偏移端口。

      config_interface 命令的 -m_axi_offset 选项全局控制设计中所有 M_AXI 接口的偏移端口。

  • clock=<name>:可选择仅为接口模式 s_axilite 指定。 这定义了用于接口的时钟信号。 默认情况下,AXI-Lite 接口时钟与系统时钟相同。 此选项用于为 AXI-Lite (s_axilite) 接口指定单独的时钟。如果捆绑选项用于将多个顶级函数参数分组到单个 AXI-Lite 接口中,则只需在捆绑成员之一上指定时钟选项。

  • num_read_outstanding=<int>:对于 AXI4 (m_axi) 接口,此选项指定在设计停止之前可以向 AXI4 总线发出多少个读取请求,而没有响应。这意味着设计中的内部存储,大小为 FIFO:

    num_read_outstanding*max_read_burst_length*word_size。
    
  • num_write_outstanding=<int>:对于 AXI4 (m_axi) 接口,此选项指定在设计停止之前可以向 AXI4 总线发出多少次写入请求而没有响应。 这意味着设计中的内部存储,大小为 FIFO:

    num_write_outstanding*max_write_burst_length*word_size
    
  • max_read_burst_length=<int>:对于 AXI4 (m_axi) 接口,此选项指定在突发传输期间读取的最大数据值数。

  • max_write_burst_length=<int>:对于 AXI4 (m_axi) 接口,此选项指定在突发传输期间写入的最大数据值数。

  • name=<string>:此选项用于根据您自己的规范重命名端口。生成的 RTL 端口将使用此名称。

示例

示例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

本示例定义了顶级转置功能端口的 INTERFACE 标准。 请注意使用 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 protocol

说明

protocol pragma将代码的一个区域指定为协议区域,除非代码中明确指定,否则Vivado HLS不会在其中插入时钟操作。协议区域可用于手动指定接口协议,以确保最终设计可连接到具有相同I/O协议的其他硬件块。

Vivado HLS不会在操作之间插入任何时钟,包括从函数参数读取或写入的操作,除非在代码中明确指定。因此,RTL遵循读写顺序。

可以指定时钟操作:

  • 在C语言中,使用ap_wait()语句(包括ap_utils.h)。
  • 在C++和SystemC中使用WaIT()语句(包括System.h)进行设计。

ap_wait和wait语句对C和C++设计的仿真没有影响。它们仅由Vivado HLS进行解释。

要创建C代码的区域,请执行以下操作:

  1. 将区域用大括号括起来,{},
  2. 可以选择将其命名以提供标识符。

例如,下面定义了一个名为io_section的区域:

io_section:{
...
}

语法

将pragma放置在区域边界内,以定义该区域的协议。

#pragma HLS protocol <floating | fixed>

其中:

  • floating:协议模式,允许协议区域外的语句与最终RTL中协议区域内的语句重叠。协议区域内的代码保持周期精确,但其他操作可能同时发生。这是默认的协议模式。
  • fixed:协议模式,确保协议区域内外的语句没有重叠。

示例

本示例将区域 io_section 定义为固定协议区域。 将编译指示放在里面的区域:

io_section:{#pragma HLS protocol fixed
...
}

任务级流水

pragma HLS dataflow

说明

DATAFLOW pragma 支持任务级流水线,允许函数和循环在其操作中重叠,增加 RTL实现的并发性,并增加设计的整体吞吐量。

所有操作都在 C 描述中按顺序执行。 在没有任何限制资源的指令(例如 pragma HLS 分配)的情况下,Vivado HLS 寻求最小化延迟并提高并发性。 但是,数据依赖性可以限制这一点。 例如,访问数组的函数或循环必须在完成之前完成对数组的所有读/写访问。 这可以防止下一个消耗数据的函数或循环开始操作。 DATAFLOW 优化使函数或循环中的操作能够在前一个函数或循环完成其所有操作之前开始操作。

当指定 DATAFLOW pragma 时,Vivado HLS 分析顺序函数或循环之间的数据流并创建通道(基于乒乓 RAM 或 FIFO),允许消费者函数或循环在生产者函数或循环完成之前开始操作。

这允许函数或循环并行运行,从而减少延迟并提高 RTL 的吞吐量。如果未指定启动间隔(一个函数或循环开始与下一个循环之间的循环数),Vivado HLS 会尝试最小化启动间隔并在数据可用时立即开始操作。

config_dataflow 命令指定用于数据流优化的默认内存通道和 FIFO 深度。为了使 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 DATAFLOWwr_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 beatoutFifo >> 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 PIPELINEoutx[TILE_HEIGHT*TILE_PER_ROW*TILE_WIDTH*i+TILE_PER_ROW*TILE_WIDTH*m+TILE_WIDTH*j+n] = tile[m][n];}}
}

pragma HLS stream

说明

  • 默认情况下,数组变量实现为RAM。
  • 顶层函数数组参数实现为RAM接口端口。
  • 通用阵列作为RAM实现读写访问。
  • 在涉及 DATAFLOW 优化的子函数中,实现了数组参数使用 RAM 乒乓缓冲通道。
  • 基于循环的 DATAFLOW 优化中,涉及的数组作为 RAM 实现乒乓缓冲通道。

如果存储在阵列中的数据以顺序方式使用或生成,则更有效的通信机制是使用 STREAM pragma 指定的流数据,其中使用 FIFO 而不是 RAM。当顶层函数的参数指定为 INTERFACE 类型 ap_fifo 时,该数组将自动实现为流。

语法

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

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

其中:

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

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

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

    config_dataflow -depth 命令提供了流式传输 DATAFLOW 区域中的所有数组的能力。 此处指定的 depth= 选项会覆盖已分配变量的 config_dataflow 命令。

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

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

    config_dataflow -default_channel fifo 命令全局表示设计中所有阵列上的 STREAM 编译指示。 此处指定的 off 选项会覆盖分配变量的 config_dataflow 命令,并恢复使用基于 RAM 乒乓缓冲区的通道的默认值。

示例

示例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

流水操作

pragma HLS pipeline

说明

PIPELINE pragma 通过允许并发执行操作来减少函数或循环的启动间隔。

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

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

如果 Vivado HLS 无法使用指定的 II 创建设计,它会:

  • 发出警告。
  • 创建具有尽可能低的 II 的设计。

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

语法

将pragma放在函数或循环体的C源代码中。

#pragma HLS pipeline II=<int> enable_flush rewind

其中:

  • II=<int>:指定流水操作的所需启动间隔。Vivado HLS试图满足此要求。基于数据依赖关系,实际结果可能具有更大的启动间隔。默认值II为1。
  • enable_flush:一个可选关键字,用于实现流水,如果流水输入的有效数据变为非活动状态,则该流水线将刷新并清空。此功能仅支持流水线函数:流水线循环不支持此功能。
  • rewind:一个可选关键字,用于启用倒带或连续循环流水,在一个循环迭代结束和下一个迭代开始之间不暂停。只有在顶级函数中有一个单循环(或完美的循环嵌套)时,rewind才有效。此功能仅支持流水线循环:流水线函数不支持此功能。
    循环前的代码段:

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

示例

此示例函数foo以1的启动间隔进行流水化:

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

II的默认值为1,因此本例中不需要特别设置II=1。

pragma HLS occurrence

说明

当流水操作函数或循环时,OCCURRENCE pragma指定区域中的代码执行频率低于封闭函数或循环中的代码。这允许执行频率较低的代码以较慢的速度进行流水线化,并可能在顶级流水操作中共享。要确定OCCURRENCE情况,请执行以下操作:

  • 一个循环迭代N次。但是,循环体的一部分由条件语句启用,因此只执行M次,其中N是M的整数倍。
  • 条件代码的出现速度比循环体的其余部分慢N/M倍。

例如,在执行10次的循环中,循环中的条件语句仅执行2次,occurrence次数为5(或10/2)。使用occurrence指令标识区域,可以使该区域中的函数和循环以比封闭函数或循环慢的更高启动间隔进行流水化操作。

语法

将pragma放在C源代码中的代码区域内。

#pragma HLS occurrence cycle=<int>

其中,cycle=<int>:指定occurrence 次数 N/M。N是执行封闭函数或循环的次数。M是执行条件区域的次数。N必须是M的整数倍。

示例

在此示例中,区域Cond_Region的occurrence 次数为4(它的执行频率比包含它的周围代码的执行频率低四倍):

Cond_Region: {
#pragma HLS occurrence cycle=4
...
}

循环展开

pragma HLS unroll

说明

展开循环以创建多个独立操作而不是单个操作集合。UNROLL 编译指示通过在 RTL 设计中创建循环体的多个副本来转换循环,这允许并行发生一些或所有循环迭代。

C/C++ 函数中的循环默认保持滚动。 当循环滚动时,综合为循环的一次迭代创建逻辑,RTL 设计按顺序为循环的每次迭代执行此逻辑。 循环执行循环归纳变量指定的迭代次数。 迭代次数也可能受循环体内部逻辑的影响(例如,中断条件或对循环出口变量的修改)。 使用 UNROLL 编译指示,您可以展开循环以增加数据访问和吞吐量。

UNROLL 编译指示允许完全或部分展开循环。 完全展开循环会为每次循环迭代在 RTL 中创建循环体的副本,因此可以同时运行整个循环。 部分展开循环可让您指定因子 N,以创建循环体的 N 个副本并相应地减少循环迭代。 要完全展开循环,必须在编译时知道循环边界。 这不是部分展开所必需的。

部分循环展开不需要 N 是最大循环迭代计数的整数因子。 Vivado HLS 添加了退出检查以确保部分展开的循环在功能上与原始循环相同。 例如,给定以下代码:

for(int i = 0; i < X; i++) {pragma HLS unroll factor=2
a[i] = b[i] + c[i];
}

循环展开 2 倍有效地将代码转换为如下代码,其中使用 break 构造确保功能保持不变,并且循环在适当的点退出:

for(int i = 0; i < X; i += 2) {a[i] = b[i] + c[i];
if (i+1 >= X) break;
a[i+1] = b[i+1] + c[i+1];
}

由于最大迭代次数 X 是一个变量,Vivado HLS 可能无法确定其值,因此会向部分展开的循环添加退出检查和控制逻辑。 但是,如果知道指定的展开因子(在此示例中为 2)是最大迭代次数 X 的整数因子,则skip_exit_check 选项可让您删除退出检查和相关逻辑。 这有助于最小化面积并简化控制逻辑。

当使用 DATA_PACK、ARRAY_PARTITION 或 ARRAY_RESHAPE 等编译指示让更多数据在单个时钟周期内访问时,Vivado HLS 会自动展开任何消耗此数据的循环,如果这样做可以提高吞吐量。 该循环可以完全或部分展开,以创建足够的硬件以在单个时钟周期内消耗额外的数据。 此功能是使用 config_unroll 命令控制的。

语法

将编译指示放在循环体中的 C/C++ 源代码中以展开。

#pragma HLS unroll factor=<N> region skip_exit_check

其中:

  • factor=<N>:指定一个非零整数,表示请求部分展开。 循环体重复指定的次数,并相应调整迭代信息。 如果未指定 factor=,则循环完全展开。

  • region:一个可选关键字,用于展开指定循环主体(区域)内的所有循环,而不展开封闭循环本身。

  • skip_exit_check:一个可选关键字,仅在使用factor= 指定部分展开 时才适用。 退出检查的消除取决于循环迭代计数是已知还是未知:

    ○ 固定(已知)边界:如果迭代计数是因子的倍数,则不执行退出条件检查。 如果迭代计数不是因子的整数倍,则该工具:

    1. 防止展开。

    2. 发出警告,必须执行退出检查才能继续。

    ○ 变量(未知)边界:根据要求移除退出条件检查。 必须确保:

    1. 变量bounds是指定展开因子的整数倍。
    2. 实际上不需要退出检查。

示例

示例1

以下示例完全展开函数 foo 中的 loop_1。 将编译指示放在 loop_1 的主体中,如下所示:

loop_1: for(int i = 0; i < N; i++) { #pragma HLS unroll a[i] = b[i] + c[i];
}

示例2

此示例指定展开因子 4 以部分展开函数 foo 的 loop_2,并删除退出检查:

void foo (...) {int8 array1[M];int12 array2[N];...loop_2: for(i=0;i<M;i++) {#pragma HLS unroll skip_exit_check factor=4array1[i] = ...;array2[i] = ...;...}...
}

示例3

下面的示例完全展开函数 foo 中 loop_1 内的所有循环,但由于 region 关键字的存在而不是 loop_1 本身:

void foo(int data_in[N], int scale, int data_out1[N], int data_out2[N]) {int temp1[N];loop_1: for(int i = 0; i < N; i++) {#pragma HLS unroll regiontemp1[i] = data_in[i] * scale;loop_2: for(int j = 0; j < N; j++) {data_out1[j] = temp1[j] * 123;}loop_3: for(int k = 0; k < N; k++) {data_out2[k] = temp1[k] * 456;}}
}

pragma HLS dependence

说明

DEPENDENCE 编译指示用于提供附加信息,这些信息可以克服循环携带依赖性并允许循环流水线化(或以较低的间隔流水线化)。
Vivado HLS 自动检测依赖性:

  • 循环内(循环独立依赖性)。

  • 循环的不同迭代之间(循环进位依赖性)。

这些依赖关系会影响何时可以调度操作,尤其是在函数和循环流水线期间。

  • 循环独立依赖:在同一个循环迭代中访问相同的元素。
for (i=0;i<N;i++){ A[i]=x; y=A[i];
}

循环进位依赖:在不同的循环迭代中访问相同的元素。

for (i=0;i<N;i++) { A[i]=A[i-1]*2;
}

在某些复杂的场景下,自动依赖分析可能过于保守,无法过滤掉错误的依赖。 在某些情况下,例如变量依赖数组索引,或者需要强制执行外部要求时(例如,两个输入永远不会是同一个索引),依赖分析可能过于保守。 DEPENDENCE pragma 允许您显式指定依赖项并解决错误的依赖项。

指定错误的依赖项,而实际上依赖项不是错误的,可能会导致硬件不正确。 在指定依赖项之前,请确保依赖项是正确的(true 或 false)。

语法

将 pragma 放在定义依赖关系的函数的边界内。

#pragma HLS dependence variable=<variable> <class> <type> <direction> distance=<int> <dependent>

其中:

  • variable=<variable>:(可选)指定要考虑依赖关系的变量。

  • <class>:可选地指定依赖关系需要澄清的变量类。有效值包括数组或指针。<class> 和 variable= 不需要一起指定,因为您可以在函数内指定一个变量或一组变量。

  • <type>:有效值包括 intra或 inter。 指定依赖是否为:

    intra:同一循环迭代内的依赖。 当依赖 dependency <type> 被指定为intra,并且 <dependent> 为 false 时,Vivado HLS 可以在循环内自由移动操作,增加它们的移动性并潜在地提高性能或面积。 当 <dependent> 指定为 true 时,必须按照指定的顺序执行操作。
    inter:不同循环迭代之间的依赖关系。 这是默认的 <type>。 如果将dependency <type> 指定为inter,并且<dependent> 为false,则在函数或循环已流水线化、循环展开或部分展开时,它允许Vivado HLS 并行执行操作,并在以下情况下阻止此类并发操作 <dependent> 被指定为 true。

  • <direction>:有效值包括 RAW、WAR 或 WAW。 这仅与循环进位相关性相关,并指定相关性的方向:

    ○ RAW(写后读 - 真实相关性) 写指令使用读指令使用的值。
    ○ WAR(Write-After-Read - 反依赖) 读指令得到一个被写指令覆盖的值。
    ○ WAW(Write-After-Write - 输出依赖) 两条写入指令以一定的顺序写入同一个位置。

  • distance=<int>:指定数组访问的迭代间距离。 仅与循环进位依赖关系相关,其中依赖关系设置为 true。

  • <dependent>:指定是否需要强制执行 (true) 或删除 (false) 依赖项。 默认值为true。

示例

示例1

在以下示例中,Vivado HLS 不了解 cols 的值,并且保守地假设写入 buff_A[1] [col] 和读取 buff_A[1][col] 之间始终存在依赖关系 . 在这样的算法中,cols 不太可能为零,但 Vivado HLS 无法对数据依赖性做出假设。 为了克服这个缺陷,您可以使用 DEPENDENCE 编译指示声明循环迭代之间没有依赖关系(在这种情况下,对于 buff_A 和 buff_B)。

void foo(int rows, int cols, ...)for (row = 0; row < rows + 1; row++) {for (col = 0; col < cols + 1; col++) {#pragma HLS PIPELINE II=1#pragma HLS dependence variable=buff_A inter false#pragma HLS dependence variable=buff_B inter falseif (col < cols) {buff_A[2][col] = buff_A[1][col]; // read from buff_A[1][col]buff_A[1][col] = buff_A[0][col]; // write to buff_A[1][col]buff_B[1][col] = buff_B[0][col];temp = buff_A[0][col];
}

示例2

删除函数 foo 中 loop_1 的相同迭代中 Var1 之间的依赖关系。

#pragma HLS dependence variable=Var1 intra false

示例3

在函数 foo 的 loop_2 中定义对所有数组的依赖,以通知 Vivado HLS 所有读取必须在同一循环迭代中的写入 (RAW) 之后发生。

#pragma HLS dependence array intra RAW true

循环优化

pragma HLS loop_flatten

说明

允许将嵌套循环展平为单个循环层次结构,从而提高延迟。在RTL实现中,需要一个时钟周期才能从外循环移动到内循环和从内循环移动到外循环。展平嵌套循环可以将其作为单个循环进行优化。这节省了时钟周期,可能允许对循环体逻辑进行更大的优化。

将LOOP_FLATTEN应用于循环层次结构中最内层循环的循环体。只有完美和半完美循环可以用这种方式展平:

  • 完美的循环:

    ○ 只有最里面的循环具有循环体内容。
    ○ 循环语句之间没有指定逻辑。
    ○ 所有循环边界都是常量。

  • 半完美环嵌套:

    ○ 只有最里面的循环具有循环体内容。
    ○ 循环语句之间没有指定逻辑。
    ○ 最外层的循环边界可以是变量。

  • 不完美的循环嵌套:当内部循环具有可变边界(或循环体不完全位于内部循环内)时,尝试重新构造代码,或在循环体中展开循环以创建完美的循环嵌套。

语法

将pragma放在嵌套循环边界内的C源代码中。

#pragma HLS loop_flatten off

其中,off:是一个可选关键字,用于防止发生展平。可以防止在展平指定位置中的所有其他循环时展平某些循环。

示例

示例1

将函数foo中的loop_1和循环层次结构中它上面的所有(完美或半完美)循环展平为单个循环。将pragma放置在 loop_1的主体中。

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

示例2

防止loop_1中的循环展平:

loop_1: for(i=0;i< num_samples;i++) {#pragma HLS loop_flatten off...

pragma HLS loop_merge

说明

将连续循环合并到单个循环中,以减少总体延迟、增加共享并改进逻辑优化。合并循环可以:

  • 减少RTL中在循环体实现之间转换所需的时钟周期数。
  • 允许并行实现循环。

LOOP_MERGE pragma将寻求合并其放置范围内的所有循环。例如,如果在循环体中应用 LOOP_MERGE pragma,Vivado HLS将指令应用于循环中的任何子循环,但不应用于循环本身。

合并循环的规则是:

  • 如果循环边界是变量,则它们必须具有相同的值(迭代次数)。
  • 如果循环边界为常量,则最大常量值用作合并循环的边界。
  • 不能合并具有可变边界和常量边界的循环。
  • 要合并的循环之间的代码不能有单侧作用。多次执行此代码应生成相同的结果(允许a=b,不允许a=a+1)。
  • 循环包含FIFO读取时无法合并。合并会更改读取的顺序。从FIFO或FIFO接口的读取必须始终按顺序进行。

语法

将C源代码中的pragma放在所需的代码范围或区域内:

#pragma HLS loop_merge force

其中,force是一个可选关键字,用于强制合并循环,即使Vivado HLS发出警告。在这种情况下,您必须手动确保合并的循环将正常工作。

示例

将函数foo中的所有连续循环合并到单个循环中。

void foo (num_samples, ...) {#pragma HLS loop_mergeint i;...loop_1: for(i=0;i< num_samples;i++) {...

loop_2 内的所有循环(但不是loop_2本身)通过使用force选项合并。将pragma放置在loop_2的主体中。

loop_2: for(i=0;i< num_samples;i++) { #pragma HLS loop_merge force ...

pragma HLS loop_tripcount

说明

TRIPCOUNT指令可应用于循环,以手动指定循环执行的总迭代次数。TRIPCOUNT指令仅用于分析,不影响合成结果。

Vivado HLS报告每个循环的总延迟,即执行循环所有迭代的时钟周期数。因此,循环延迟是循环迭代次数或tripcount的函数。tripcount可以是一个常量值。它可能取决于循环表达式中使用的变量值(例如,x<y),也可能取决于循环中使用的控制语句。在某些情况下,Vivado HLS无法确定tripcount,且延迟未知。这包括用于确定tripcount的变量为:

  • 输入参数。
  • 通过动态操作计算的变量。

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

语法

将pragma放在循环体中的C源代码中:

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

其中:

  • max=<int>:指定循环迭代的最大次数。
  • min=<int>:指定循环迭代的最小次数。
  • avg=<int>:指定循环迭代的平均次数。

示例

函数foo中的loop_1指定最小tripcount为12,最大tripcount为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;}
}

数组优化

pragma HLS array_map

说明

将多个较小的数组组合成一个大型阵列,以帮助减少块 RAM 资源。通常使用 pragma HLS array_map 命令(具有相同的实例=目标)将多个较小的数组组合成一个较大的数组。 然后可以将这个更大的数组定位到单个更大的内存(RAM 或 FIFO)资源。当设备支持时,每个数组都映射到块 RAM 或 UltraRAM。 FPGA 中提供的基本块 RAM 单元是 18K。 如果许多小数组没有使用完整的 18K,更好地利用 Block RAM 资源是将许多小数组映射到一个更大的数组中。(如果块 RAM 大于 18K,它们会自动映射到多个 18K 单元。) ARRAY_MAP 编译指示支持两种将小数组映射到大数组的方法: - 水平映射:这对应于通过连接原始数组来创建一个新数组。 在物理上,这被实现为具有更多元素的单个数组。

  • 垂直映射:这对应于通过连接数组中的原始字段来创建一个新数组。 在物理上,这被实现为具有更大位宽的单个数组。
    数组按照指定编译指示的顺序连接,从以下位置开始: - 水平映射的目标元素零。
  • 垂直映射的位零。

语法

将编译指示放在 C 源代码中定义数组变量的函数边界内。

#pragma HLS array_map variable=<name> instance=<instance>  <mode> offset=<int>

其中:

  • variable=<name> :指定要映射到新目标数组 <instance> 的数组变量的必需参数。

  • instance=<instance>:指定要合并数组的新数组的名称。

  • <mode>:可选地将数组映射指定为水平或垂直。
    ○ 水平映射是默认的 <mode>,将数组连接起来形成一个新数组,其中包含更多元素。
    ○ 垂直映射将数组串联起来,形成一个新的具有更长单词的数组。

  • offset=<int>:仅适用于水平类型数组映射。 偏移量指定在将数组映射到新数组 <instance> 之前要应用的整数值偏移量。 (如果未指定偏移量,Vivado HLS 会自动计算所需的偏移量以避免重叠数组元素。)例如:

    ○ 数组变量的元素 0 映射到新目标的元素 <int>
    ○ 其他元素映射到新目标的 <int+1><int+2>.…。

示例

示例1

函数 foo 中的数组 array1 和 array2 被映射到单个数组中,在以下示例中指定为 array3:

void foo (...) { int8 array1[M]; int12 array2[N]; #pragma HLS ARRAY_MAP variable=array1 instance=array3 horizontal #pragma HLS ARRAY_MAP variable=array2 instance=array3 horizontal ...loop_1: for(i=0;i<M;i++) { array1[i] = ...; array2[i] = ...; ...} ...
}

示例2
本示例将函数 foo 中的数组 A[10] 和数组 B[15] 水平映射到单个新数组 AB[25]。
元素AB[0] 将与A[0] 相同。元素AB[10] 将与B[0] 相同,因为未指定偏移= 选项。数组 AB[25] 的位宽将是 A[10] 或 B[15] 的最大位宽。

#pragma HLS array_map variable=A instance=AB horizontal
#pragma HLS array_map variable=B instance=AB horizontal

示例3
下面的示例将数组 C 和 D 垂直串联成一个新的数组 CD,并合并 C 和 D 的位宽。CD 中的元素数是原始数组 C 或 D 中的最大值:

#pragma HLS array_map variable=C instance=CD vertical
#pragma HLS array_map variable=D instance=CD vertical

pragma HLS array_partition

说明

将数组划分为更小的数组或单个元素。
这种分区:

  • 导致 RTL 具有多个小存储器或多个寄存器,而不是一个大存储器。
  • 有效增加存储的读写端口数量。
  • 潜在地提高设计的吞吐量。
  • 需要更多内存实例或寄存器。

语法

将编译指示放在 C 源代码中定义数组变量的函数边界内。

#pragma HLS array_partition variable=<name> <type> factor=<int> dim=<int>

其中:

  • variable=<name>:指定要分区的数组变量的必需参数。

  • <type>:可选地指定分区类型。 默认类型是完整的。支持以下类型:

    ○ cyclic:循环分区通过交错原始数组中的元素来创建更小的数组。 通过在返回到第一个数组之前将一个元素放入每个新数组以重复循环直到数组完全分区,数组被循环分区。
    例如,如果使用 factor=3:元素 0 分配给第一个新数组;元素 1 分配给第二个新数组;元素 2 被分配给第三个新数组;元素 3 再次分配给第一个新数组。

    block:块分区从原始数组的连续块创建更小的数组。 这有效地将数组拆分为 N 个相等的块,其中 N 是由 factor= 参数定义的整数。

    complete:完整分区将数组分解为单个元素。 对于一维数组,这对应于将内存解析为单独的寄存器。
    这是默认的 <type>

  • factor=<int>:指定要创建的较小数组的数量。对于complete的类型分区,不需要指定factor。 对于块和循环分区,需要 factor。

  • dim=<int>:指定要分区的多维数组的哪个维度。 指定为从 0 到 N 的整数,对于具有 N 维的数组:

    ○ 如果使用值 0,则多维数组的所有维都使用指定的类型和因子选项进行分区。
    ○ 任何非零值仅对指定维度进行分区。 例如,如果使用值 1,则仅对第一个维度进行分区。

示例

示例1

本示例使用块分区将 13 个元素的数组 AB[13] 分区为四个数组:

#pragma HLS array_partition variable=AB block factor=4

因为四不是 13 的整数因数所以三个新数组各有三个元素,一个数组有四个元素 (AB[9:12])。

示例2

此示例将二维数组的第二维 AB[6][4] 划分为两个新的维度为 [6][2] 的数组:

#pragma HLS array_partition variable=AB block factor=2 dim=2

示例3

本示例将二维 in_local 数组的第二维划分为单独的元素。

int in_local[MAX_SIZE][MAX_DIM];
#pragma HLS ARRAY_PARTITION variable=in_local complete dim=2

pragma HLS array_reshape

说明

将阵列分区与垂直阵列映射相结合。

ARRAY_RESHAPE 编译指示结合了 ARRAY_PARTITION 的效果,将数组分解为更小的数组,以及 ARRAY_MAP 的垂直类型的效果,通过增加位宽来连接数组元素。 这减少了消耗的块 RAM 数量,同时提供了分区的主要好处:对数据的并行访问。 此编译指示创建了一个元素更少但位宽更大的新数组,从而允许在单个时钟周期内访问更多数据。

参考以下代码:

void foo (...) {
int array1[N];
int array2[N];
int array3[N];
#pragma HLS ARRAY_RESHAPE variable=array1 block factor=2 dim=1
#pragma HLS ARRAY_RESHAPE variable=array2 cycle factor=2 dim=1
#pragma HLS ARRAY_RESHAPE variable=array3 complete dim=1
...
}

ARRAY_RESHAPE 编译指示将数组转换为下图所示的形式:

语法

将编译指示放在 C 源代码中定义数组变量的函数区域内。

#pragma HLS array_reshape variable=<name> <type> factor=<int> dim=<int>

其中:

  • <name>:一个必需的参数,指定要重塑的数组变量。

  • <type>:可选地指定分区类型。 默认类型是完整的。 支持以下类型:

    cyclic:循环重塑通过交错原始数组中的元素来创建更小的数组。 例如,如果使用factor=3,则将元素0分配给第一个新数组,将元素1分配给第二个新数组,将元素2分配给第三个新数组,然后再次将元素3分配给第一个新数组 . 最后一个数组是新数组的垂直串联(词串联,以创建更长的词)到单个数组中。

    block:块重塑从原始数组的连续块创建更小的数组。 这有效地将数组拆分为 N 个相等的块,其中 N 是由 factor= 定义的整数,然后将 N 个块合并为一个字宽*N 的数组。

    complete:完全重塑将数组分解为临时的单个元素,然后将它们重新组合成一个具有更宽单词的数组。 对于一维数组,这相当于创建一个非常宽的寄存器(如果原始数组是 M 位的 N 个元素,则结果是一个 N*M 位的寄存器)。 这是数组整形的默认类型。

  • factor=<int>:指定当前数组除以的数量(或要创建的临时数组的数量)。 factor=2时,数组分成两半,同时将位宽加倍。
    factor=3时将数组分成三份,位宽为三倍。对于complete的类型分区,不需要指定factor。 对于块和循环分区,需要 factor。

  • dim=<int>:指定要分区的多维数组的哪个维度。 对于具有 N 维的数组,指定为从 0 到 N 的整数:

    ○ 如果使用值 0,则多维数组的所有维度都使用指定的类型和因子选项进行分区。
    ○ 任何非零值仅对指定维度进行分区。 例如,如果使用值 1,则仅对第一个维度进行分区。

  • object:仅与容器数组相关的关键字。 当指定关键字时,ARRAY_RESHAPE 编译指示应用于容器中的对象,重塑容器内对象的所有维度,但保留容器本身的所有维度。当未指定关键字时,编译指示适用于容器数组而不是对象。

示例

示例1

使用块映射将具有 17 个元素的 8 位数组 AB[17] 重新整形(分区和映射)为具有五个元素的新 32 位数组。

#pragma HLS array_reshape variable=AB block factor=4

factor=4 表示数组应该被分成四份。 因此,17 个元素被重新整形为 5 个元素的数组,位宽是其四倍。 在这种情况下,最后一个元素 AB[17] 被映射到第五个元素的低八位,而第五个元素的其余部分为空。

示例2

将二维数组 AB[6][4] 重塑为维度 [6][2] 的新数组,其中维度 2 具有两倍的位宽:

#pragma HLS array_reshape variable=AB block factor=2 dim=2

示例3

将函数 foo 中的三维 8 位数组 AB[4][2][2] 重塑为一个新的单元素数组(一个寄存器),128 位宽(4*2*2*8)

#pragma HLS array_reshape variable=AB complete dim=0

dim=0 表示重塑数组的所有维度。

结构体打包

pragma HLS data_pack

说明

将结构的数据字段打包成一个具有更宽字宽的标量。

DATA_PACK 编译指示用于将结构体的所有元素打包到单个宽向量中以减少变量所需的内存,同时允许同时读取和写入结构体的所有成员。 生成的新宽字的位对齐可以从结构域的声明顺序推断出来。 第一个字段采用向量的 LSB,结构体的最后一个元素与向量的 MSB 对齐。

如果结构包含数组,则 DATA_PACK 编译指示执行与 ARRAY_RESHAPE 编译指示类似的操作,并将重构后的数组与结构中的其他元素组合。 在结构中声明的任何数组都被完全分区并重新整形为一个宽标量,并与其他标量字段打包在一起。 但是,不能使用 DATA_PACK 和 ARRAY_PARTITION 或 ARRAY_RESHAPE 优化结构,因为这些编译指示是互斥的。

在对具有大型数组的结构对象使用 DATA_PACK 优化时,应该谨慎行事。 如果一个数组有 4096 个 int 类型的元素,这将导致一个宽度为 4096*32=131072 位的向量(和端口)。 Vivado HLS 可以创建此 RTL 设计,但是逻辑综合不太可能在 FPGA 实现期间对其进行路由。

通常,赛灵思建议使用任意精度(或位精度)数据类型。标准 C 类型基于 8 位边界(8 位、16 位、32 位、64 位),但是,在设计中使用任意精度数据类型可让您在 C 代码中指定确切的位大小。 位精确宽度导致硬件运算符更小、更快。 这允许在 FPGA 中放置更多逻辑,并使逻辑以更高的时钟频率执行。 但是,如果需要,DATA_PACK 编译指示还允许您沿 8 位边界对齐打包结构中的数据。

如果要使用 AXI4 接口实现结构端口,应该考虑使用 DATA_PACK <byte_pad> 选项来自动将结构的成员元素对齐到 8 位边界。 AXI4-Stream 协议要求 IP 的 TDATA 端口的宽度为 8 的倍数。定义 TDATA 端口宽度不是 8 的倍数的 AXI4-Stream IP 是违反规范的,因此,它是一个 要求将 TDATA 宽度四舍五入为字节倍数。

语法

将编译指示放在要打包的 struct 变量的定义附近:

#pragma HLS data_pack variable=<variable> instance=<name> <byte_pad>

其中,

  • variable=<variable>:是要打包的变量。

  • instance=<name>:指定打包后结果变量的名称。 如果未指定 <name>,则使用输入 <variable>

  • <byte_pad>:可选地指定是否在8 位边界(8 位、16 位、24 位…)上打包数据。 此选项支持的两个值是:

    struct_level:首先打包整个结构,然后向上填充到下一个 8 位边界。
    field_level:首先在 8 位边界上填充结构的每个单独元素(字段),然后打包结构。

示例

示例1

将具有三个 8 位字段字段(R、G、B)的结构数组 AB[17] 打包成一个新的 24 位 17 元素数组。

typedef struct{
unsigned char R, G, B;
} pixel;
pixel AB[17];
#pragma HLS data_pack variable=AB

示例2

将具有函数 foo 中的三个 8 位字段(typedef struct {unsigned char R, G, B;} pixel)的结构指针 AB 打包成一个新的 24 位指针。

typedef struct{unsigned char R, G, B;
} pixel;
pixel AB;
#pragma HLS data_pack variable=AB

示例3

在此示例中,为 rgb_to_hsv 函数的输入和输出参数指定了 DATA_PACK 编译指示,以指示编译器在 8 位边界上打包结构以改进内存访问:

void rgb_to_hsv(RGBcolor* in, // Access global memory as RGBcolor structwise HSVcolor* out, // Access Global Memory as HSVcolor structwise int size) { #pragma HLS data_pack variable=in struct_level #pragma HLS data_pack variable=out struct_level ...
}

HLS-指令使用指南相关推荐

  1. HLS - intel HLS 指令使用指南

    写在前面 本文主要介绍关于intel的HLS工具的相关指令的使用方法. 仿真设置 正常默认方式仿真,对模拟器函数调用是按照顺序的,前面的调用没有返回前,不会发生新的调用. ihc_hls_enqueu ...

  2. ESP8266WiFi模块AT指令入门指南

    ESP8266WiFi模块AT指令入门指南      1.1.模块 AP 模式下做 TCP serve AT+CWMODE=2                        开启 AP 模式(串口助手 ...

  3. mysql kill 指令_MySQL kill指令使用指南

    KILL [CONNECTION | QUERY] processlist_id 在Mysql中每个连接都是单独线程运行,可以使用语句 KILL processlist_id statement.来终 ...

  4. ios快捷指令使用指南-TUST校园网认证自动连接登录

    ios快捷指令使用指南-TUST校园网认证自动连接登录 2021.9.1更新: 校园网登录页面更新后,部分规则变动,更新后快捷指令链接如下: https://www.icloud.com/shortc ...

  5. NEON指令优化指南学习之一

    RM平台NEON指令的编译和优化 本文介绍了ARM平台基于ARM v7-A架构的ARM Cortex-A系列处理器(Cortex-A5, Cortex-A7,Cortex-A8, Cortex-A9, ...

  6. AngularJS 指令实践指南(二)

    这个系列教程的第一部分给出了AngularJS指令的基本概述,在文章的最后我们介绍了如何隔离一个指令的scope.第二部分将承接上一篇继续介绍.首先,我们会看到在使用隔离scope的情况下,如何从指令 ...

  7. SIM800C模块AT指令编程指南

    SIM800C是一款四频GSM/GPRS模块,为城堡孔封装.其性能稳定,外观小巧,,能满足客户的多种需求.SIM80C工作频率为GSM/GPRS850/900/180/190OMHz,可以低功耗实现语 ...

  8. wpa_cli 操作指令使用指南

    原文:https://blog.csdn.net/hanlunko2016/article/details/81276248 一. Wpa_supplicant 启动脚本:       wpa_sup ...

  9. 一文带你解决Git那些事~ git 指令 实用指南

    Git是目前世界上最先进的分布式版本控制系统.虽然日常中最多的在上面找项目代码...... Git有非常完善版本控制流程,适合团体和个人的开发,提升开发效率,企业开发必备!日常使用的时候可能接触更多的 ...

  10. v-slot vue2.6新增指令使用指南

    子组件 <template><div class="wrapper"><slot name="demo" :msg="m ...

最新文章

  1. 只需3分钟,就能轻松创建 一个SpreadJS的React项目
  2. vsphere中虚机的cpu热插拔和内存热添加
  3. 同实例下复制表的2种方法
  4. [转]The Top 10 Attributes of a Great Programmer
  5. Android中从源码分析关于AsyncTask的使用
  6. react build后直接从浏览器打开
  7. Raki的网络流24题题解总结
  8. ASCII,UTF-8,GBK编码简介
  9. C语言中sprintf函数的用法
  10. 【老生谈算法】matlab实现Dijkstra最短路算法源码——Dijkstra算法
  11. 【重磅】《演化学习:理论与算法进展》| 南大周志华、俞扬、钱超重要成果...
  12. mui ajax 下拉,mui下拉菜单
  13. 软件开发项目计划编制过程[转]
  14. iOS开发简历这样写,面试电话接到手软
  15. 记一次vue2.0+vue-video-player实现hls播放全过程
  16. python 图书管理_Python-图书管理系统
  17. 第三章第三节、他是一个箍桶匠
  18. 手把手教你读财报----银行业---第二课
  19. 编写高效的软件缺陷报告
  20. Node与express开发

热门文章

  1. java-php-python-ssm网上拍卖系统2021计算机毕业设计
  2. hashMap线程安全
  3. rapidio 网络枚举--深度优先遍历算法
  4. monolog 日志库
  5. java栈和队列的实现
  6. R7000P双系统装机记录
  7. FTX US与IEX合作,共同搭建一个受监管、负责任和透明的市场结构
  8. 电子商务网上开店三种模式
  9. PHP微信小程序/抽奖小程序开源源码
  10. 如何优化冷启动的时间