引言:在产品研发中,涉及大规模FPGA逻辑设计中,过长的编译时间给FPGA调试带来极大挑战,本系列文章给出优化FPGA编译时间的系统方法,加快FPGA设计及调试。
本文作为开篇,首先对编译时间进行分析,定位耗时大头;再按根因从精简约束、启用增量流程、用好Vivado报告诊断工具、采用OOC模块级综合;跨多款设计则借助自动推荐策略与directive扫描,迭代收敛,切实缩短编译总耗时。
1、 节省编译时间分析
1.1 编译耗时分析
编译耗时受多种因素共同影响,包括工具流程、工具选项、RTL改动、约束改动、目标器件,以及工具在实现设计过程中碰到的各类关键问题。除此之外,所用机器的配置及其负载情况也是重要变量。不过在本文中,我们只会探讨与设计本身和工具流程相关的因素。
1.2 如何衡量编译耗时
对比某项改动前后的编译耗时时,随时可以在vivado.log中搜索编译耗时信息。例如,你会看到类似下面的日志行:
place_design: Time (s): cpu = 03:21:34 ; elapsed = 01:58:53 . Memory (MB): peak = 21362.934 ;gain = 3668.312 ; free physical = 12076 ; free virtual = 142273
这一行给出了place_design阶段的总耗时和内存占用情况。其中的cpu时间,是该阶段内多个线程各自执行子任务的累计耗时(各线程CPU时间相加,所以通常会大于实际流逝的时间)。我们真正要看的是elapsed时间—也就是place_design从启动到结束之间实际过去了多少时间。日志里还有其他几行时间统计也采用同样的格式,只是前面不带命令名,例如:
Time (s): cpu = 00:34:50 ; elapsed = 00:17:24 . Memory (MB): peak = 21322.859 ;gain = 3612.184 ;free physical = 42807 ; free virtual = 172805
这里统计的是某个step内各个阶段各自的耗时。因此,要得到编译总耗时,只需把每个step报告的耗时相加即可,无论工程模式(project mode)还是非工程模式(non-project mode)都适用:T(synth_design)+T(opt_design)+T(place_design)+T(phys_opt_design)+T(route_design)需要注意一点:工程模式还要额外花时间去生成多份报告文件,这部分耗时也应当计入。这样算下来,你就能清楚地看出到底是哪个step占掉了编译总耗时的大头。如果你想排查的是单条命令的耗时,而不是某个运行step,可以用Tcl命令自行计时。例如,执行下面的命令,就能看到一次get_pins只花了44毫秒:
set start [clock milliseconds]; get_pins -filter {NAME =~ *FPGA*/O};set stop [clock milliseconds] ; puts "TIME: [expr $stop -$start]"
TCL console output -> TIME: 44
当你面对一份几千行的超大约束文件、想快速了解每条命令各花了多少时间时,这种方法就很管用。对于增量流程(incremental flow),我们会在日志文件中直接生成一张对照表,把默认流程和增量流程在每个 step 上的耗时分别汇总,读起来一目了然。不过有一点要心里有数:增量运行会在综合或实现阶段从参考设计(reference)中读取并复用信息,因此省下来的时间只集中在部分阶段;如果网表改动很大,能被复用的部分就会变少,编译耗时也会随之受到影响。
1.3 编译耗时分析
拿到编译耗时的相关数据后,下一步就是对这些数据做分析,找出哪个 step 影响最大,从而确定该从哪里下手优化。下面举几个例子:
示例 1:假设我们发现route_design占掉了大部分编译耗时。再去看日志报告,会发现问题出在设计利用率偏高,引发了布线拥塞,所以布线器花了很长时间。这种情况下,可以借助report_design_analysis生成拥塞报告,定位到底是哪个区域或哪个模块在制造瓶颈。定位清楚之后,要么调整代码、改用更不容易造成拥塞的RTL写法,要么直接调用工具自带的拥塞优化策略来应对。 示例 2:如果设计中实例化了大量 IP 或模块,而它们每次运行都并不需要重新生成,那就可以从流程优化的角度切入。比如,对于设计中某些已实例化的IP核,可以开启IP缓存(IP caching),这样就不必每次都重新生成IP,省掉IP生成的那部分时间。再比如,可以采用自底向上的开发流程做并行开发,从而缩短设计实现与集成的整体周期。还可以在完成一次完整流程、得到引导文件(guiding file)之后,启用增量流程,加快后续的设计迭代。
2、 针对设计自身问题的编译耗时优化
要解决由设计本身引起的编译耗时问题,可以采用下面几类技巧。我们按照常见的根因与对应解法,把它们归为四类:
- 约束(Constraints)增量实现(Incremental Implementation)工具自带选项(Tool-Driven Options)使用 OOC 运行(Out-of-Context Run)
2.1 约束
设计中的约束应当干净、合理、精确。这样的约束能让系统内存得到更高效的利用,从而缩短整体编译耗时。具体做法是:先分析编译耗时中有多少花在了约束上,再弄清楚这段时间具体是怎么被消耗掉的,最后优化约束的写法、让它更“省力”。相关内容会在后续文章展开讲解。
2.2 增量流程
图1、增量编译流程
增量综合与增量实现,是一种上手简单、也便于管理的提速手段,往往能带来很实在的收益。当设计的改动比例很小时,后续运行可以快速迭代;而且多次运行的结果会更稳定、更可预期,编译耗时自然就降下来了。
2.3 工具与报告选项
工具自带的选项,往往能先帮你消掉一批设计自身的问题——比如设计 DRC 违规、时序约束覆盖不当、设计拥塞等,这些都会严重拖慢编译。因此,在做更深度的流程优化之前,建议先把这几项过一遍。具体做法是借助 Vivado 自带的一些报告工具生成报告,再做针对性分析。 (1)report_methodology:用于排查设计方法论层面的问题。报告里标出的不良实践,有些会直接影响编译耗时;在下一次运行之前照着报告改掉,往往能很快见效。 (2)report_design_analysis:用于定位时序 / 复杂度 / 拥塞问题。通过查看最关键的几条路径、设计复杂度(Rent指数)以及布局热点区域,你能更清楚地看清设计里的瓶颈所在,也更容易想到可行的改进方向。 (3)report_qor_suggestions:会生成一份更底层的 Tcl 脚本,里面给出额外的优化建议,并且可以直接回灌到设计中执行。 (4)report_exceptions:用于查看时序例外(timing exceptions)之间的相互作用及其覆盖情况。时序约束若收得过紧(over-tighten),反而会让编译耗时变长。
2.4 OOC运行/模块级综合
把设计中的各个核心模块放到OOC(Out-of-Context)模式下运行,会生成并行的子运行(sub runs),从而缩短设计集成阶段的时间。同时,模块级综合还允许你为不同的子模块分别指定编译耗时策略或性能策略。集成时间一缩短,编译总耗时自然就降下来了。
3、 跨多款设计的编译耗时优化
当需要在多款设计之间统一压低编译耗时时,就得换一套更通用的思路:先把方法铺开,再根据每款设计的实际结果不断迭代调整。这类技巧可分为以下两类:
3.1、Vivado 自动推荐流程与约束
从 2019.1 版本开始,Vivado 新增了一项能力:以 Tcl 文件的形式自动生成多套推荐策略,生成后直接source即可用。工具会替你完成筛选,省掉手动并行扫所有设计、再横向对比的苦力活,迭代周期自然就缩短了。
3.2 实现指令
(Directive)的全组合扫描 在现有策略里挑选时,该选哪些以“缩短编译耗时”为目标的 directive;此外,还会给出一些建议,帮助你自己定义一套省编译耗时的策略。
4、 总结
综合运用上述技巧,我们希望达成的闭环是:先拆解编译总耗时,明确优化范围,找到可行的提速手段,最终把耗时真正降下来。
65