1、hls简介
HLS ( high level synthesis )即高层次综合,主要是利用高级编程语言实现算法。
2、循环优化
约束语法:
#pragma HLS unroll
#pragma HLS PIPELINE II=1
绝大多数循环都以串行的方式执行,这种执行方式比较浪费时间。对于串行的循环有两种优化方式,转为 并行( Unroll ) 或者是 管道( Pipeline )。
并行分为以下几种:
数据并行:对不同的数据处理。
线程并行:多线程并发处理。
指令并行:同一时间执行多条指令。
管道并行:多条指令同时执行,但不同时间执行指令的不同部分。
其中,数据并行为最理想的循环执行方式;不过大多数时候数据存在复杂的依赖关系,常采用管道并行的循环执行方式。
3、性能度量
在软件开发领域,通过使用算法复杂度 O(n)进行度量;
在FPGA中,使用启动时间间隔( Initiation Interval ,II )进行度量;
1、II 表示每次迭代之间的时间间隔
2、理想情况下,II = 1
3、如果 II 非常大,说明 对应的代码不能在FPGA中被展开或者pipeline。
循环依赖
在循环过程中存在内存依赖或者数据依赖都会导致操作延时,例如下面代码就存在内存4、依赖
for( ... )
{
A[x] = A[y] ;
}
这样的代码会导致 II 比较大,应为 A[x] 依赖于 A[y] ,对应的硬件必须等待对应的依赖关系完成之后才能完成。
4.1、 消除依赖
消除依赖是指通过算法优化或者一些技巧对 内存依赖 或者 数据依赖 进行消除,从而减小 II。
先看一段未经优化的代码:外层for每个 clk都会启动,而内层for需要外层的sum,导致循环变成串行结构。
int sum = 0 ;
for( i = 0;i < N;i++ )
{
for( j = 0;j < N;j++ )
{
sum += A[i*N+j] ;
}
sum += B[i] ;
}
修改如下:对于内层的for增加一个sum2,使内层for和外层for之间没有依赖,从而使串行变成 unroll or pipeline执行流程,降低代码的 II 。
int sum = 0 ;
for( i = 0;i < N;i++ )
{
int sum2 = 0 ;
for( j = 0;j < N;j++ )
{
sum2 += A[i*N+j] ;
}
sum += sum2 ;
sum += B[i] ;
}
4.2、 将 依赖放宽
先看一段未经优化的代码:假如执行一个乘的 II 为 6 ,该例存在数据依赖,消耗时间为 6N
float mul = 1.0f ;
for( i = 0 ;i < N ; i++ )
{
mul = mul * A[i] ;
}
修改如下:
#include "learn_for.h"
#define M 6
#define N 12
void learn_for
(
uint32 A[10]
)
{
int i ,j ;
float mul = 1.0f ;
float mul_copies[M] ;
loop_initialized:
for( i = 0 ; i < M ; i++ )
{
mul_copies[i] = 1.0f ;
}
loop_assign_mul_cur:
for( i = 0 ; i < N ; i++ )
{
float cur = mul_copies[M-1] * A[i] ;
loop_assign_copies:
for( j = M - 1 ; j > 0 ; j-- )
{
#pragma HLS unroll
mul_copies[j] = mul_copies[j-1] ;
}
mul_copies[0] = cur ;
}
loop_assign_mul:
for( i = 0 ; i < M ; i++ )
{
#pragma HLS unroll
mul = mul * mul_copies[i] ;
}
}
循环展开的优点:
1、直接利用FPGA硬件,对循环内部逻辑进行复制
2、编译器会重新分析所有的依赖,允许编译器进行最大程度的代码优化
3、降低模块的延迟
4、提高资源的利用率
5、设置端口为bram端口
#pragma HLS BIND_STORAGE variable=out_data type=ram_1p impl=bram
#pragma HLS BIND_STORAGE variable=in_data type=ram_1p impl=bram
#pragma HLS INTERFACE mode=bram port=out_data
#pragma HLS INTERFACE mode=bram port=in_data
6、嵌套循环
编写嵌套循环时,需要注意的事项:
1、内层循环通常是影响性能的关键部分
2、通常情况下,大多数工作要置于内层循环
3、因 尽可能将内层循环的 II 接近 1
HLS代码优化的总体原则:
1、避免指针别名
2、最小化内存依赖
3、将嵌套循环改为单层循环
说明如下:
1、指针别名:是指多个不同的指针指向相同的内容。
注:
ARRAY_PARTITION :用于优化数组的存储和访问,将一个大数组分割成多个小数组,以提高并行处理能力和减少访问延迟。