【原创】Vivado HLS中四种数组端口实现方法的对比
0赞在Vivado HLS中,C代码中的数组作为端口时,它们被默认综合为RAM端口。以下面的程序为例:
voidarray_io(dout_t d_o[N], din_t d_i[N])
{
inti, rem;
// Store accumulated data
staticdacc_t acc[CHANNELS];
// Accumulate each channel
For_Loop:for(i=0;i rem=i%CHANNELS; acc[rem] = acc[rem] + d_i[i]; d_o[i] = acc[rem]; } } 在综合之后,端口被处理为: 图1数组端口的默认综合结果 从图1中可以看出,输出数组d_o被ap_memory协议综合为端口,且Vivado HLS自动添加了输出数据端口d_o_d0、使能信号d_o_ce0、写使能端口d_o_we0、输入数据端口d_i_q0。输入数据没有生成写使能端口,因为它只需要读取输入数据。因为这个例子里for循环默认是串行执行的,读操作和写操作没有同时发生,所以数组端口被实现为单口RAM了。根据需要,我们还可以把数组接口综合为双口RAM、FIFO或者把它们展开为多个独立的端口。仍然以图1对应的源代码为例,下面就来看一下它们分别是如何实现的。 1.数组端口实现为双口RAM和FIFO 上面的例子里,for循环是串行执行的,所以单口RAM就能满足要求了。如果我们想把数组实现为双口RAM,那么for循环就需要被配置成并行循环的,也就是循环展开。在Vivado HLS中打开源程序,然后通过Directive视图改变for循环的配置为展开,如图1所示。 图2展开for循环 在Directive视图中,我们还可以配置端口使用的资源,例如把输入d_i配置为双口RAM,如图3所示。
图3配置端口的资源为双口RAM 按照类似的方法,把输出端口d_o的接口类型INTERFACE配置为ap_fifo类型。最终修改之后的指示文件的视图如图4所示。
图4修改结果 然后运行C代码综合,结果如图5所示。
图5双口RAM的综合结果 从图5中可以看出,输入端口d_i已经具有双口RAM的接口,它有两条地址线,两个独立的输入和两个使能端口。 2.数组的分割 在接口综合的时候,Vivado HLS可以根据我们预先定义的因子,对数组进行分割,分割方法如图6所示。
图6数组分割 在这里,我们把d_i分割为2部分,把d_o分割为4部分,进行C代码综合;然后把d_i和d_o都分割为4部分,再运行C代码综合,对比其端口的综合结果,分别如图7左图、右图所示。 图7数组分割结果 从图7中可以看出,当d_i分割为2部分,把d_o分割为4部分时,d_i仍然为双口RAM输入,但是当d_i和d_o分割为相同多的份数时,d_i的双口RAM的配置被Vivado HLS自动优化掉了:它被优化为4个单口RAM,与4个输出端口一一对应。 3.完成展开数组 完全展开数组,意味着把数组里的每个元素都当作单独的端口进行处理,这样可以最大限度地并行运算,提高运行速度,当然占用的器件资源也会相应地增多。展开方法与图6相同,只不过是把展开的类型从block改成complete,如图8所示。 图8完全展开数组的选项 用图8把d_i和d_o都配置为完全展开之后,端口的综合结果如图9所示。
图9数组完全展开之后的综合结果 因为结果较长,在图9中没有全部列出,可以看出d_o和d_i都被完全展开了。 最后,我们可以对比一下单口RAM、双口RAM、部分展开和全部展开情况下的性能和资源利用率情况,如图10所示(这里使用的开发板是MicroZed)。
图10不同综合方法的性能与资源对比