HLS新手入门教程

您所在的位置:网站首页 新手入门编程代码 HLS新手入门教程

HLS新手入门教程

2024-02-20 14:20| 来源: 网络整理| 查看: 265

文章目录 HLS学习笔记1. 什么是HLS2. HLS开发流程3. HLS基本语法3.1. #pragma HLS3.2. HLS数据类型3.3. HLS模块定义3.4. 数组分区3.5. 流水线优化3.6. 组合逻辑优化3.7. 一些基本概念3.8. 完整示例3.8.1. 矩阵乘法3.8.2. 函数调用和循环3.8.3. 流水线和并行化指令 4. HLS高级语法4.1. 定点数和浮点数支持4.2. 数据重用4.3. #pragma HLS interface4.4. #pragma HLS design4.5. 流式处理器4.6. #pragma HLS dataflow4.7. #pragma HLS reset variable 5. HLS技巧6. 总结7. 资料来源

HLS学习笔记 1. 什么是HLS

HLS是一种高级综合技术,它允许开发人员使用高级语言(如C、C++和SystemC)来描述数字电路的行为和功能,然后将其转换为硬件电路实现。这种转换过程是自动完成的,因此开发人员无需手动编写硬件描述语言(HDL)。 HLS的主要目的是简化FPGA设计流程,提高设计效率和设计质量。通过使用高级语言来描述电路,开发人员可以更快速地进行设计和调试,同时也可以更容易地对电路进行修改和优化。此外,HLS还可以自动生成优化后的硬件电路,从而提高性能和资源利用率。

2. HLS开发流程

HLS开发流程大致可以分为以下几个步骤:

设计和实现HLS模块的功能和行为。这一步通常使用C/C++等高级语言来描述电路行为,包括输入/输出端口、计算逻辑和状态机等。使用HLS工具将高级语言代码转换为硬件电路实现。这一步通常由HLS工具自动完成,但也需要开发人员进行一些配置和调整,以便获得所需的性能和资源利用率。对生成的硬件电路进行验证和调试。这一步通常需要使用仿真工具对硬件电路进行验证,以确保其与高级语言代码的行为一致,并进行调试以解决任何问题。将生成的硬件电路与其他硬件/软件组件进行集成和部署。这一步通常需要将生成的硬件电路与其他硬件/软件组件进行集成,以实现最终的系统功能。 3. HLS基本语法

HLS使用特定的语法和指令来描述电路行为和特性。下面是一些基本的HLS语法和指令:

3.1. #pragma HLS

#pragma HLS是HLS中最常用的指令之一,用于控制HLS工具生成的硬件电路的特性和行为。该指令的语法如下:

#pragma HLS directive_name [directive_options]

其中,directive_name是具体的指令名称,directive_options是指令的参数。 以下是一些常见的#pragma HLS指令:

#pragma HLS interface:定义模块的输入/输出端口类型和属性。#pragma HLS array_partition:将数组分区为多个部分,以优化资源利用率和性能。#pragma HLS pipeline:将循环展开为流水线以提高性能。#pragma HLS inline:将指定的函数内联以提高性能。#pragma HLS reset:定义复位信号的属性和行为。#pragma HLS latency:定义操作的延迟,以便HLS工具可以更好地优化电路。 3.2. HLS数据类型

HLS支持C/C++语言中的大多数基本数据类型,例如int、float、double等。此外,HLS还支持以下数据类型:

ap_int和ap_uint:分别表示带符号整数和无符号整数。ap_fixed和ap_ufixed:分别表示带符号和无符号的固定点数。ap_vector和ap_matrix:分别表示向量和矩阵。 3.3. HLS模块定义

HLS模块定义类似于C/C++函数定义,包括模块名称、输入/输出端口、模块属性等。例如:

void my_module( int input1, int input2, int output1, int output2 ) { #pragma HLS interface ap_ctrl_none port=return #pragma HLS interface ap_none port=input1 #pragma HLS interface ap_none port=input2 #pragma HLS interface ap_none port=output1 #pragma HLS interface ap_none port=output2 // 模块实现逻辑 }

上述代码中,my_module是一个HLS模块,有两个输入端口input1和input2,以及两个输出端口output1和output2。#pragma HLS interface指令用于定义端口的属性和类型,例如ap_ctrl_none表示该模块不包含任何控制信号,ap_none表示该端口不进行数据流控制。

3.4. 数组分区

HLS中的array_partition是一种用于在FPGA或ASIC中实现数组分区的指令。它允许将一个数组划分为多个小数组,每个小数组都可以独立地存储在FPGA或ASIC的不同部分,以实现并行化操作。这样可以提高设计的并行性,从而提高性能。使用 array_partition 可以帮助设计人员更好地利用硬件资源,提高设计效率。例如:

#define N 1024 int data[N]; #pragma HLS array_partition variable=data complete dim=1

上述代码中,data是一个长度为1024的数组。#pragma HLS array_partition指令用于将该数组分为多个部分,并指定每个部分的类型。complete表示将数组完全分区,dim=1表示将数组按行进行分区。 在HLS的array_partition指令中,dim参数是用于指定分区的维度的。它指示分区应该应用于哪个维度。对于一维数组,dim参数应该设置为1,而对于二维数组,dim参数应该设置为0或1。如果设置为0,则分区将应用于第一维,而如果设置为1,则分区将应用于第二维。例如,考虑以下代码段:

int A[4][8]; #pragma HLS array_partition variable=A dim=1 complete

这将A数组的第二维(即列)分为8个小数组,每个小数组包含4个元素。因此,每个小数组都可以并行地存储在FPGA或ASIC的不同部分,以实现并行化操作。 除了dim参数之外,array_partition指令还支持以下参数:

variable: 指定要分区的数组变量名称。factor: 指定要分区的因子数。例如,#pragma HLS array_partition variable=A factor=2 将数组A分为两个块。type: 指定分区的类型。可选的类型包括block、cyclic、complete和none。block类型指定分区为块状分区,其中每个块包含连续的元素。cyclic类型指定分区为循环分区,其中每个块包含间隔相等的元素。complete类型指定分区为完全分区,其中所有分区包含相等数量的元素。none类型指定不进行分区。dim: 指定要应用分区的维度,可以是0或1,具体含义在前面回答中已经介绍。 这些参数可以组合使用以实现各种不同的分区方案,以便更好地利用FPGA或ASIC上的并行计算资源。 3.5. 流水线优化

HLS支持将循环展开为流水线,以提高性能。例如:

#define N 1024 int data[N]; void my_module(int *input, int *output) { #pragma HLS interface ap_ctrl_none port=return #pragma HLS interface ap_none port=input #pragma HLS interface ap_none port=output #pragma HLS pipeline II=1 for (int i = 0; i < N; i++) { output[i] = input[i] * 2; } }

II=1表示该循环的迭代间隔为1,即将该循环展开为一个流水线。HLS工具将在流水线上并行执行多个操作,以提高性能。 #pragma HLS pipeline是在HLS(高层次综合)中使用的一种指令,用于指示编译器将循环展开为流水线。通过使用#pragma HLS pipeline,可以将循环分为多个阶段,从而允许每个阶段在不同的时钟周期内并行执行,从而提高系统的性能。 使用#pragma HLS pipeline指令可以有效地利用FPGA或ASIC上的并行计算资源,从而实现更高的时钟频率和更快的数据处理速度。它通常用于加速循环密集型的计算和数据处理任务,如图像处理、信号处理和矩阵计算等。 #pragma HLS pipeline指令的语法如下:

#pragma HLS pipeline [N]

其中N是可选参数,指示流水线的并行度。默认情况下,流水线的并行度等于1,即每个阶段在一个时钟周期内执行。如果指定了N,则表示将流水线分为N个阶段,从而允许每个阶段在不同的时钟周期内并行执行。通常,N的值应该是2的幂,以便更好地利用硬件并行计算资源。 除了N参数之外,#pragma HLS pipeline还支持一些其他的可选参数,包括:

II:II代表“间隔间隔”(Iteration Interval)的缩写,是在HLS(高层次综合)中使用的一个指令参数,用于指示循环迭代之间的最小间隔。通过指定II参数,可以帮助编译器更好地理解循环的数据依赖关系,从而生成更高效的硬件设计。 在流水线化的循环中,每个迭代可以分为多个阶段,这些阶段可以在不同的时钟周期内并行执行。如果循环迭代之间的间隔太小,就可能会导致不同的阶段之间存在数据依赖关系,从而导致流水线暂停等性能问题。因此,可以使用II参数指定循环迭代之间的最小间隔,以便在硬件设计中解决这些问题。II的值应该是一个正整数,表示相邻两次迭代之间的最小间隔,通常以时钟周期为单位。例如,如果II=2,则表示每两次迭代之间必须间隔两个时钟周期,即第一个迭代在时钟周期1执行,第二个迭代在时钟周期3执行,第三个迭代在时钟周期5执行,以此类推。 enable_flush:指示是否应该在输入结束时强制冲洗流水线,以避免在下一个输入时出现数据冲突。ii_reduction:指示是否应该将内部循环展开为累加器,从而进一步减少延迟和提高时钟频率。 下面是一个使用#pragma HLS pipeline指令的例子: #pragma HLS pipeline II=1 for (int i = 0; i < N; i++) { // ... }

在上面的例子中,#pragma HLS pipeline指令将循环展开为流水线,并将流水线分为II=1个阶段,每个阶段在一个时钟周期内执行。通过流水线化,该循环可以更快地执行,并且可以在不同的时钟周期内并行执行。

3.6. 组合逻辑优化

HLS支持将指定的函数内联以提高性能。例如:

int add(int a, int b) { return a + b; } int sub(int a, int b) { return a - b; } int calc(int a, int b) { return add(a, b) * sub(a, b); } void my_module(int *input, int *output) { #pragma HLS interface ap_ctrl_none port=return #pragma HLS interface ap_none port=input #pragma HLS interface ap_none port=output #pragma HLS inline for (int i = 0; i < N; i++) { output[i] = calc(input[i], input[i+1]); } }

上述代码中,calc函数内部调用了add和sub函数。#pragma HLS inline指令用于将calc函数内联,即将其实现逻辑直接嵌入到my_module中,以避免在运行时频繁调用函数。 在HLS(高层次综合)中,inline是一种指令,用于告诉编译器在编译时将函数调用内联展开,而不是生成实际的函数调用。 使用inline指令可以有效地减少函数调用带来的开销,因为它可以将函数体的代码直接插入到调用该函数的代码中,从而避免了函数调用的开销(如函数调用和返回操作以及参数的复制等)。这在一些高频率的数据通路设计中非常有用。 下面是一个使用inline指令的例子:

inline int add(int a, int b) { return a + b; } int main() { int x = 10, y = 20; int z = add(x, y); // 实际上是将add函数的代码插入到这里 return 0; }

在上面的例子中,inline指令告诉编译器将add函数的代码内联到调用它的地方,而不是生成实际的函数调用。这样,当程序运行时,将不会存在真正的函数调用,而是在main函数中直接执行add函数的代码,从而避免了函数调用的开销。 #pragma HLS inline是在HLS中使用的指令,用于指示编译器在编译时对特定函数进行内联展开优化。使用#pragma HLS inline可以告诉编译器将指定的函数内联展开,从而加速系统的性能。 #pragma HLS inline指令的语法如下:

#pragma HLS inline int add(int a, int b) { return a + b; } int main() { int x = 10, y = 20; int z = add(x, y); // 实际上是将add函数的代码插入到这里 return 0; }

在上面的例子中,#pragma HLS inline指令告诉编译器将add函数内联展开,从而加速系统的性能。

3.7. 一些基本概念 iteration latency: 从第一个输入到第一个输出的时间iteration interval(II): 每两次输出/输入之间的时间间隔total latency: 总时间,total latency = iteration latency + II * (number of items - 1),II可以改善total latency但不影响iteration latency 在这里插入图片描述 3.8. 完整示例 3.8.1. 矩阵乘法

以下是一个完整的HLS示例,展示了如何使用HLS实现一个矩阵相乘的功能。

#include #include #define N 32 #define M 32 #define P 32 typedef ap_axiu data_t; void matrix_multiply( data_t A[N*M], data_t B[M*P], data_t C[N*P] ) { #pragma HLS interface ap_ctrl_none port=return #pragma HLS interface axis port=A #pragma HLS interface axis port=B #pragma HLS interface axis port=C data_t a; data_t b; data_t c; hls::stream stream_A; hls::stream stream_B; hls::stream stream_C; // 将输入数据流转换为流对象 for (int i = 0; i < N*M; i++) { #pragma HLS pipeline II=1 a = A[i]; stream_A.write(a); } for (int i = 0; i < M*P; i++) { #pragma HLS pipeline II=1 b = B[i]; stream_B.write(b); } // 矩阵乘法计算逻辑 for (int i = 0; i < N; i++) { for (int j = 0; j < P; j++) { #pragma HLS pipeline II=1 int sum = 0; for (int k = 0; k < M; k++) { a = stream_A.read(); b = stream_B.read(); sum += a.data * b.data; } c.data = sum; c.keep = 15; c.strb = 15; c.last = (i == N-1 && j == P-1) ? 1 : 0; stream_C.write(c) } } // 将输出流转换为输出数据流 for (int i = 0; i < N*P; i++) { #pragma HLS pipeline II=1 c = stream_C.read(); C[i] = c; } }

在上述示例中,我们定义了一个名为matrix_multiply的函数,该函数用于计算两个矩阵的乘积,并将结果存储在输出矩阵中。我们使用了#pragma HLS interface指令指定了函数的输入输出接口,以及数据类型。然后我们定义了一些流对象,用于在HLS中处理数据流。 在函数实现中,我们首先将输入的数据流转换为流对象,然后执行矩阵乘法计算。在矩阵乘法计算中,我们使用三重嵌套循环遍历矩阵,以计算矩阵乘积的每个元素。内部的循环使用#pragma HLS pipeline指令进行流水线操作。最后,我们将输出流转换为输出数据流,并将其返回。 这个示例演示了HLS如何使用流水线和流对象来高效处理数据流,以及如何使用#pragma HLS interface指令来指定HLS代码的接口和数据类型。对于HLS的新手来说,这个示例可以作为一个非常好的学习范例。

3.8.2. 函数调用和循环

下面我们再来一个简单的示例,演示如何在HLS中使用函数调用和循环。 假设我们有一个函数compute,它的作用是对一个数组进行一系列计算,并返回计算结果。现在我们想在HLS中使用这个函数,以便可以在FPGA上进行硬件加速。下面是示例代码:

#define N 1024 void compute(int A[N], int B[N], int C[N]) { for (int i = 0; i < N; i++) { B[i] = A[i] + 1; } for (int i = 0; i < N; i++) { C[i] = B[i] * 2; } } void top_function(int A[N], int C[N]) { #pragma HLS interface ap_fifo port=A #pragma HLS interface ap_fifo port=C int B[N]; compute(A, B, C); }

在上述示例中,我们定义了一个名为compute的函数,用于对一个数组进行一系列计算。然后我们定义了另一个函数top_function,它使用#pragma HLS interface指令指定了函数的输入输出接口和数据类型。在top_function中,我们首先声明一个数组B,用于存储compute函数的计算结果。然后我们调用compute函数,将输入数组A、输出数组C和中间数组B作为参数传递给该函数。最后,我们将C数组作为输出返回。 这个示例演示了如何在HLS中使用函数调用和循环。在HLS中,我们可以像在C语言中一样使用函数和循环来实现算法,从而更容易地将现有的软件算法移植到硬件中。同时,我们也可以使用#pragma HLS interface指令来指定函数的接口和数据类型,从而更方便地与其他硬件模块集成。

3.8.3. 流水线和并行化指令

接下来,我们将介绍如何在HLS中使用流水线和并行化指令来实现加速。 流水线是一种常见的并行化技术,它将一个任务分解为多个阶段,每个阶段可以并行执行。例如,在对一个长数组进行操作时,我们可以将其分为多个段,每个段可以并行处理。在HLS中,我们可以使用#pragma HLS pipeline指令来实现流水线加速。下面是一个示例代码:

#define N 1024 void top_function(int A[N], int C[N]) { #pragma HLS interface ap_fifo port=A #pragma HLS interface ap_fifo port=C #pragma HLS pipeline int B[N]; for (int i = 0; i < N; i++) { #pragma HLS unroll B[i] = A[i] + 1; } for (int i = 0; i < N; i++) { #pragma HLS unroll C[i] = B[i] * 2; } }

在上述示例中,我们使用#pragma HLS pipeline指令来指定函数中的循环可以流水线并行执行。在每个循环中,我们还使用#pragma HLS unroll指令来指定循环可以展开为多个迭代,以进一步提高并行度。这个示例演示了如何使用流水线和并行化指令来实现加速。 最后,我们需要注意一些常见的优化技巧,以进一步提高HLS的性能。例如,我们可以使用#pragma HLS array_partition指令将数组分区,以充分利用FPGA的存储资源。我们还可以使用#pragma HLS dataflow指令来实现数据流架构,从而更好地利用FPGA的并行性能。此外,我们还可以使用#pragma HLS stream指令来指定数据流接口,以优化数据传输和缓存。这些优化技巧可以进一步提高HLS的性能和效率。

4. HLS高级语法

在HLS中,还有一些高级特性,如定点数和浮点数支持,数据重用和代码生成等。我们在下面继续介绍一些高级特性和应用示例。

4.1. 定点数和浮点数支持

在FPGA中,通常使用定点数来表示数字,因为它可以更有效地利用硬件资源和提高运算速度。在HLS中,我们可以使用ap_fixed和ap_ufixed类型来表示定点数,它们具有定点位和整数位。例如:

#include "ap_fixed.h" typedef ap_fixed fixed_point;

上述代码定义了一个16位定点数,其中8位为小数位,另外8位为整数位。我们还可以使用ap_float类型来表示浮点数,它具有单精度和双精度浮点数。例如:

#include "ap_float.h" typedef ap_float single_float; typedef ap_float double_float; 4.2. 数据重用

在HLS中,我们可以使用数据重用技术来减少计算量和存储器带宽。数据重用指的是在计算过程中多次使用同一数据,而不是每次都从存储器中读取数据。在HLS中,我们可以使用#pragma HLS array_reshape指令和#pragma HLS data_pack指令来实现数据重用。例如:

#define N 1024 void top_function(int A[N], int B[N]) { #pragma HLS interface ap_fifo port=A #pragma HLS interface ap_fifo port=B int C[N]; for (int i = 0; i < N; i++) { #pragma HLS pipeline II=1 #pragma HLS unroll factor=4 C[i] = A[i] + B[i]; } #pragma HLS array_reshape variable=C complete dim=1 #pragma HLS data_pack variable=C for (int i = 0; i < N/4; i++) { #pragma HLS pipeline II=1 int sum = C[i*4] + C[i*4+1] + C[i*4+2] + C[i*4+3]; B[i] = sum; } }

在上述示例中,我们使用#pragma HLS array_reshape指令将数组C从一维数组重塑为二维数组,以利用数据重用。然后,我们使用#pragma HLS data_pack指令对数组C进行数据打包,以减少存储器带宽。最后,我们将数组C用于计算,并使用流水线和并行化指令来实现加速。

4.3. #pragma HLS interface

#pragma HLS interface是在HLS(高层次综合)中使用的一个指令,用于定义模块的接口,包括输入、输出端口和数据类型等。通过使用#pragma HLS interface,可以使HLS编译器了解模块的接口和数据类型,并生成相应的硬件设计。 具体而言,#pragma HLS interface指令的作用是将模块的输入和输出端口映射到HLS的IO接口上,从而将模块与外部环境连接起来。此外,还可以使用该指令指定输入和输出端口的数据类型、数据位宽、信号是否阻塞等属性,以便在硬件设计中生成相应的电路。 以下是一个使用#pragma HLS interface指令的简单例子:

#pragma HLS interface ap_ctrl_none port=return #pragma HLS interface axis port=input #pragma HLS interface axis port=output void my_module(stream &input, stream &output) { // ... }

在上面的例子中,#pragma HLS interface指令定义了一个名为my_module的模块,该模块有两个输入输出端口input和output,数据类型为stream,其中data_t是一个自定义的数据类型。第一行的ap_ctrl_none指示该模块不需要控制器端口,port=return表示该模块的返回值映射到HLS的返回端口上。 需要注意的是,#pragma HLS interface指令只是定义模块的接口,不会生成模块的实现。模块的实现应该在其他地方定义,例如在C或C++源代码中。在模块实现的代码中,需要使用与接口定义中相同的数据类型和端口名,以便与HLS生成的电路连接起来。同时,还需要使用HLS指令来指示编译器如何将模块转换为硬件电路,例如使用#pragma HLS design和#pragma HLS pipeline等指令。 #pragma HLS interface指令用于定义模块的接口和属性,以下是常用的参数:

ap_none、ap_hs、ap_stable、ap_vld:指定输入/输出端口的数据类型。port=:指定模块的返回值端口名,必须在模块的接口中指定。axis、s_axilite、m_axi、s_axi等:指定输入/输出端口的接口类型,例如AXI总线、轴接口等。register、noflip、flatten等:指定输入/输出端口的属性,例如数据是否寄存器存储、是否需要数据翻转等。 以下是参数类型和接口协议对应图 在这里插入图片描述

图中的“ D”表示“ default”,表示 Vivado HLS 工具默认综合出来的接口。“ S”表示“ support”,表示 Vivado HLS 工具支持综合出来的接口。

4.4. #pragma HLS design

#pragma HLS design指令用于指示编译器如何将模块转换为硬件电路。以下是常用的参数:

flatten:将模块展开为更简单的逻辑结构。pipeline:使模块内部的循环/分支语句并行执行,提高性能。inline:将模块内的函数调用展开为实际的函数代码。dataflow:指示编译器使用数据流编程模型进行优化。latency:指定模块的延迟目标。parallel:指定模块的并行度目标。 使用示例: #pragma HLS design flatten #pragma HLS design pipeline II=2

在使用#pragma HLS design时,需要将其放置在模块定义之前,并且模块定义必须在同一个文件中。在模块实现的代码中,可以使用该指令来指示编译器对模块进行优化,例如将模块展开为更简单的逻辑结构、并行执行循环/分支语句、调用函数等。同时,还可以使用其他HLS指令来进一步控制优化策略,例如#pragma HLS loop_tripcount、#pragma HLS dependence等。

4.5. 流式处理器 流式处理器:流式存储器(streaming memory)是一种用于解决数据存储和读取延迟问题的技术。使用流式存储器可以减少在高级综合(HLS)中数据存储和读取的延迟,从而提高设计的吞吐量和性能。 以下是使用流式存储器的一些技巧:数据缓冲区:在HLS设计中,使用流式存储器可以将数据存储在缓冲区中,以便稍后读取。这可以减少存储和读取数据的延迟,从而提高设计的性能。数据分块:使用流式存储器可以将数据分成块,每个块都可以作为单独的流进行处理。这可以帮助优化流水线设计,减少存储和读取数据的延迟。数据流合并:在某些情况下,可以将多个数据流合并为一个数据流。这可以减少存储和读取数据的延迟,同时提高设计的性能。数据流分裂:类似于数据流合并,数据流分裂可以将一个数据流分成多个数据流进行处理。这可以帮助优化流水线设计,减少存储和读取数据的延迟。内存分配:在HLS设计中,可以使用内存分配技术来管理流式存储器。这可以减少存储和读取数据的延迟,同时提高设计的性能。总之,使用流式存储器可以帮助优化HLS设计,减少数据存储和读取的延迟,从而提高设计的性能和吞吐量。

以下是一个简单的HLS流式处理器的例子,该流式处理器使用Pipelining和Loop Unrolling优化策略来提高性能。

#include // 定义流式处理器 void stream_processor(hls::stream& in_stream, hls::stream& out_stream, int num_elements) { #pragma HLS interface ap_ctrl_none port=return #pragma HLS interface axis port=in_stream #pragma HLS interface axis port=out_stream int buffer[4]; // 循环展开初始化,实现4元素缓冲区 for (int i = 0; i < 4; i++) { #pragma HLS unroll buffer[i] = 0; } // 流式处理器核心部分 for (int i = 0; i < num_elements; i++) { #pragma HLS pipeline II=1 int input_data = in_stream.read(); // 向缓冲区添加新数据 buffer[0] = buffer[1]; buffer[1] = buffer[2]; buffer[2] = buffer[3]; buffer[3] = input_data; // 计算输出 int output_data = buffer[0] + buffer[1] + buffer[2] + buffer[3]; // 输出到流 out_stream.write(output_data); } }

上述例子中,stream_processor函数实现了一个简单的流式处理器。它的输入是一个整数流,输出也是一个整数流。在函数内部,使用了Pipelining和Loop Unrolling优化策略,将处理逻辑拆分为多个阶段,并使每个阶段都可以并行执行。同时,通过循环展开的方式实现了一个4元素的缓冲区,以加速计算过程。在处理完所有输入数据之后,该函数会将输出流中的数据写回到主存中。 需要注意的是,在使用HLS开发流式处理器时,还需要考虑数据流的稳定性和流量控制等问题。为了确保稳定性,可以使用ap_stable等数据类型来声明输入/输出端口;而为了控制流量,可以使用ap_fifo等缓冲区类型来调整输入/输出流的大小。

4.6. #pragma HLS dataflow

#pragma HLS dataflow数据流指令: 将设计的功能划分成不同的阶段,让HLS能够对不同的功能阶段进行优化,提高综合后的效果。

#include "hls_stream.h" void adder(hls::stream &in, hls::stream &out, int val) { for (int i = 0; i < 10; i++) { int x = in.read(); out.write(x + val); } } void multiplier(hls::stream &in, hls::stream &out, int val) { for (int i = 0; i < 10; i++) { int x = in.read(); out.write(x * val); } } void top(hls::stream &in, hls::stream &out, int val) { #pragma HLS dataflow hls::stream intermediate; adder(in, intermediate, val); multiplier(intermediate, out, val); } 4.7. #pragma HLS reset variable

指定变量在每个时钟周期的初始值。

int counter = 0; void my_counter(bool reset, int &count) { #pragma HLS reset variable=counter if (reset) { counter = 0; } else { counter++; } count = counter; } 5. HLS技巧

下面我将为你介绍一些HLS的最佳实践,帮助你更好地使用HLS进行硬件电路设计。

了解HLS的限制和优化 虽然HLS可以让你使用高级编程语言进行硬件电路设计,但是它仍然有一些限制。例如,HLS无法处理动态内存分配,因此你需要手动分配所有的内存。另外,HLS通常无法处理递归函数,因为递归需要使用栈来保存函数调用的状态,而栈需要使用大量的存储器资源。 此外,为了获得最佳的性能和资源利用率,你需要使用HLS的一些优化选项,例如循环展开、分区、指令重排等。这些优化选项可以根据你的算法和目标设备进行调整,因此你需要了解HLS的优化选项和如何使用它们。将复杂的算法拆分成简单的模块 HLS最适合处理简单的、高度并行的算法。因此,当你使用HLS进行硬件电路设计时,应该尽可能将复杂的算法拆分成简单的模块。每个模块都应该尽可能独立,以便并行化处理。优化内存访问 在HLS中,内存访问通常是性能和资源利用率的瓶颈。因此,你需要优化内存访问以获得最佳的性能和资源利用率。一种常见的优化方法是使用局部变量和数组,这可以减少对存储器的访问次数。另外,你还可以使用流缓冲区来减少存储器的访问次数。使用硬件加速器IP核 HLS提供了许多硬件加速器IP核,例如FFT、卷积等。这些IP核已经经过优化,可以在FPGA上实现高性能的硬件电路。当你需要实现这些算法时,应该首先查看HLS提供的IP核,以便快速实现高性能的硬件电路。进行性能分析和调试 最后,当你使用HLS进行硬件电路设计时,需要进行性能分析和调试,以确保硬件电路的性能和正确性。HLS提供了许多调试和性能分析工具,例如波形查看器、性能分析器等。你可以使用这些工具来定位和解决问题,从而加快硬件电路设计的开发速度。 希望这些最佳实践可以帮助你更好地使用HLS进行硬件电路设计。

以下是一些在使用HLS进行高层次综合时可能有用的技巧:

分区(partitioning):使用HLS时,将大型数组分成较小的块可以增加并行性,从而提高性能。流水线(pipelining):将操作流水线化可以减少操作的延迟,并在FPGA上实现更高的时钟频率。循环展开(loop unrolling):展开循环可以减少循环迭代的次数,并增加并行性。优化数据类型:HLS支持多种数据类型,包括固定点数和浮点数。选择合适的数据类型可以减少逻辑资源使用和运行时间。优化存储:使用合适的存储器类型可以减少存储器使用和延迟。例如,使用流式存储器(streaming memory)可以减少数据存储和读取的延迟。优化数据流:使用HLS时,将操作转换为数据流可以提高性能,因为它允许并行操作。使用pragma指令:HLS支持pragma指令,这些指令可以告诉HLS如何执行某些操作。pragma指令可以优化代码,从而提高性能。 - 调整数据结构:合适的数据结构可以减少逻辑资源使用和运行时间。例如,使用链表(linked list)可以减少数据移动的延迟。使用代码重构:HLS生成的代码可能不是最优的。通过重构代码,可以手动优化代码,从而提高性能。合适的编译器指令:编译器指令可以告诉编译器如何优化代码。使用合适的编译器指令可以提高性能。 6. 总结

在本教程中,我们介绍了HLS的基本概念和编程模型,包括HLS的主要特性、编程模型和调优技巧。我们还提供了一些示例,展示了如何使用HLS实现不同的应用程序,并介绍了HLS中的一些高级特性,如定点数和浮点数支持、数据重用和代码生成等。通过学习本教程,您将能够掌握HLS的基本概念和编程技巧,以便使用HLS来实现高效的FPGA设计。 HLS是一个强大的工具,可以帮助我们将软件算法转换为硬件电路,从而在FPGA上获得高性能的加速效果。在学习HLS时,我们需要掌握C/C++编程语言和FPGA体系结构知识,并掌握HLS中常见的优化技巧和指令。同时,我们也需要有耐心和恒心,逐步积累经验和技能,从而成为一名优秀的HLS工程师。

7. 资料来源

ChatGPT,对没错哈哈哈哈,全部是ChatGPT生成的,我只是知识的搬运工 >。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3