paradoxfx

【原创】Vivado HLS中四种数组端口实现方法的对比

0
阅读(11660)

Vivado HLS中,C代码中的数组作为端口时,它们被默认综合为RAM端口。以下面的程序为例:

         void array_io (dout_t d_o[N], din_t d_i[N])

{

    int i, rem;

    // Store accumulated data

    static dacc_t acc[CHANNELS];

    // Accumulate each channel

        For_Loop: for (i=0;i<N;i++) {

            rem=i%CHANNELS;

            acc[rem] = acc[rem] + d_i[i];

            d_o[i] = acc[rem];

        }

}

    在综合之后,端口被处理为:

 

1 数组端口的默认综合结果

         从图1中可以看出,输出数组d_oap_memory协议综合为端口,且Vivado HLS自动添加了输出数据端口d_o_d0、使能信号d_o_ce0、写使能端口d_o_we0、输入数据端口d_i_q0。输入数据没有生成写使能端口,因为它只需要读取输入数据。因为这个例子里for循环默认是串行执行的,读操作和写操作没有同时发生,所以数组端口被实现为单口RAM了。根据需要,我们还可以把数组接口综合为双口RAMFIFO或者把它们展开为多个独立的端口。仍然以图1对应的源代码为例,下面就来看一下它们分别是如何实现的。

1. 数组端口实现为双口RAMFIFO

         上面的例子里,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_id_o都分割为4部分,再运行C代码综合,对比其端口的综合结果,分别如图7左图、右图所示。

 

7 数组分割结果

         从图7中可以看出,当d_i分割为2部分,把d_o分割为4部分时,d_i仍然为双口RAM输入,但是当d_id_o分割为相同多的份数时,d_i的双口RAM的配置被Vivado HLS自动优化掉了:它被优化为4个单口RAM,与4个输出端口一一对应。

3. 完成展开数组

         完全展开数组,意味着把数组里的每个元素都当作单独的端口进行处理,这样可以最大限度地并行运算,提高运行速度,当然占用的器件资源也会相应地增多。展开方法与图6相同,只不过是把展开的类型从block改成complete,如图8所示。

 

8 完全展开数组的选项

         用图8d_id_o都配置为完全展开之后,端口的综合结果如图9所示。

 

9 数组完全展开之后的综合结果

         因为结果较长,在图9中没有全部列出,可以看出d_od_i都被完全展开了。

最后,我们可以对比一下单口RAM、双口RAM、部分展开和全部展开情况下的性能和资源利用率情况,如图10所示(这里使用的开发板是MicroZed)。

 

10 不同综合方法的性能与资源对比