目录
1. BIND_OP_STORAGE 概述
BIND_OP_STORAGE 其实是两个优化指令的合称:BIND_OP 和 BIND_STORAGE。
1.1 BIND_OP
Vitis HLS 使用特定 impl 来实现代码中的运算。BIND_OP 编译指示用于指定针对每个特定变量,都应将一项运算(mul、add、div)映射到特定器件资源,以便在 RTL 内实现 (impl)。如果不指定 BIND_OP 编译指示,Vitis HLS 会自动判定用于运算的资源。
1.2 BIND_STORAGE
BIND_STORAGE 编译指示用于将代码中的变量(阵列或函数实参)分配给 RTL 中的特定存储器类型 (type)。如果不指定此编译指示,那么 Vitis HLS 工具会判定要分配的存储器类型。HLS 工具在硬件中使用指定的实现 (impl) 来实现该存储器。
2. 语法解析
2.1 BIND_OP
#pragma HLS bind_op variable=<variable> op=<type> impl=<value> latency=<int>
- variable=<variable>:用于定义要将 BIND_OP 编译指示分配到的变量
- op=<type>:用于定义要绑定到特定实现资源的运算。受支持的函数运算包括:mul、add 和 sub 受支持的浮点运算包括:fadd、fsub、fdiv、fexp、flog、fmul、frsqrt、frecip、fsqrt、dadd、dsub、ddiv、dexp、dlog、dmul、drsqrt、drecip、dsqrt、hadd、hsub、hdiv、hmul 和 hsqrt。
- impl=<value>:定义用于指定运算的实现。受支持的函数运算实现包括 fabric 和 dsp。受支持的浮点运算实现包括:fabric、meddsp、fulldsp、maxdsp 和 primitivedsp。
- latency=<int>:定义运算的实现的默认时延。有效的时延值因指定的 op 和 impl 而异。默认值为 -1,即交由 Vitis HLS 选择时延。
支持的整数运算操作:
OP | Impl | Min Latency | Max Latency |
add | fabric | 0 | 4 |
add | dsp | 0 | 4 |
mul | fabric | 0 | 4 |
mul | dsp | 0 | 4 |
sub | fabric | 0 | 4 |
sub | dsp | 0 | 0 |
支持的浮点数运算操作:
操作 | 实现 | Min Latency | Max Latency |
fadd | fabric | 0 | 13 |
fadd | fulldsp | 0 | 12 |
fadd | primitivedsp | 0 | 3 |
fexp | meddsp | 0 | 21 |
fmul | maxdsp | 0 | 7 |
… | … | … | … |
快速记忆方法:
受支持的函数运算包括:
mul、add 、 sub
受支持的浮点运算包括:
fadd fsub fdiv fexp flog fmul frsqrt frecip fsqrt
dadd dsub ddiv dexp dlog dmul drsqrt drecip dsqrt
hadd hsub hdiv hmul hsqrt
Impl解释:
- fabric: 使用 FPGA 的逻辑单元和 RAM 块来实现算法。
- dsp: 使用 FPGA 的 DSP 功能来实现算法,例如 FFT、FIR、DDS 等。
- meddsp: 使用 FPGA 的 DSP 功能和一些逻辑单元来实现算法,适合中等复杂度的算法。
- maxdsp: 使用 FPGA 的 DSP 功能和更多的逻辑单元来实现算法,适合高复杂度的算法。
- fulldsp: 使用 FPGA 的所有可用资源来实现算法,包括 DSP、RAM 和逻辑单元。
2.2 BIND_OP 用法示例
double mult (double a, double b) {
double c, d;
#pragma HLS BIND_OP variable=c op=dmul impl=fabric latency=2
#pragma HLS BIND_OP variable=d op=dmul impl=fulldsp latency=10
c = a * b;
d = a * c;
return d;
}
解释:
- 指定变量 c 的双精度浮点运算,实现方式fabric,延时为2;
- 指定变量 d 的双精度浮点运算,实现方式为fulldsp,延时为10;
Vitis HLS 编译器得到的结果如下:
从命名可以看出:
- dmul: 双精度乘法器。
- 64ns: 输入数据的位宽是 64 位,ns的含义不明。
- 64: 输出数据的位宽是 64 位,3和5的含义不明。
- max_dsp: 实现方法是使用最大数量的 DSP 功能。
- U2: 模块的实例名字。
注意:
- 编译器对待手动指定latency,会优先满足时钟频率上的要求,然后尽量靠近用户指定的latency。
- 如上述案例,虽然指定了latency=2,但是编译器需要latency等于3才能满足时钟频率要求。
- 给定更多latency,系统能运行在更高的时钟频率上。
2.3 BIND_STORAGE
#pragma HLS bind_storage variable=<variable> type=<type> [ impl=<value> latency=<int> ]
- variable=<variable>:定义要将 BIND_STORAGE 编译指示分配到的变量。
- type=<type>:定义要绑定到指定变量的存储器的类型。受支持的类型包括:fifo、ram_1p、ram_1wnr、ram_2p、ram_s2p、ram_t2p、rom_1p、rom_2p、rom_np。
- impl=<value>:定义指定存储器类型的实现。受支持的实现包括:bram、bram_ecc、lutram、uram、uram_ecc、srl、memory 和 auto,如下所述。
- latency=<int>:定义用于绑定类型的默认时延。如下表所示,有效的时延值因指定的 type 和 impl 而异。默认值为 -1,即交由 Vitis HLS 选择时延。
存储类型:
类型 | 描述 |
FIFO | FIFO。Vitis HLS 可判定如何在 RTL 中将其实现,除非指定 -impl 选项。 |
RAM_1WNR | 含 1 个写入端口和 N 个读取端口的 RAM,内部使用 N 个 bank。 |
RAM_2P | 双端口 RAM,允许在某一端口上执行读操作,并在另一个端口上执行读写操作。 |
RAM_S2P | 双端口 RAM,允许在某一端口上执行读操作,并在另一个端口上执行写操作。 |
RAM_T2P | 真正的双端口 RAM,支持在 2 个端口上执行读写操作。 |
ROM_1P | 单端口 ROM。Vitis HLS 可判定如何在 RTL 中将其实现,除非指定 -impl 选项。 |
ROM_2P | 双端口 ROM。 |
ROM_NP | 多端口 ROM。 |
实现类型:
名称 | 描述 |
MEMORY | 通用存储器,允许 Vivado 工具选择实现。 |
URAM | UltraRAM 资源 |
URAM_ECC | 含 ECC 的 UltraRAM |
SRL | 移位寄存器逻辑资源,Shift Register Look-up Table(移位寄存器查找表) |
LUTRAM | 分布式 RAM 资源 |
BRAM | 块 RAM 资源 |
BRAM_ECC | 含 ECC 的块 RAM |
AUTO | Vitis HLS 会自动判定变量的实现。 |
受支持的存储器类型、实现和时延组合:
操作 | 实现 | Min Latency | Max Latency |
FIFO | BRAM | 0 | 0 |
FIFO | LUTRAM | 0 | 0 |
FIFO | MEMORY | 0 | 0 |
FIFO | SRL | 0 | 0 |
FIFO | URAM | 0 | 0 |
RAM_1P | AUTO | 1 | 3 |
RAM_1P | BRAM | 1 | 3 |
RAM_1P | LUTRAM | 1 | 3 |
RAM_1P | URAM | 1 | 3 |
RAM_1WNR | AUTO | 1 | 3 |
RAM_1WNR | BRAM | 1 | 3 |
RAM_1WNR | LUTRAM | 1 | 3 |
RAM_1WNR | URAM | 1 | 3 |
RAM_2P | AUTO | 1 | 3 |
RAM_2P | BRAM | 1 | 3 |
RAM_2P | LUTRAM | 1 | 3 |
RAM_2P | URAM | 1 | 3 |
操作 | 实现 | Min Latency | Max Latency |
RAM_S2P | BRAM | 1 | 3 |
RAM_S2P | BRAM_ECC | 1 | 3 |
RAM_S2P | LUTRAM | 1 | 3 |
RAM_S2P | URAM | 1 | 3 |
RAM_S2P | URAM_ECC | 1 | 3 |
RAM_T2P | BRAM | 1 | 3 |
RAM_T2P | URAM | 1 | 3 |
ROM_1P | AUTO | 1 | 3 |
ROM_1P | BRAM | 1 | 3 |
ROM_1P | LUTRAM | 1 | 3 |
ROM_2P | AUTO | 1 | 3 |
ROM_2P | BRAM | 1 | 3 |
ROM_2P | LUTRAM | 1 | 3 |
ROM_NP | BRAM | 1 | 3 |
ROM_NP | LUTRAM | 1 | 3 |
2.4 BIND_STORAGE 示例
#pragma HLS bind_storage variable=coeffs type=RAM_1P impl=bram
解释:
指令告诉HLS工具将 coeffs 数组绑定到一个单端口RAM上,并且使用块RAM作为其实现方式。
3. 实例演示
#define BUFFER_SIZE 1024
#define DATA_SIZE 4096
// TRIPCOUNT identifier
const unsigned int c_len = DATA_SIZE / BUFFER_SIZE;
const unsigned int c_size = BUFFER_SIZE;
extern "C" {
void vadd(const unsigned int* in1, // Read-Only Vector 1
const unsigned int* in2, // Read-Only Vector 2
unsigned int* out_r, // Output Result
int size // Size in integer
) {
unsigned int v1_buffer[BUFFER_SIZE]; // Local memory to store vector1
unsigned int v2_buffer[BUFFER_SIZE]; // Local memory to store vector2
unsigned int vout_buffer[BUFFER_SIZE]; // Local Memory to store result
// Using the BIND_OP pragma the user can specify the operator, implementation
// and latency
#pragma HLS BIND_OP variable = v1_buffer op = mul impl = DSP latency = 2
#pragma HLS BIND_OP variable = v2_buffer op = mul impl = DSP latency = 2
#pragma HLS BIND_OP variable = vout_buffer op = add impl = DSP
// Using the BIND STORAGE the used can choose the type, resource and latency
#pragma HLS BIND_STORAGE variable = v1_buffer type = RAM_1P impl = BRAM latency = 2
#pragma HLS BIND_STORAGE variable = v2_buffer type = RAM_1P impl = LUTRAM latency = 2
#pragma HLS BIND_STORAGE variable = vout_buffer type = RAM_1P impl = URAM
// Per iteration of this loop perform BUFFER_SIZE vector addition
for (int i = 0; i < size; i += BUFFER_SIZE) {
#pragma HLS LOOP_TRIPCOUNT min = c_len max = c_len
int chunk_size = BUFFER_SIZE;
// boundary checks
if ((i + BUFFER_SIZE) > size) chunk_size = size - i;
// Auto-pipeline is going to apply pipeline to these loops
read1:
for (int j = 0; j < chunk_size; j++) {
#pragma HLS LOOP_TRIPCOUNT min = c_size max = c_size
v1_buffer[j] = in1[i + j] * in1[i + j];
}
read2:
for (int j = 0; j < chunk_size; j++) {
#pragma HLS LOOP_TRIPCOUNT min = c_size max = c_size
v2_buffer[j] = in2[i + j] * in2[i + j];
}
vadd:
for (int j = 0; j < chunk_size; j++) {
// As the outer loop is not a perfect loop
#pragma HLS loop_flatten off
#pragma HLS LOOP_TRIPCOUNT min = c_size max = c_size
// perform vector addition
vout_buffer[j] = v1_buffer[j] + v2_buffer[j];
}
// burst write the result
write:
for (int j = 0; j < chunk_size; j++) {
#pragma HLS LOOP_TRIPCOUNT min = c_size max = c_size
out_r[i + j] = vout_buffer[j];
}
}
}
}
其中关键的优化指令如下:
// Using the BIND_OP pragma the user can specify the operator, implementation and latency
#pragma HLS BIND_OP variable = v1_buffer op = mul impl = DSP latency = 2
#pragma HLS BIND_OP variable = v2_buffer op = mul impl = DSP latency = 2
#pragma HLS BIND_OP variable = vout_buffer op = add impl = DSP
// Using the BIND STORAGE the used can choose the type, resource and latency
#pragma HLS BIND_STORAGE variable = v1_buffer type = RAM_1P impl = BRAM latency = 2
#pragma HLS BIND_STORAGE variable = v2_buffer type = RAM_1P impl = LUTRAM latency = 2
#pragma HLS BIND_STORAGE variable = vout_buffer type = RAM_1P impl = URAM
运行 Vitis HLS 编译器,我们得到如下结果:
================================================================
== Pragma Report
================================================================
* Valid Pragma Syntax
+----------------+----------------------------------------------------------------+-------------------------+
| Type | Options | Location |
+----------------+----------------------------------------------------------------+-------------------------+
| bind_op | variable = v1_buffer op = mul impl = DSP latency = 2 | src/vadd.cpp:20 in vadd |
| bind_op | variable = v2_buffer op = mul impl = DSP latency = 2 | src/vadd.cpp:21 in vadd |
| bind_op | variable = vout_buffer op = add impl = DSP | src/vadd.cpp:22 in vadd |
| bind_storage | variable = v1_buffer type = RAM_1P impl = BRAM latency = 2 | src/vadd.cpp:24 in vadd |
| bind_storage | variable = v2_buffer type = RAM_1P impl = LUTRAM latency = 2 | src/vadd.cpp:25 in vadd |
| bind_storage | variable = vout_buffer type = RAM_1P impl = URAM | src/vadd.cpp:26 in vadd |
+----------------+----------------------------------------------------------------+-------------------------+
请注意区分,一个变量可以同时使用这两种绑定,例如 v1_buffer 既被指定了op,又被指定了storage,op 绑定和 storage 绑定关注不同的方面。op 绑定关心的是如何执行计算,而storage 绑定关心的是如何存储数据。
4. 总结
这些指令指导了高级综合(HLS)工具在优化指定数组的存储和操作实现时的行为。它们有助于在 FPGA 设计中实现更好的性能和资源利用率。存储类型的选择(BRAM、LUTRAM 或 URAM)以及专用 DSP 资源的使用会影响设计的整体效率。指定的延迟控制了这些操作的时序特性。