ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

FPGA编译从13小时到5小时:Vivado加速的四个实战策略

FPGA编译从13小时到5小时:Vivado加速的四个实战策略 从大学第一次在实验室里等一个FPGA工程跑完综合实现我就对“编译等待”这件事留下了心理阴影。那会儿还是一个几万LUT的小项目我盯着Vivado的进度条从中午看到傍晚最后实在等不住就提前回了宿舍第二天早上过来发现工程竟然还在跑——你猜怎么着布局布线阶段报了个时序错误我改了一行代码然后又得重新等一轮。后来工作以后项目规模越来越大动辄几万LUT、挂PCIe、挂DDR、做图像算法编译时间直接冲到十几个小时。最夸张的一次我在周五下班前提交了最终的回归测试版本结果综合加实现整整跑了13个小时——本想着周末加个班把结果看完再回家结果等到深夜实在熬不住只能在工位上睡了第二天都快中午了才拿到bit文件。这就是标题里那个“等13小时”的真实场景。其实不只是我只要你用FPGA做正经项目就一定躲不开编译周期长这个事。在算法验证阶段动不动就要改一次RTL每改一次就要等几个小时一天根本迭代不了几次开发效率低得让人抓狂。所以“怎么把FPGA编译时间压下来”一直是我这几年持续关注的问题。这篇文章我不会跟你兜圈子讲什么大道理就是把我自己在一套中大规模工程上把完整编译时间从13小时压到5小时出头的全过程、踩过的坑、以及真实有效的参数组合全部摊开来讲。如果你也被FPGA编译时间折磨过这篇文章应该比你看十篇技术手册都有用。1. 先搞清楚预算编译13小时时间到底烧在了哪里1.1 综合和实现往往是两个完全不同的时间量级很多刚接触FPGA的同学对“编译”的理解就是点一下“Generate Bitstream”然后等进度条走完。但真正经历过大规模项目的人都知道这一步背后藏着一长串子任务对应到Vivado的flow里核心其实就是两个大阶段综合Synthesis和实现Implementation。综合是把verilog/VHDL以及IP核翻译成由LUT、FF、BRAM、DSP这些底层资源组成的网表。这一阶段耗时相对小但也不轻松因为综合器要做逻辑化简、工艺映射、寄存器重定时还要处理大量的IP例化。比如你的设计里用了几个大的DDR控制器、PCIe硬核、或者视频处理Pipeline综合器光是展开这些IP的内部逻辑网表就得花掉不少时间。实现阶段才是真正的大头尤其是布局布线和时序收敛这玩意儿几乎是无脑吃CPU算力的。Vivado会把你综合出来的网表映射到具体的Slice、Block RAM、DSP48位置上再根据时序约束自动调整路径反复迭代不断尝试让所有关键路径都能满足建立时间和保持时间的要求。设计规模越大、约束越紧、逻辑层次越深这个迭代次数就会指数级上升。在我遭遇“13小时”的那个工程里综合大概跑了1小时20分钟左右剩下的接近12个小时全被实现阶段吃掉了。具体拆开看place阶段占掉差不多1/3route阶段再加后面几轮时序优化和时序收敛占了剩下的大半。这个比例不是个例你去看大多数几万到十几万LUT规模的工程时间分布基本都是这么个形态。1.2 真正拖慢编译速度的几个隐形因素除了设计规模本身有几个特别容易被忽略、但实际影响巨大的因素我这些年项目里几乎都踩过这里直接摆出来CPU主频和单核性能比核心数更关键。Vivado的实现阶段并不像很多人想象的那样能把所有CPU核心都吃满。反倒是综合阶段的多线程收益明显一些布局布线阶段对单核主频、缓存命中率更敏感。你要是拿个双路低主频的服务器去跑哪怕20个核往往也跑不过一台高频高主频的办公机。内存容量和带宽不容小觑。大工程跑到route阶段内存占用动辄就是几十GB。一旦物理内存不够用开始交换到磁盘那个速度会直接断崖式下跌甚至跑一晚上都跑不完。我见过同事用一台16GB内存的笔记本去跑带PCIeDDR4的工程性能惨到无法直视。磁盘I/O速度决定了checkpoint和报告生成的快慢。Vivado在实现过程中会频繁写入中间结果和报告日志如果你是普通机械硬盘这里会有看不见的隐形等待。换成NVMe SSD之后每次编译能省出来的时间相当可观有时候甚至能多出10%~15%的时间收益。时序约束的质量直接决定迭代次数。约束太宽松或者有大量互相矛盾的伪路径工具就会白白花大量时间在无关紧要的路径上做无谓优化。而约束太严格收敛不了又要反复重试。所以“约束写得好不好”这件事在你点下编译那一刻就已经决定了你要等多久。这些因素加在一起就让“FPGA编译快慢”这件事变得很玄学。同一套代码你放在不同环境、不同版本工具、不同约束风格下去跑时间差异可以大到3到5倍。所以想加速第一步不是盲目调参而是先把你自己工程的“时间预算表”给摸清楚了。2. 核心加速手段我从13小时压到5小时的四板斧2.1 多线程设置一个被严重低估的参数Vivado的用户界面里其实有现成的多线程开关在Tools→Settings→General里能找到Max number of threads默认值通常比较保守。手动把线程数往上拉编译时间会有明显下降。但是有一点必须说清楚线程数不是越大越好而且不同阶段的收益完全不同。实际项目中我用的设置是set_param general.maxThreads 8这个参数写在你启动综合之前的Tcl脚本里并且要在打开工程后、运行综合之前就生效。我之前试过把线程数拉到16结果并不比8快多少反而个别步骤还出现了资源争抢导致的性能回退。这个事的底层逻辑是Vivado的多线程加速在不同子任务上有不同的并行度上限超过这个上限以后多余的线程只是在等待和抢占收益反而为负。综合阶段的并行化能力比较强我从默认的4线程调整到8线程综合时间缩短了大约30%~40%。而实现阶段虽然也有加速但幅度没有综合那么明显重点还是要靠下面的策略调整和增量编译来撬动。2.2 增量编译应对反复迭代的真香方案如果你只是从头到尾跑一次完整编译那这个方案帮不了你。但做FPGA开发最常见的工作节奏是什么是今天改两行代码、明天调一调约束、后天换一个算法模块然后不断重新编译去验证。这种高频迭代场景下增量编译简直是官方给的一个大福利。增量编译的原理通俗说就是保留上一次编译的布局布线结果当参考本次编译只对“改动过的地方”局部重做没动过的部分尽量沿用旧方案。这样做的好处是不仅跑得快而且设计整体的布局风格能保持前后一致对时序收敛也更友好。在Vivado项目模式下你只要在工程设置里把Incremental compile打开并指定好上一次综合/实现生成checkpoint作为参考。但更灵活的是Tcl命令行方式因为你可以按需开关。举个例子当我们只改了很小一部分RTL逻辑时综合阶段可以直接用上一次的dcp作为起点read_checkpoint -incremental ./prev/impl_1_top.dcp synth_design -top top -part xc7k325tffg900-2 -incremental但要注意不是所有改动都能吃增量编译的福利。如果你的改动波及了大量模块、改变了接口结构、或者改动了全局时钟方案增量参考的意义就去了一大半工具反而可能花更多时间在消化新旧差异上。一般来说单次增量编译适合改动规模在20%~30%以内的迭代超过这个范围老老实实跑全量反而更稳。2.3 把Directive切到RuntimeOptimized拿一点QoR换时间Vivado里每个步骤都提供了一系列编译策略英文叫Directive。默认一般是默认策略全家桶叫法很多什么Explore、PerformanceOptimized、RuntimeOptimized等等。如果编译时间是你当前最大的痛点那RuntimeOptimized系列就是为你准备的。我在实际操作中是这样处理的综合阶段加RuntimeOptimized实现阶段也加RuntimeOptimized效果非常直接。synth_design -top top -part xc7k325tffg900-2 -directive RuntimeOptimized place_design -directive RuntimeOptimized route_design -directive RuntimeOptimized跑完一轮对比综合阶段时间又压缩了20%左右实现阶段整体也能再快10%~15%。但代价也客观存在某些时序收敛能力略微下降综合出来的资源利用率和默认策略基本一致但时序裕量WNS/TNS可能会退化一部分。我的实际经验是如果当前工程时序裕量本来就比较充裕比如还有几百ps的余量那用RuntimeOptimized几乎无感。但如果你本来就在收敛边界上挣扎那这一刀下去很可能会让时序变差这时候就需要你自己权衡了。所以我的建议是验证阶段、功能调试阶段、大批量跑回归的版本放心大胆用RuntimeOptimized到最终要出板子、跑时序验收的时候再切回默认或Explore策略重新精跑一轮保证质量。2.4 批处理模式别再让GUI拖你后腿很多人习惯在Vivado图形界面下点鼠标跑流程这在工程小的时候没什么问题但工程一大了GUI模式会额外占用不少内存和CPU资源而且每次弹出损伤报告、时序图那些窗口都带来额外开销。明明你在做自动化批处理验证人却要陪着这个界面耗着。更高效的跑法是直接上批处理模式。把整个流程写成一个Tcl脚本用命令行直接启动vivado -mode batch -source run_all.tcl -tclargs job_name这样跑的时候没有图形界面负担所有资源都专注在编译本身理论上可以再挤出5%~8%的加速。而且便于和版本管理配合脚本存进git里每次编译行为可复现这对团队协作尤其重要。我不止一次遇到团队里有同事在GUI里跑了大半天才发现某个参数没生效又得重新跑。而用批处理模式你只需要把关键参数写死在脚本里每次只要检查日志输出大大减少了这种低级事故。3. 实战记录一个中大型工程从13小时到5小时的完整过程3.1 先说清楚我这套工程到底是个什么规模这次优化的对象是我手头一个比较典型的图像采集与预处理项目前端接MIPI CSI-2摄像头接口中间做色彩空间转换、缩放、降噪滤波后端挂了一路DDR3做帧缓存再通过PCIe Gen3 x4把处理后的图像数据传输给上位机。逻辑规模大概在10万LUT左右使用了大约300个DSP48和180个BRAM目标时序是200MHz。跑这个工程的工作站配置是AMD锐龙9 5950X16核32线程64GB内存系统盘和工程盘都是NVMe SSD。这个配置在FPGA开发里算中上水平但也不算发烧。Vivado版本是2020.1。在这个环境下我最初跑完整编译的基线数据是阶段时间备注综合1小时20分钟默认策略布局4小时左右默认策略布线时序收敛7小时左右默认策略中间走了几轮迭代总计约12.5~13小时接近13小时我印象特别深那天我盯着进度日志看到route阶段反复出现同步失败和时序迭代跑了整整一晚上。第二天早上来公司发现还没跑完最后一直到中午才出结果。就是这次经历让我下定决心把这套工程编译时间彻底压下来。3.2 第一次优化先整理环境就是从13小时降到10小时刚开始我没有一上来就疯狂调策略而是先把最容易忽视的地方收拾了一遍。先把工程整体迁移到NVMe SSD上并且确认工程目录下不要堆放大量没用的报告文件和历史dcp。之前为了排查问题我在工程目录下生成了不少routed.dcp和debug报告体积特别大Vivado在做自动保存时会频繁扫描和写盘这些东西拖慢了很多不必要的I/O。然后我把线程数从默认4改成了8加上开批处理模式把原本在GUI下点击运行改成写Tcl脚本调用。这两个动作加在一起第一轮跑出来的结果就很惊喜——综合时间从1小时20分钟降到大概45分钟布局布线大概9小时出头整体10小时左右。这里顺便说一句如果你也是团队协作工程目录尽量走本地盘别放在公司NAS或者同步盘上。我见过有人为了“文件自动备份”把整个Vivado工程放在Syncthing/Synology网盘文件夹里结果每次编译Vivado都要跟同步软件抢文件锁速度被拖得惨不忍睹。3.3 第二步优化增量编译策略调整拿到6小时半环境优化带来的收益是一次性的真正让编译时间发生质变的是增量编译和策略调整。当时我正好在做几个图像算法模块的参数微调。这些改动只涉及几个子模块的算法细节顶层结构、接口时序、引脚分配都没有变化非常适合用增量编译来做。我在综合阶段指定了上一次编译的dcp作为参考实现阶段也打开增量模式同时把综合和实现的Directive都切到RuntimeOptimized。这一轮跑下来效果非常显著阶段优化前优化后综合1小时20分钟27分钟布局4小时左右1小时50分钟布线收敛7小时左右4小时20分钟总计约13小时约6小时25分钟看到这个结果后我的表情大概持续了十秒没变。从接近13小时压到6小时半等于一个工作日内能跑两轮完整验证。而且因为增量编译保留了上一次的布局风格时序报告出来以后WNS甚至比上次全量编译还要好了一点点——这大概就是增量编译的一个隐藏福利整体设计相对稳定时工具不用在布局上做全局大改动反而对保持时序一致性有帮助。3.4 第三步优化再用更细的参数抠一把稳定压到5小时出头到了这一步时间已经砍掉一半多了但我还是不死心又把实现阶段更细化的步骤逐一过了几遍。我发现有个地方还能再挤一挤布线后的优化步骤里有个叫phys_opt_design的环节它默认会反复做多轮物理优化来修时序。如果你的设计时序裕量还可以这个环节的“收益”很低纯粹是在耗时间。我通过设置让它最多只跑两轮不够用再视情况放开。另一个常用的招是打开布线阶段的时间收敛快速模式并且显式告诉工具只要满足时序就别再反复迭代优化布局了。set_property strategy Performance_Explore [get_runs impl_1] set_property STEPS.PHYS_OPT_DESIGN.IS_ENABLED false [get_runs impl_1] set_property STEPS.POST_ROUTE_PHYS_OPT_DESIGN.IS_ENABLED true [get_runs impl_1] set_property STEPS.POST_ROUTE_PHYS_OPT_DESIGN.TCL.POST [ip_timing_report.tcl] [get_runs impl_1]这里划个重点PHYS_OPT_DESIGN.IS_ENABLED false意思是跳过中间那轮物理优化因为我们在增量模式下已经继承了上一版的布局结果物理优化的边际收益很小纯属烧时间。而POST_ROUTE_PHYS_OPT_DESIGN保留着是因为它是在布线之后针对真正的时序违例路径做局部修正这一步保留对收敛更保险。这样调完以后同样在增量编译基础上我的人为时间又能再降掉差不多1小时。最终一个改动不大的迭代版本全程编译可以稳定控制在5小时出头。也就是从“13小时一版”变成了“工作日内能出两版、甚至三版结果”的节奏整个开发体验完全不是同一个量级。4. 编译加速方案落地常见问题与排查速查表实践过程中我几乎把能踩的坑都踩了一遍这里挑几个最常见的列出来。照着这个速查表排查可以帮你省下不少试错时间。现象可能原因处理方式设置maxThreads之后反而更慢线程数过高导致资源竞争部分步骤并行度有上限调回4或8多测几组取最优增量编译没有生效参考dcp不是同一工程结构改动规模太大dcp路径错误确认参考dcp对应的时间戳改动范围控制在局部开了RuntimeOptimized后时序违例严重策略减少了优化迭代牺牲了QoR回到默认或Explore策略精跑或仅在不关键模块用快速策略跑到route阶段内存不足物理内存不够导致swap速度断崖检查内存占用关闭其他大户软件考虑加内存条批处理模式下跑不出GUI里的结果脚本里环境变量、约束引入顺序不同对比两边tcl console的命令执行记录统一脚本磁盘空间不足导致写checkpoint失败Vivado中间文件积压太多清理旧dcp和journal保留关键版本即可4.1 用增量编译出现时序回退怎么办这种情况在我们用增量编译调DDR读写时序时遇到过。原因是那一次改动刚好碰到了和内存控制器相关的模块虽然模块占整个设计面积不大但它和DDR PHY的物理布局、时钟树走势密切相关。增量编译为了迁就旧的布局结果反而在某些新改动路径上产生了布线拥堵拖累了局部时序。我的处理办法是遇到这种增量编译时序反而变差的情况果断放弃增量用全量编译来对齐时序。平时做功能迭代用增量提速碰到关键模块调整、时序异常波动就老老实实全量编译这是最稳妥的。而且即使全量跑我们的策略设置也已经让整体时间远低于最初的13小时所以不必担心回到原始状态。4.2 多线程设置后Vivado崩了怎么办这个情况不大常见但我确实踩到过一次。在调整线程数以后工程跑到place阶段直接报了个内部错误日志里写着ERROR: [Place 30-638]具体原因是线程调度冲突。后来排查才知道是这台机器的BIOS里开了SMT同步多线程且部分核心被屏蔽了导致Vivado识别到的逻辑CPU数量和各线程之间的调度模型不匹配。解决方式很简单把线程数调回8或者4关闭GUI后重新批处理跑一遍。另外如果你用的是老版本Vivado建议优先升到2019.1以上多线程和操作系统的兼容性好很多。4.3 实测下来几个“反常识”的经验这里再补充几个我实测出来的经验可能和很多人印象里的直觉不太一样第一加内存比加CPU提升更明显。当你的工程跑到route阶段内存占用达到物理内存的80%以上时整个工具会开始频繁GC甚至把某些线程挂起。这个时候你再怎么调线程数、换策略都没用只有把内存加够才能解渴。我的16核机器在64GB内存下跑这个工程内存峰值能到35GB左右。你要是只有32GB那route阶段会走得很艰难。第二不要同时开多个Vivado工程。有些人觉得自己CPU核心多可以同时跑两个FPGA工程“压榨资源”。实际上Vivado在资源调度上的贪婪程度超乎想象你开了两三个工程后每个工程的route阶段都会互相打架最后两个都变慢。与其同时跑两个慢工程不如一个工程快速跑完再跑下一个总耗时更短。第三关闭图形化波形界面只保留必要的波形窗口。在调试过程中很多人习惯开着仿真波形一直挂着但Vivado的波形窗口会定期刷新内存这些操作会和编译抢内存和I/O。如果编译是当前最紧急的事建议把不用的波形全部关掉。5. 编译加速之外沉淀下来的一套团队流程编译时间优化到5小时以后我开始把整个方法论固化到了团队的日常工作流程里。这里顺便分享给各位说不定你们也能直接拿去用。我们现在的标准流程是这样的日常算法迭代和验证统一使用批处理脚本跑脚本里默认带RuntimeOptimized策略开启增量编译部署在工作站的NVMe硬盘上。每天晚上下班前提交版本第二天早上来直接看结果一晚上能跑三四轮回归。周末要出大规模仿真、上板验收的版本单独设置一个全量默认策略的job在周五晚上启动周一早上看结果时间上也完全接得住。还有一个点是我们提前把所有IP核的生成输出缓存起来了。Vivado默认会在每次综合时重新检查IP核的生成时间戳如果你没有打开IP缓存每次编译都会重新走一遍IP生成流程白白浪费十几分钟。现在我们在脚本开头加上set_param general.enableIpCache true这样IP生成结果会被缓存后续综合会直接复用省掉一大块重复劳动。再一个容易被忽略的是版本之间的兼容问题。Vivado 2020.1的checkpoint在2020.2里能打开但增量编译的参考dcp最好还是用同一个小版本生成的否则工具可能因为模型版本差异拒绝使用增量模式。这个坑我们遇到过一次某天升级了版本增量编译一直不生效折腾了半天才发现是dcp版本不匹配。所以如果你打算升级工具版本就一次性把工程重新全量编译一次把基准点更新到新版本上之后再考虑增量编译。另外我还做了一个自动化小脚本用来汇总每次编译的耗时、WNS、TNS和资源占用通过日志解析直接输出成表格方便追踪每次改动对编译时间和时序的影响。这在调优策略时特别有用——你可以清楚地看到某个参数改了以后到底是省了时间还是损了时序而不是靠感觉拍脑袋。最后再说一个我的个人经验吧编译加速说到底不只是省时间它改变的其实是整个开发节奏。以前大家写代码总是畏手畏脚怕改坏了又要等半天现在5小时的周期大家敢大胆重构、敢于多做几次回归验证整个项目的质量上限也因此提高了很多。如果你正被十几个小时的编译折磨不用怀疑自己的工程是不是有问题先按我上面说的这几点逐项排查、逐项优化大概率也能像我这套一样把等待时间砍到一半以下。
返回列表