基于 FPGA的HLS技术与应用

发布于:2025-03-22 ⋅ 阅读:(26) ⋅ 点赞:(0)

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 :用于优化数组的存储和访问,将一个大数组分割成多个小数组,以提高并行处理能力和减少访问延迟。

 


网站公告

今日签到

点亮在社区的每一天
去签到