ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

FPGA编译时间从13小时压到5小时:Vivado全流程优化实践

2026/9/9 7:21:20 拓冰建站 浏览量
FPGA编译时间从13小时压到5小时:Vivado全流程优化实践 1. 编译一次13小时这个坑我是怎么踩进去的2022年我接手公司一个通信基带项目时第一次全量编译跑了整整13小时。当时我以为是工程太大忍了。后来换了SSD、加了内存、把CPU从8核升到16核还是9小时。再后来我把Vivado版本从2019.1升到2022.1依然是7到8小时打底。直到我开始认真梳理一次编译到底干了什么、时间花在哪里才发现问题根本不在电脑配置上而在读不懂编译报告、用不对综合策略、不敢关默认选项。这篇文章不聊怎么调时钟约束、不聊怎么写RTL就聊一件事FPGA工程编译时间从13小时压到5小时以内中间每一步我做了什么哪些做法是真正有效的哪些是在交智商税。先交代一下我当时的工程规模后面所有数据都以这个工程为基准Xilinx ZU19EG逻辑单元约113万LUT用了差不多38万BRAM 61%DSP 28%涉及两个PCIe硬核、三组DDR4、一组QSFP-DD、若干低速接口。Vivado版本是2021.2运行在Ubuntu 18.04上CPU是i7-9700K32GB内存。这个规模在中小公司和研究所里相当典型——不是超大规模芯片设计但每天迭代几次一次编译半天起步谁都顶不住。在那个阶段我每次提交代码之后是这么过的启动综合然后切出去写文档、回邮件、看论文每隔半小时回来看一眼进度条。等到综合跑完再跑布局布线往往已经下班了。第二天早上来看到时序违例改两行代码再启动新一轮9小时。一个星期的有效工作时间被编译时间吃掉大半。这就是大多数FPGA工程师的真实工作节奏也是我下决心彻底解决这个问题的直接原因。编译慢这件事本质上不是一个“忍一忍就过去”的问题。它直接影响你的迭代密度、排错速度和交付节奏。时序违例的定位在项目后期可能需要几十次迭代如果每次迭代都要等8小时以上整个团队的开发效率会呈指数级下滑。后面我做的所有优化都是为了把编译时间变成一种可以被管理、被预期的资源而不是听天由命的等待。别急着换电脑。先看清楚你手头的时间到底被谁吃掉了再决定下一步怎么走。2. 先搞清楚一件事13个小时到底烧在了哪个阶段在动手优化之前分辨清楚时间分布比什么都重要。FPGA编译流程其实分得很清楚综合Synth会把HDL翻译成门级网表逻辑优化、布局、布线、时序收敛和生成比特流则在后半段完成。不少人以为“慢”就是布线慢实际上一查OOC综合跟全局综合混在一起时间分配跟你想象的完全不一样。Vivado的命令行里有一个很好用的开关——-report_qor_suggestions和-no_preroute那样先不说第一步要做的就是开一次带详细日志的编译然后在每个阶段结束后记录时间戳。我给你一个可以直接抄的命令脚本。新建一个build_timing.sh内容大致如下#!/bin/bash source /tools/Xilinx/Vivado/2021.2/settings64.sh vivado -mode batch -source run_full.tcl 21 | tee full_build.logrun_full.tcl里用start_gui之前最笨也最可靠的办法就是把每个步骤分开open_project top.xpr # 只跑综合 reset_run synth_1 launch_runs synth_1 -jobs 8 wait_on_run synth_1 puts SYNTH_DONE [clock format [clock seconds] -format %T] # 只跑布局布线 launch_runs impl_1 -to_step route_design -jobs 8 wait_on_run impl_1 puts IMPL_ROUTE_DONE [clock format [clock seconds] -format %T]跑完之后你在日志里搜关键词就行重点看几个时间戳ERROR: [Synth 8-1236]这类错误之前记录的是综合启动到结束的秒数place_design完成时日志会打印当前是第几轮布局route_design结束后会输出总负裕量TNS和终止路径数我当时实测出来的时间分布大概是这样编译阶段耗时分钟占比综合含OOC IP综合178约34%布局96约18%布线142约27%时序优化比特流生成约109约21%总计约525100%综合占了三分之一布线占了差不多三成这两个大头不解决后面所有优化都是杯水车薪。说句实话当时看到这个分布我都愣了一下因为日常大家抱怨的都是“布线太慢”很少有人真正把综合单独拎出来算账。综合慢的一个关键原因是工程里挂了太多IP核每一个IP的OOCOut-of-Context综合都是单独跑的哪怕你用到的只有这个IP的一个最小配置它也会完整跑一遍例化和优化流程。这一步的核心收获是不要再凭感觉猜瓶颈拿数据说话。我在团队里要求所有成员在提“编译慢”这个结论之前必须先提供一份阶段耗时统计否则没有任何讨论基础。后面我们的优化全部对标这份数据改一步看一步从不盲目折腾。3. 从读写磁盘和进程调度下手把基础效率先提上来3.1 工程放本地、别放共享盘这一点太重要了必须放在最前面说。很多人习惯把工程放在公司服务器或者NAS共享目录里方便多台机器协同出图但Vivado的临时文件和中间文件极其密集在万兆网络上跑还行千兆网络下光是文件同步的开销就能把编译时间拉长一半以上。我自己实测过同一个工程放到本地NVMe上综合阶段耗时降低约22%布局布线阶段的IO等待时间明显下降。这个差距在远程桌面、云盘同步、版本管理客户端实时扫描目录的场景下会被进一步拉大。如果你非要用共享环境至少把Xilinx工程的.cache和.runs目录重定向到本地用符号链接挂过去不然后患无穷。另外还有一个容易被忽略的坑Windows下的Defender实时扫描、OneDrive同步、公司下发的终端管控软件会在编译期间反复扫描工程目录。把这些排除项配上或者干脆用Ubuntu做编译机能省出一大块不可见的时间开销。3.2 用好并行综合但进度条数字不代表实际负载Vivado的-jobs参数可以指定综合和布局布线用的线程数默认写4不一定吃满。我第一次直接开了16因为机器是8核16线程结果布局布线阶段出现了大量线程切换编译时间反而变长了。这是很多人忽略的一个点并行不是越大越好。Vivado的线程模型在综合阶段基本能线性扩展但布局布线的并行度受设计本身的顺序依赖限制线程太多会加剧cache miss和内存带宽竞争。经过几轮试错之后我最终确定了一个比较稳妥的经验值场景建议 jobs 数综合8布局6布线4你可以在Tcl里按阶段单独设置set_param general.maxThreads 8 set_param place.numThreads 6 set_param route.numThreads 4很多人看到任务管理器里CPU占用率100%就以为发挥到位了实际上Vivado有相当一部分时间是单线程或者双线程在跑关键路径其他核在等待。所以“CPU占用率100%”并不能说明瓶颈在CPU反而提示你可能在内存带宽或者文件锁上出了问题。3.3 内存、SWAP和临时目录的坑Vivado很吃内存。32GB看起来不小但如果你的工程像我当时那样BRAM用了六成布局布线阶段内存占用能顶到28GB以上。一旦触发swap编译时间就不是线性增加而是直接乘以三到五倍甚至直接卡死。你可以用free -h和top在编译过程中盯一下内存水位。如果发现swap被大量占用优先考虑以下几个方案把DDR频率从默认降下来这个对性能有微小影响但对内存带宽释放很有帮助适合仿真用机器升级到64GB内存条成本相对最低如果没办法加内存就别同时开多个Vivado工程这个看起来是废话但实际团队里经常有人忘了关另一个窗口另外强烈建议把临时目录挪到tmpfs内存盘上。在/etc/fstab里加一行tmpfs /tmp tmpfs defaults,size16G,mode1777 0 0然后把Vivado的缓存目录指过去set_param general.tempDir /tmp/vivado_cache实测综合阶段日志写入更快尤其是RTL分析过程中的中间文件读写明显减轻了磁盘压力。这个操作对SSD同样有效能减少不必要的磨损不说编译速度也能稳定提升5%到10%。4. 从策略层面削减综合和优化开销5小时的基础是这样打下来的4.1 删掉用不上的IP配置根治OOC综合时间爆炸这是我从13小时降到7小时的最大功臣没有之一。先吐槽一下不少人做FPGA工程特别爱“库里啥都放”DDR4控制器配完4个通道里面每个通道的example design也生成了一堆用不到的仿真文件PCIe的IP配的是4x Gen3实际只用了2x Gen2还挂了两个显示接口的IP只用于验证板卡综合时也不注释掉。结果是每一个IP的OOC综合都在白烧时间而这块时间在实际工作中是很少有人逐项审计的。我当时做了一件事打开IP Catalog逐个核对了工程里所有例化IP把不产生实际逻辑、只用于仿真或者实验的IP从综合设置里剔除掉。然后把DDR4控制器从2个降到1个临时版本PCIe抢在综合前切成gen2 x2。就这么一轮清理综合时间直接从178分钟掉到了95分钟下降46%。记住一个原则仿真和综合是两条路仿真IP不该进综合域。Vivado里你可以在Simulation Sources目录下放仿真模型但如果你把它们挂在Design Sources里综合器也会捎带手帮它们跑一轮流程这是最常见也最冤枉的时间支出。4.2 综合策略选对布局布线压力骤减Vivado综合策略默认是Vivado Synthesis Defaults这个选项比较均衡。真正想压时间我会额外做三个调整。第一个是-flatten_hierarchy。默认是full会把整个设计打平做跨模块逻辑优化。但在工程很大、层次清晰的情况下打平会引起综合器和后端工具之间反复传网表时间开销很大。设置为rebuilt可以把层次结构保留到布局布线阶段既不影响QoR还能显著缩短综合阶段。set_property strategy Vivado Synthesis Defaults [get_runs synth_1] set_property STEPS.SYNTH_DESIGN.ARGS.FLATTEN_HIERARCHY rebuilt [get_runs synth_1]第二个是-retiming。默认是关闭的。开启后综合器会把寄存器前移后移来平衡时序但代价是综合时间变长不少。我的做法是项目前中期不开只有到最后收敛阶段再开因为前中期你的约束一直在变开了也白开纯浪费。第三个是-keep_equivalent_registers。这个选项默认也是关闭的。打开后综合工具不会把你RTL里的冗余寄存器合并掉对最终面积和性能没有好处但某些场景下能减少跨模块的反复优化计算量。不过这个影响比较有限属于边际优化。综合策略的调整效果也很直观综合时间从95分钟降到了68分钟而且布局布线的拥塞度没有恶化。4.3 布局布线里的“省时间”参数改动之前想清楚布局阶段最费时间的操作之一是时钟树综合的增量优化。如果你的设计没有特别紧的时钟约束可以试试set_param place.maxFanout 128这是把高扇出信号的默认阈值抬高让综合器少插入几层缓冲器。扇出超过128的信号可能会变慢但如果你的设计主频不高150MHz以下这个改动几乎感觉不到差别布局时间能缩短10%左右。布线阶段有一个特别关键的选项set_param routed.routingScreens 32默认值我记得是96还是128数字越大布线器尝试的路径越充分但时间成倍增加。32这个值适合时序余量相对充足的设计如果约束很紧建议至少保持64。但我要强调的是这些参数属于“拿时序换时间”的杠杆动它之前务必看看你的TNS和WNS数据确保还有余量可挥霍。我当时主时钟约束是250MHz布线后余量还有0.6ns左右才敢把routingScreens调低。如果时序已经贴着限跑这样的调整会让你后续花更多时间在时序收敛上得不偿失。5. 增量编译其实挑食用对了是利器用不对是坑5.1 增量编译不是默认好用的Vivado从2019.1开始把增量综合和增量布局布线做得比较成熟了但“成熟”不等于“无脑用”。增量编译的原理是记住上一次编译的中间结果下次编译时只重跑有变化的部分。听起来很美好但有两个苛刻的前提RTL改动不能动到关键层次结构IP配置不能变。你只要改了端口列表、顶层模块例化关系或者改了IP配置增量编译很可能直接失效而且不会告诉你为什么只是默默地跑了全量流程你还以为它已经在帮你省时间了。我在项目中期给模块加了几个状态机输出信号导致好几个子模块的端口都变了增量编译不仅没有提速布局布线甚至比全量还慢因为增量数据失效之后还要额外花时间做一致性检查。从那以后我定了一条规矩大版本提交跑全量小修小补跑增量。5.2 增量跟out-of-context模块要分开对待一个工程里如果有多个OOC的IP增量编译对它们基本无效因为每次综合它们都会根据配置单独重跑。要想让OOC的IP不吃编译时间可以采用“复用上次结果”的方式。具体做法是在工程里勾选IP的Use Precompiled IP选项或者把它们从综合运行里摘出来改为直接引用之前生成好的DCP文件。这对完全固定参数的IP特别有效比如PCIe、DDR4这种已经收敛过的IP每次编译都重跑一套OOC流程纯属浪费生命。对于参数可能会频繁调试的IP我建议用set_property GENERATE_SYNTH_CHECKPOINT true生成一次综合网表后续的小改动不再触发它的重综合。5.3 增量编译的正确打开方式我后来形成了一套比较稳定的实践流程贴在下面供参考。# 打开工程 open_project top.xpr # 跑增量综合一定要勾选增量模式 set_property STEPS.SYNTH_DESIGN.IS_ENABLED true [get_runs synth_1] set_property STEPS.SYNTH_DESIGN.ARGS.INCREMENTAL [get_files top.dcp] [get_runs synth_1] launch_runs synth_1 -jobs 8 wait_on_run synth_1 # 增量布局布线 set_property STEPS.OPT_DESIGN.IS_ENABLED true [get_runs impl_1] set_property STEPS.PLACE_DESIGN.ARGS.INCREMENTAL [get_files top_route_design.dcp] [get_runs impl_1] launch_runs impl_1 -to_step route_design -jobs 6 wait_on_run impl_1前提是每次跑完都保留好上一次的DCP推荐放在一个固定的路径下比如./checkpoints/。这个流程在只改少数模块代码的场景下能把单次迭代从6小时拉到2.5小时以内。但如果你连续三次改的都是关键路径上的模块增量编译效率会越来越差这时候别犹豫直接全量跑。增量编译还有两个副作用你得知道第一生成的物理网表可能带着上次布线的形状记忆导致时序收敛的局部最优问题和全量不一样极端情况下会出现“全量能过、增量反而过不了”的情况。第二布局布线增量模式下时钟资源分配的灵活性会变小对大规模多时钟域设计不太友好。5.4 增量不生效时的快速判断一旦发现增量编译没有预期提速先查三个地方synth_1运行报告里有没有出现Incremental synthesis was not performed的标注impl_1日志里有没有read incremental checkpoint相关提示上一次的DCP路径是否被清理或者改名了这三个问题挨个排查基本能在五分钟内定位增量失效的原因。别等到编译跑完了再回头查白白浪费几个小时。6. 分布式编译多人多机协同时间顺着降6.1 分布式编译对FPGA工程的适用边界CPU资源不够就把多台机器聚起来一起算。这是很多工程师的直觉也是我在5小时目标达成后进一步压时间的方向。但这里有个认知要先纠正FPGA编译不是所有阶段都能分布式加速。综合阶段的多个IP的OOC综合和多个模块的综合相对独立可以拆开并行而布局布线阶段因为所有逻辑要落到同一颗芯片里资源分配强耦合Vivado本身没有提供跨进程的并行布局布线能力。所以分布式编译主要做两件事把多个IP的OOC综合拆到不同机器上跑利用hw_server远程连接到算力更强的服务器来跑全流程6.2 用多机并行跑OOC综合的实战配置工具层面Vivado支持-remote_ip_cache和-ip两种方式。我采用的是最朴素也最稳定的方法在CI服务器上把IP的OOC综合单独抽出来写成独立Tcl脚本。比如你有4个IP可以在四台机器上分别执行# 机器A open_project top.xpr set_property GENERATE_SYNTH_CHECKPOINT true [get_ips pcie_ip] synth_ip [get_ips pcie_ip]# 机器B open_project top.xpr set_property GENERATE_SYNTH_CHECKPOINT true [get_ips ddr4_ip] synth_ip [get_ips ddr4_ip]然后把各自生成的DCP汇总回主工程路径主工程里把IP的PROCESSING_ORDER改成early还是late按依赖关系设好就行。这样IP的OOC综合可以并行主流程的综合和布局布线在主服务器上跑整个墙钟时间能压缩不少。6.3 单机多工程并行真不建议有人图省事在一台机器上同时跑两个Vivado工程以为核多没事。这个操作我强烈不建议。且不说内存带宽分配的问题单是两家综合器的临时目录、共享IP缓存、许可证文件的争抢就能让你崩溃。有一次我这么干结果两个工程同时读同一个IP的缓存文件其中一个直接把IPC文件写坏了后续重新生成IP花了大半天。自那以后团队里定死一条规矩一台编译机同一时间只跑一个主工程所有编译任务提交到队列里排队执行。6.4 硬件加速方案成本高但效果猛如果你预算充足还有一条路用专门的大规模FPGA服务器配合Vivado的Hardware Server模式把整个综合和布局布线放在远端高配机器上跑本地只做RTL修改和约束管理。我当时测试过一台双路AMD EPYC 7742、512GB内存的服务器跟本地那台i7机器对比全量编译从525分钟降到了320分钟单纯CPU多核性能提升带来的收益非常可观。如果你经常处理百万门级以上的设计这笔硬件投入大概率是划算的。7. 实测成绩单与最终执行清单到这里把优化方案汇总一下看看到底能把13小时压缩到什么程度。优化动作阶段时间变化累计耗时基线默认配置共享磁盘IP冗余全部0约525分钟工程迁移到本地NVMe临时目录进tmpfs全部-约40分钟约485分钟清理冗余IP仿真源分离综合-约83分钟约402分钟综合策略调整flattenrebuilt综合-约27分钟约375分钟多线程参数微调布局布线-约35分钟约340分钟增量编译平均收益全部-约90分钟约250分钟多机OOC并行综合综合-约35分钟约215分钟约3.6小时当然这个数据需要说明前提增量编译的收益是在不改IP配置、只改部分RTL的前提下测的全量提交时数据和前面几行统一。按我实际工作的混合节奏平均单次迭代时间稳定在5小时以内紧凑一点可以到3.5小时全量提交约5.5小时。相比最初的13小时效率提升超过60%。如果你也想做同样的优化我建议按下面的顺序来不要在低收益的事情上浪费时间先做阶段耗时统计确认瓶颈分布把工程从共享盘挪到本地SSD/NVMe设置tmpfs缓存目录清理综合域里的仿真IP和冗余IPOOC综合的节省通常最大再根据阶段统计微调综合策略和并行度改动频繁但范围不深的时候再上增量编译最后考虑多机并行OOC综合或升级编译服务器坦白说预算有限的情况下把工程挪到本地、清掉冗余IP、综合策略选对这三步做完13小时降到7-8小时并不难基本是白捡的收益。而真正想稳定进5小时增量编译的高效解法和跨机器并行是绕不开的坎。我在实际踩坑中还发现另一个容易忽略的指标综合报告里的Critical Warning数量。如果某个模块有几百条critical warning综合器会在优化时不停尝试修正这部分隐形时间开销很大。把RTL里反复出现的latch推断、位宽不匹配、多驱动问题清掉综合时间还能再少10%左右。这块没有捷径只能靠代码规范约束。另外说一个容易被忽视的运维细节Vivado的许可证服务器有时会因为防病毒软件拦截或license过期导致后台悄悄重试连接。这类问题不会直接报错中断编译但会让综合器卡在某个阶段莫名增加几分钟到几十分钟的等待。遇到“编译时间突然比上次多了一大截”的情况先看一眼license server的状态别急着优化RTL。8. 动能升级这件事越早做越划算编译时间优化这件事收益是复利式的。省下来的每一小时都可以用来多跑一轮时序迭代、多验证几个边界条件、多跟算法同事对齐一个细节。等到项目流片前那一周才发现时序差0.1ns想改却跑不动编译那时候再回头做基础设施优化就真的太晚了。如果非要说一个最重要的经验我会选“先把数据拿出来”这六个字。所有时间优化都应该以阶段耗时统计为起点以改动前后对比为验收标准。不拿数据做依据的效率优化都是玄学操作的再多也找不到主次。记得给自己留一份“编译日历”。把每次提交的时间、编译各阶段耗时、改动内容记录下来。一周后再回看你会惊讶地发现哪些改动看似很小、对时间的影响却巨大——这些经验比任何工具技巧都值钱。编译时间压缩到5小时不是终点而是让你重新找回开发节奏的起点。真正的好运气都是留给那些把重复等待变成有效迭代的人。