本文转载自:XILINX开发者社区微信公众号
本文作者:赛灵思产品应用工程师 Wen Chen
在Vitis HLS 工具中,要真正完成AXI总线突发,我们需要一个合适的代码风格并结合恰当的指令设置来达到这个目的。本章节带大家看看如何玩转AXI总线突发读写的代码风格-下。
本文我们将例举几个典型的会导致编译器流水线突发推理(Pipeline Burst Inferencing) 失败的情况,并结合一个具体的例子来分析和总结AXI总线突发读写的代码风格。
1. 由于重复的内存访问导致的外流水线突发失败
在以下示例中,外循环L1突发推理将失败,因为循环 L1 的迭代 0 和迭代 1 访问数组 a 和 b 中的相同元素。突发推断的优化是建立在数据是一种全有或全无类型的情况下的,也就是说编译器目前不会推断部分突发。因为突发读写推断的算法的宗旨是试图最大化突发的长度。编译器突发推断将尝试以自下而上的方式 - 从内循环到外循环,并在不满足其中一个前提条件时停止。在下面的示例中,当看到元素 8 再次被读取时,突发推断将停止,因此在这种情况下将只能推断出长度为 9 的内部循环L2的流水线突发。
L1: for (int i = 0; i < 8; ++i)
L2: for (int j = 0; j < 9; ++j)
b[i*8 + j] = a[i*8 + j];
itr 0: |0 1 2 3 4 5 6 7 8|
1: | 8 9 10 11 12 13 14 15 16|
2. 任意精度类型的循环的边界变量类型也在一定程度上影响突发推断
当 ap_int/ap_uint 类型作为循环边界变量时, 由于突发推理取决于循环归纳变量和循环索引的计数,因此使用非本机类型可能会阻碍优化的触发。建议始终使用无符号整数类型作为循环归纳变量。
3. 循环边界值的大小无法保证至少进入循环一次
在某些情况下,编译器可能无法推断出循环归纳变量的最大值N永远不会为零--也就是说,如果它不能证明总是会进入循环。在这种情况下,增加一个 assert (N > 0) 的语句表达将帮助编译器推断出这一点。
assert (N > 0);
L1: for(int a = 0; a < N; ++a) { … }
4. 数组的内部或循环主体存在读写依赖
如果我们写入一个数组位置,然后在同一次或下一次迭代中从中读取,编译器很难优这种类型的数组依赖,基本上,对于这些情况,优化将失败,因为这样读写依赖的代码不能保证写入操作一定会在读取之前发生。
5. 访问内存的循环主体中存在条件判断语句
如果内存访问是有条件地进行,则可能导致编译器突发推理算法失败,因为它无法通过条件语句进行推理。在某些情况下,编译器会简化条件甚至删除它,但通常建议不要在内存访问周围使用if条件语句。
6. HLS 编译器无法分析循环内部调用的函数中进行的 M-AXI 访问
HLS编译器并不擅长 Cross function 的数组访问分析。在这种情况下,用户可以使用 INLINE 编译指令内联函数以避免无法突发读写的问题。
7. Dataflow循环内部的流水线突发推理
内部具有 DATAFLOW pragma 指令的循环不支持突发推理。 因为任务级的数据流循环内的每个进程/任务都可能有突发。此外,DATAFLOW 指令允许了这些任务可以并行执行,所以数据流区域内不支持共享 M-AXI 端口。
分析以下函数无法推导出流水线突发的原因哪些呢?
void my_function(hls::stream&out_pkt, int *din, int input_idx) {
T v;
v.data = din[input_idx];
out_pkt.write(v);
}
void my_kernel(hls::stream&out_pkt,
int *din,
int num_512_bytes,
int num_times) {
#pragma HLS INTERFACE mode=m_axi port = din offset=slave bundle=gmem0
#pragma HLS INTERFACE mode=axis port=out_pkt
#pragma HLS INTERFACE mode=s_axilite port=din bundle=control
#pragma HLS INTERFACE mode=s_axilite port=num_512_bytes bundle=control
#pragma HLS INTERFACE mode=s_axilite port=num_times bundle=control
#pragma HLS INTERFACE mode=s_axilite port=return bundle=control
unsigned int idx = 0;
L0: for (int i = 0; i < ntimes; ++i) {
L1: for (int j = 0; j < num_512_bytes; ++j) {
#pragma HLS PIPELINE
my_function(out_pkt, din, idx++);
}
}
1. 首先非常明显的是内存访问是从被调用的函数 my_function 中进行的。这里建议用户对任何此类访问 M-AXI 存储器的函数使用inline指令,因为inline指令可以消除嵌套函数之间的层次结构,便于HLS分析突发推理。
2.在此示例中突发推理将失败的另一个原因是通过 my_function 中的 din 访问的内存由变量 (idx) 定义,该变量不是循环归纳变量 i 和 j 的函数,因此可能不是单调递增或递减的。应该使用 (i*num_512_bytes+j) 代替传递 idx。
最后我们给出一些借助软件本身Vitis HLS的综合报告,寻找找到突发读写优化的方向的方法。
在Vitis HLS 中,对于给定的内核,HLS 编译器将突发分析优化实现为多通道优化,但这样的分析是基于每个函数,而不是总体设计。也就是说突发分析优化仅针对一个函数完成,不支持跨函数的突发优化。Vitis HLS的Synthesis Summary 报告中增加了Bursting Optimizationde 板块,还报告了那些可以突发优化的机会错过了,以帮助我们改进突发读写的优化。
本篇内容从理论层面分析了HLS编译器如何寻找理解突发访问出发,提出了顺序突发和流水线突发两种概念,分析了哪些错误的代码风格会阻止突发推理。
下一篇文章中,我们将着重分析如何在Vitis HLS中利用指令精准控制AXI协议的突发读写,以资源面积换取高吞吐量,提高数据传输握手效率。