HLS可以用于将C语言函数转换成硬件模块。这是一个革命性的工具,从此软件人员也可以创建硬件模块。下面从软件工程师的角度,介绍使用HLS创建硬件模块时的注意事项。为了避免重复,请先阅读UG871 《Vivado Design Suite Tutorial: High-Level Synthesis》。
进行硬件加速,要先准备好用来生成硬件模块的函数,它需要保存在一个单独的文件里。在创建工程时,指定它做为顶层函数。同时也要准备一个实现相同功能的函数,它不会被生成硬件模块,用于验证硬件模块的功能是否正确。最后还要准备一个测试的main()函数,它分别调用前面所述的两个软件函数,并比较它们输出的结果是否一致。在C语言验证和RTL验证时,HLS工具都会调用它。
创建HLS工程的具体步骤,请参考UG871 《Vivado Design Suite Tutorial: High-Level Synthesis》。假设已经创建好HLS工程。下面介绍最基本的约束。
下面是一个典型的C语言函数。
long long cmplx_dot_st_1port
(
long long* p_a,
unsigned int ui_vector_length,
unsigned int ui_test_param[2]
)
上面这个函数中,p_a是一个指针,用于存放有用于存储大量输入/输出数据。CPU执行这个函数时,CPU指令会读写其中的数据。如果C语言函数转换成硬件模块,可以生成AXI Master接口,由硬件模块自动读取数据,相当于集成了一个DMA控制器;也可以生成AXI Stream接口,AXI Stream Slave接口接受其它数据源生成的数,AXI Stream master接口可以作为数据源。指针原来是32位的。为了提高带宽,这个函数中将其改成了64位的指针。下面是为其生成AXI Master接口的约束:
#pragma HLS RESOURCE variable=p_a core=AXI4M
#pragma HLS INTERFACE ap_bus depth=1024 port=p_a
为指p_a生成AXI Stream接口的约束:
#pragma HLS INTERFACE axis port= p_a
ui_vector_length是一个输入参数,指定buffer的长度。转换成硬件模块后,会生成一个可写的寄存器,可以由AXI Lite总线访问,由CPU写入长度。为输入参数ui_vector_length生成AXI Lite寄存器的约束:
#pragma HLS RESOURCE variable=ui_vector_length core=AXI4LiteS
HLS为它生成寄存器后,还会为其生成两个函数,用于设置和读取。
void XCmplx_dot_st_1port_SetUi_vector_length(XCmplx_dot_st_1port3a *InstancePtr, u32 Data)
u32 XCmplx_dot_st_1port_GetUi_vector_length(XCmplx_dot_st_1port3a *InstancePtr)
ui_test_param[2]是一个数组,既可以输入参数又可以输出参数。转换成硬件模块后,会生成可读可写的寄存器,因此硬件模块既可以从它得到从CPU输入的参数,也可以用它向CPU返回数据。由于硬件资源限制,这种数组不能太大。为数组生成寄存器的约束:
#pragma HLS RESOURCE variable=f_accum_param core=AXI4LiteS
#pragma HLS ARRAY_PARTITION variable=f_accum_param complete dim=1
HLS会为数组中每一个成员创建一个寄存器,也会创建对应寄存器的读写函数。
函数的返回值也需要创建寄存器,这样CPU读这个寄存器就可以得到返回值。为返回值创建寄存器的约束:
#pragma HLS RESOURCE variable=return core=AXI4LiteS
HLS同样会为返回值寄存器创建读写函数。
u64 XCmplx_dot_st_1port_GetReturn(XCmplx_dot_st_1port3a *InstancePtr);
如果是用户自己的函数,虽然参数个数和类型可能都不一样,都可以参照上述类型做约束。HLS生成的硬件模块的寄存器都是无符号32位数据。必要的时候,可以使用C语言的union来做类型转换。
约束好与CPU的接口后,需要考虑性能优化。HLS有很多性能优化的技巧。但是作为软件工程师,掌握基本的就可以了。“#pragma HLS DATAFLOW”是一个很有用的优化命令,可以给每个函数添加一个。“#pragma HLS PIPELINE”也是非常有用的优化命令,每个循环可以添加一个。这是最简单的优化,而且比较有效果。
然后就可以综合C语言的函数,HLS工具会报告生成的硬件模块的性能、资源情况。接下来导出硬件模块,做成IP,直接在Vivado IP Integrator里使用,做一个嵌入式系统。这种IP,可以给Zyqn中的ARM使用,也可以给MicroBlaze使用。最后编译Vivado工程,生成bit文件。
如果要替换原来的C语言函数,还需要为硬件模块写驱动。HLS自动为每个输入输出参数生成了读写函数。它还为控制硬件模块提供了下列控制函数。
void XCmplx_dot_Start(XCmplx_dot *InstancePtr);
u32 XCmplx_dot_IsDone(XCmplx_dot *InstancePtr);
u32 XCmplx_dot_IsIdle(XCmplx_dot *InstancePtr);
u32 XCmplx_dot_IsReady(XCmplx_dot *InstancePtr);
驱动函数的实现挺简单。第一步是先调用XCmplx_dot_IsReady( )函数检查硬件状态。如果是就绪状态,则进行第二步,调用输入参数的写函数,向硬件模块写入输入参数。第三步则是等待硬件模块完成运算,可以在循环中调用XCmplx_dot_IsDone( )函数检查。如果完成,则进行第四步,读取运算结果,比如调用XCmplx_dot_st_1port_GetReturn( ) 函数读取返回值;如果结果在输出参数中,则调用对应参数的读取函数得到结果。HLS生成的寄存器,都是32位的寄存器。HLS生成的驱动,缺省认为结果是无符号32位数。如果是其它类型,可以使用指针或者union来做转换。
比如要在64位数据、32位浮点数、32位整型数据之间做转换,可以使用下面的数据结构
struct u32_cmplx{
unsigned int real;
unsigned int img;
};
union ll_data_u
{
long long ll_data;
struct sp_cmplx cmplx_data;
struct u32_cmplx u32_cmplx_data;
};
从寄存器读到的数据,可以保存到u32_cmplx_data.real中;再以cmplx_data.real引用,就是浮点数。
用CPU做运算,CPU的运算能力通常是瓶颈。使用HLS生成硬件模块加速后,运算能力大大提高,通常运算能力都不再是瓶颈。很多时候,存储器和数据通道成了瓶颈。一个优化办法,就是提高数据通道的带宽。C语言里,经常用32位的指针。这种情况下,最好改成64bit的指针,可以提高数据通道的带宽,降低运算时间。
评论
查看更多