【雪天鱼】Vitis HLS 学习笔记(1) Pipelining 流水线

1 自媒体账号

目前运营的自媒体账号如下:

QQ 学习交流群

  • FPGA科研硕博交流群 910055563 (进群有一定的学历门槛,长期未发言会被请出群聊,主要交流FPGA科研学术话题)
  • CNN | RVfpga学习交流群(推荐,人数上限 2000) 541434600
  • FPGA&IC&DL学习交流群 866169462

菜鸡一枚,记录下自己的学习过程,可能后续有更新,也可能没有更新,谨慎参考。

2 Pipelining 流水线

示例代码链接:Vitis-HLS-Introductory-Examples/Pipelining/Loops/pipelined_loop at master · Xilinx/Vitis-HLS-Introductory-Examples · GitHub

GUI 图形化界面

  1. 创建 HLS 工程,复制 loop_pipeline.cpploop_pipeline.h 源文件到该工程 src目录(自己建一个)中并添加
  2. Project Settings -> Synthesis -> 选择要综合的 Top Function
  3. 运行 C综合,并进行结果分析

TCL 脚本操作

  1. 修改 set_part 命令后的芯片型号,确保可用,每次创建一个 solution 都要指定,所以都需要修改下

源文件 loop_pipeline.cpp:

#include "loop_pipeline.h"

dout_t loop_pipeline(din_t A[N]) {

    int i, j;
    static dout_t acc;

LOOP_I:
    for (i = 0; i < 20; i++) {
    LOOP_J:
        for (j = 0; j < 20; j++) {
            acc += A[j] * i;
        }
    }

    return acc;
}

该函数接收数组 A 作为输入,并通过两层嵌套循环计算一个累加表达式 sum(i * A[j]),其中 ij 都是从 0 到 19的整数。最终,函数返回这个累加的结果。接下来进行采用不同的优化指令的综合结果的分析。

2.1 只对最内层的 LOOP_J 循环进行 pipeline

  • 试了下对 LOOP_J 添加 set_directive_pipeline -off "loop_pipeline/LOOP_J" 编译指示也无法关闭对最内层循环的pipeline,说明 Vitis HLS 可能默认就会优化循环的 II,进行 pipeline .
  • C++ 综合报告:

image.png

基础概念在 UG1399 的 ch.15 分析综合的结果 章节的 小节 调度查看器中有
image.png

LOOP_J 循环单次迭代所需要的时钟周期数(对应报告中的是 Iteration Latency)是 5,然后两次迭代之间的启动时间间隔(对应报告中的 Interval) 是 1, 循环完成的总迭代数( Trip Count)是 400, 即 20x20,循环完成所有迭代所需的时钟周期数(Latency)为 403, 可以理解为 5 + (400 - 1)= 404 (这里不知道为什么少了一周期),即 Latency = Iteration Latency + (Trip Count - 1) x II

image.png|875
可以参考此图进行理解,无流水线化时 (A) 循环完成3次迭代需要 3x3=9 cycles, 循环流水打拍后(B) 需要 3 +(3-1)=5个周期

我们来把 II 设置为3时,看下结果,根据上面的式子得到 Latency = Iteration Latency + (Trip Count - 1) x II = 5 + 399 x 3 = 1202
(与 HLS 结果相比还是多了一个周期,猜测是HLS是从0计数导致?)

image.png|875

这里我们就可以看出来,对循环的总延迟影响比较大的是 Trip Count 和 II,而 Trip Count 在设计中一般是和实现的算法绑定的比较深的,能比较自由优化的往往是 II, II=3时,可以看到 LUT 是比 II=1时 少用了一些资源,说明当优化 II 时,可能会导致额外的资源开销。而迭代的延迟是和该循环每次迭代进行的具体操作有关,可以打开 Schedule Viewer 进行查看

image.png
从图中可以看出来,一次迭代主要是加载 A[j] 数据,乘以 i, 以及 累加 acc这三步,所以单次迭代内进行的操作越复杂,所需要的延迟就越大。

2.2 对最外层的 LOOP_I 循环进行 pipeline

image.png
循环就直接没有迭代了,也就是说可以一次性把原先需要迭代 400 次的循环给计算完毕。这里查看如下的C++综合 log 可以发现对 LOOP_J 进行了 unroll 展开,并且是完全展开。可以在一次迭代中完成原先的 LOOP_J 所有计算,而 LOOP_I 没有过多的数据依赖, i 的值是已知的0~19, 所以猜测这里通过表达式优化,直接把所有循环都展开了。

这里的表达式优化是指:每个 A[j] 都会和 0~19都乘以一遍,可以直接求和 0~19,然后一起乘以A[j], 而由于 A[j] 与同一被乘数相乘,所以还可以累加所有的 A[j]

INFO: [XFORM 203-502] Unrolling all sub-loops inside loop 'LOOP_I' (loop_pipeline.cpp:5) in function 'loop_pipeline' for pipelining.
INFO: [HLS 200-489] Unrolling loop 'LOOP_J' (loop_pipeline.cpp:5) in function 'loop_pipeline' completely with a factor of 20.
INFO: [XFORM 203-11] Balancing expressions in function 'loop_pipeline' (loop_pipeline.cpp:5:26)...19 expression(s) balanced.
INFO: [HLS 200-111] Finished Loop, function and other optimizations: CPU user time: 0 seconds. CPU system time: 0 seconds. Elapsed time: 0.111 seconds; current allocated memory: 166.698 MB.
INFO: [HLS 200-111] Finished Architecture Synthesis: CPU user time: 0 seconds. CPU system time: 0 seconds. Elapsed time: 0.058 seconds; current allocated memory: 163.661 MB.
INFO: [HLS 200-10] Starting hardware synthesis ...
INFO: [HLS 200-10] Synthesizing 'loop_pipeline' ...

通过 Schedule Viewer 发现 0~9 每个时钟周期加载了两个 A[j]的值并存储,然后在此过程中不断的累加 A[j]i的值,在第10 ~ 11 完成两个累加结果最终相乘。在第 12 周期完成与 acc 初值的累加,在第 13 周期完成结果的写回。

2.3 对函数 loop_pipeline 进行 pipeline,并对数组A进行分组

编译指示:

set_directive_pipeline "loop_pipeline"
set_directive_array_partition -dim 0 -type complete "loop_pipeline" A

综合报告:
image.png

假设我们能在一个时刻而不是顺序依次读取所有 A[j],然后 DSP 的数量也足够,那么可以在一个周期内完成所有 A[j] 与 sum(0, 19)的累加值的相乘,查看 Schedule Viewer 发现 0~4周期用 17个 DSP 完成了所有乘法,第 0~6 周期完成所有加法,最后写入结果。

2.4 思考

  • 如何在对最外层的 LOOP_I 循环进行 pipeline 时,控制最内层的 LOOP_J 部分展开。
    尝试的方法:
  • 编译指示solution: 对最外层的 LOOP_I 进行 pipeline 的同时,设置 LOOP_J 按因数 factor = 10 展开 不可行
set_directive_pipeline "loop_pipeline/LOOP_I"
set_directive_unroll -factor 10 "loop_pipeline/LOOP_J"

但实际测试外层的pipeline 会强制完全展开内循环,相关 log 如下:

INFO: [HLS 214-188] Unrolling loop 'LOOP_J' (loop_pipeline.cpp:11:9) in function 'loop_pipeline' partially with a factor of 10 (loop_pipeline.cpp:3:0)
INFO: [XFORM 203-502] Unrolling all sub-loops inside loop 'LOOP_I' (loop_pipeline.cpp:5) in function 'loop_pipeline' for pipelining.
WARNING: [XFORM 203-503] Ignored partial unroll directive for loop 'LOOP_J' (loop_pipeline.cpp:5) because its parent loop or function is pipelined.
INFO: [HLS 200-489] Unrolling loop 'LOOP_J' (loop_pipeline.cpp:5) in function 'loop_pipeline' completely with a factor of 2.

可以看到 WARNING 报的信息是 由于父循环是被 pipelined,所有内循环的部分展开被忽略,强制按 factor = 10x2=20 完全展开

  • 编译指示solution: 对最外层的 LOOP_I 不进行 pipeline 的同时,设置 LOOP_J 按因数 factor = 10 展开,与此同时,由于可以同时进行内循环不同迭代的计算了,所以需要对 A数组进行分组,确保可以同时读取对应的A数组中的元素,否则 II 是没法正常优化为 1 的 可行
set_directive_unroll -factor 10 "loop_pipeline/LOOP_J"
set_directive_array_partition -dim 0 -type complete "loop_pipeline" A

image.png
Latency = 4 + (40-1) x 1 = 43, Trip Count = 20x20 / factor(10) = 40
而且这里自动给 II 优化为了 1, 相当于 pipeline 了

没有数组分组的综合报告:
image.png
最终优化后的 II 由于数组接口限制就没法达到 1,而是变为了 5

相关推荐

  1. Jenkins Pipeline 流水线

    2024-04-23 02:40:02       43 阅读
  2. Pipeline流水线组件

    2024-04-23 02:40:02       26 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-04-23 02:40:02       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-04-23 02:40:02       106 阅读
  3. 在Django里面运行非项目文件

    2024-04-23 02:40:02       87 阅读
  4. Python语言-面向对象

    2024-04-23 02:40:02       96 阅读

热门阅读

  1. 什么是掩码补丁位置?

    2024-04-23 02:40:02       56 阅读
  2. 图标字体库——Font Awesome

    2024-04-23 02:40:02       39 阅读
  3. MySQL用户和权限管理深入指南

    2024-04-23 02:40:02       30 阅读
  4. 机器学习——逻辑回归

    2024-04-23 02:40:02       36 阅读
  5. 如何使用React.js从头开始构建TODO应用

    2024-04-23 02:40:02       32 阅读
  6. hbase基础(三)

    2024-04-23 02:40:02       36 阅读
  7. FreeLearning PHP 译文集翻译完成

    2024-04-23 02:40:02       40 阅读
  8. ELB 后端主机异常

    2024-04-23 02:40:02       34 阅读
  9. JVM面试题

    2024-04-23 02:40:02       37 阅读
  10. 浏览器乱码的原因是什么?如何解决?

    2024-04-23 02:40:02       39 阅读