FPGA编译加速实战:从13小时到5小时的优化路径与工程实践
2026/9/7 11:28:56 网站建设 项目流程

做大型FPGA工程的人对“编译加速”这四个字的体会,绝对不止是点一下Progress看进度条那么轻松。我前阵子处理一个包含了PCIe、DDR、MIPI图像采集、Biss-C编码器解析、还有卡尔曼滤波的综合项目,Vivado全流程跑下来13小时,属于典型的“下午启动、第二天早上看结果,结果还经常死在布线那一步”的状态。后来我把流程从头到尾重新梳理了一遍,项目RTL基本没动,只是把机器环境、工具参数、增量复用和约束里的隐形问题处理干净,同样的设计压缩到了5小时以内。这篇文章不聊那些玄乎的“优化框架”,只讲我在这个工程上真正做过、并且验证有效的事。

1. 13小时的时间黑洞:先摸清编译流程各阶段的花费

1.1 综合、优化、布局、布线,谁在偷偷吃掉时间

拿到一个13小时的工程,第一件事不是去调工具参数,而是先搞清楚时间到底烧在哪个阶段。Vivado的GUI里有Report Runtime,跑完一次完整流程后能直接看到每个步骤的耗时;如果是Tcl流程,也可以用time命令包一层。我这边基线数据大致是这样:

阶段基线耗时占比主要工作
综合 synth_design2h00m约15%RTL转网表,受逻辑规模和层次影响
逻辑优化 + 布局 place_design4h30m约35%常量传播、寄存器合并、cell摆放和拥塞控制
布线 route_design5h00m约38%给所有net找物理通路,处理绕线和DRC
bitgen + 时序分析等1h30m约12%生成比特流、跑时序报告

布线是绝对的大头。很多人第一次接触FPGA实现流程时,觉得布线不就是把所有连线拉通吗,为什么这么慢?实际上布线器面对的是几千上万个pin、几十万条net,在满足工艺规则和拥塞约束的前提下找可行解,本质是个离散优化问题。它不像人手工画PCB那样可以盯着局部慢慢调,它要全局统筹,改一条net可能影响周围一片资源。这就导致布局阶段留下的任何隐患,到布线阶段都会被放大。

还有一点容易被忽略:综合阶段之所以耗时不小,是默认情况下很多IP和子模块都被重新综合了一遍。如果工程里挂了MIG、PCIe、MIPI D-PHY这类大IP,光这些IP的综合就能占用半小时以上。后面我会详细讲怎么把这些“重复劳动”彻底减掉。

1.2 同样的工程换台机器,为什么能差两三倍

这里先泼一盆冷水:如果你的工程跑在NFS网络存储上,就算把CPU从16核换成64核,你可能也感受不到成倍的提升。我基线那13小时就是跑在公司服务器上的,双路24核,配置不算差,但工程目录整个放在NFS上。Vivado跑实现时会在.runs目录里产生大量小文件,每一次读写都要经过网络文件系统,连文件锁都要走网络round trip,这一项白白吃掉的时间非常可观。

第二个隐形损耗是工具默认线程数。Vivado里general.maxThreads的默认值通常是4,很多服务器明明空闲着一大半核心,工具却只用4个线程在跑。这不算bug,是EDA工具一贯保守的行为——并行会改变布局布线结果,工具厂商宁可在默认参数上求稳。但对我们来说,这就是白白浪费的算力。

第三个坑是内存。Place和Route阶段的内存占用跟设计规模和资源利用率强相关,高利用率的大工程跑到一半吃掉几十GB内存很正常。如果机器只有16GB或32GB,系统开始换页到磁盘,那4小时的布线能拖到10小时以上。看任务管理器里内存是不是红了,往往比调工具参数更先解决问题。

2. 把闲置的CPU利用起来:并行线程与多Job调参

2.1 Vivado里最常见的三个并行入口

在我这个工程里,验证后实际有效的是三个地方:

第一个是全局线程数。Tcl脚本最前面加一行:

set_param general.maxThreads 8

如果你的服务器是16物理核,设置8是安全线;如果是32物理核,可以试12到16。我最终在这台双路24物理核服务器上用的是8,再往上跑,WNS开始出现波动,收益也不明显了。

第二个是综合和实现阶段的-jobs参数。Vivado 2019.2之后的版本,synth_designimpl_design都支持-jobs N,它能让工具在可拆分的子模块上做并行处理。注意一个前提:综合时不要用完全打平层次的策略,层次保留得越完整,并行粒度越大,收益越明显。Fully flat的网表基本没有可并行的边界。

第三个是launch_runs-jobs。整条命令可以这么写:

open_project /home/build/fpga_accel.xpr set_property strategy "Flow_RuntimeOptimized" [get_runs impl_1] launch_runs impl_1 -jobs 4 -to_step write_bitstream wait_on_run impl_1 open_run impl_1 report_timing_summary -file timing_summary.rpt report_utilization -file utilization.rpt report_congestion -file congestion.rpt

这一段可以直接存成build.tcl,以后每次编译都通过vivado -mode batch -source build.tcl执行。把日常构建脚本化之后,不仅省掉了开GUI等待的时间,更重要的是每个工程师拿到的流程都一样,不会出现“你机器上参数和我机器上不一样”的扯皮。

2.2 Quartus与国产工具链的等效设置

用Quartus做Intel平台的朋友也不用羡慕,对应的设置同样存在。GUI里在Assignments -> Device -> Device and Pin Options -> Compilation Process settings里可以设置并行编译进程数,对应的QSF约束是:

set_global_assignment -name NUM_PARALLEL_PROCESSORS 4

要在命令行模式跑的话,quartus_mapquartus_fit都支持--parallel=N参数:

quartus_map --parallel=4 top_level quartus_fit --parallel=4 top_level

Quartus还有一个“Smart Recompile”特性,也就是常说的增量编译,它和Vivado的增量实现思路类似,前提是改动足够小。老版本叫Smart Recompile,新版Prime里在Compilation Process相关设置里也能找到。

至于高云、安路这类国产工具链,我最近也在一两个项目里接触过。它们的编译本身比Xilinx小工程快不少,但并行度和增量编译的支持还在成长期,能调的旋钮不多。遇到这种环境,我的建议是不要跟工具死磕并行,把精力放在机器本地磁盘、内存容量和约束正确性上,收益反而更直接。

2.3 并行的边界:不是核心越多越快

并行布局布线并不是把同一份工作拆给4个人做得更快这么简单,它更像让4组人同时设计一栋楼的不同楼层,每层内部可以快速推进,但楼层之间的管线、承重结构怎么搭,还是需要整体协调。线程越多,协调成本越高,结果的不确定性也越大。

我实测下来几个规律:在同一个工程上,maxThreads从默认4提到8,全流程能明显缩短;从8提到16,收益曲线变缓;从16提到32,不仅没快,WNS还出现了几十皮秒的劣化。原因很简单,物理核就那么多,超线程带来的逻辑核做布局布线这种重计算收益有限,反而增加了内存带宽竞争。

另外要注意内存。每个并行线程都会增加内存压力,跑之前先用free -g看一下机器内存余量。如果在布线阶段发现swap开始增长,不要犹豫,立刻把线程数降下来。线程多但内存在换页,这个状态比少几个线程更致命。跑的过程中可以用htop观察,CPU利用率长期在90%以上且稳定,说明并行是有效的;如果看到某个进程在D状态等IO,问题多半出在磁盘或网络存储上。

3. 增量编译与DCP复用:让工具只处理真正变化的部分

3.1 增量编译为什么能快,又为什么不是万能药

增量编译的原理不复杂:工具把上一次综合或实现的中间结果当作参考,这一次只处理发生变化的部分,其余直接复用。我打个比方,它更像编辑器的“增量保存”,而不是每次Ctrl+S都把整篇几千行的文档重新写入硬盘。

在Vivado里,综合侧可以用synth_design -incremental指定上一次综合生成的参考DCP;实现侧的思路类似,跑一次完整流程后保留route_design的DCP作为参考,下次小改动就在这个参考上做增量place和route。不同版本的具体开关名有点差异,建议以装的那个Vivado版本配套的UG904为准,但整体思路是一样的。

增量不是万能的。它只适合“小改动”场景,比如改了一两行RTL、调了一个参数、加了一条约束。如果版图级结构发生了剧烈变化,比如顶层模块换了个连接方式、某个大模块整体重写,增量参考反而会成为负担,工具要带着旧约束重新协调,还不如直接全量跑。

更关键的一点是:增量编译跑得快,不代表结果质量好。工具复用了旧布局布线的框架,如果新逻辑恰好落在原来拥塞的区域,局部绕线会比全新布局更差。所以我给自己定了个规矩:每次增量跑完,必须对比report_timing_summary里的WNS和TNS,劣化超过一定范围就回退到全量。

3.2 OOC与IP综合缓存:最容易忽略的“现成资产”

OOC,也就是Out-Of-Context,脱胎于“孤立上下文综合”。Vivado的IP Catalog默认对IP做OOC综合,每个IP在自己的独立synth run里先综合成网表并缓存下来。这样做的最大好处是:只要IP配置没变,下次全流程构建可以直接复用这个网表,不用在顶层综合时把所有IP再推一遍。

我这里遇到的情况很典型:工程里的MIG DDR控制器、PCIe核、MIPI D-PHY,三个大IP的综合加起来接近40分钟。一开始没人动OOC设置,每次全量构建都在重复烧这40分钟。后来确认这几个IP的配置不再变化,直接把它们的综合结果固化成DCP留在工程目录里。之后即使改动顶层逻辑,只要不碰IP配置,综合阶段就能稳定在一小时以内。

这里有个容易踩的坑:很多人为了省磁盘,清理工程时会把.runs目录整个删掉。这在Vivado里非常伤,因为IP的综合缓存、甚至某些中间checkpoint都在这下面,删了之后下次构建全部从头算,等于把前面省的时间全吐回去。我的建议是:.runs目录里认准IP对应的_synth_1子目录,这个不要轻易动;真要清磁盘,优先清历史备份和日志。

3.3 大工程里做参考Checkpoint的实操流程

具体到我这边的做法,可以归纳成四步:

  1. 花一次完整构建的时间,跑出一个质量合格的基准版本,确认WNS为正、没有DRC error。
  2. 把这个版本的route_design DCP单独备份出来,放到固定目录,命名里带上日期和构建tag,比如route_ref_20250117_1200.dcp
  3. 后续小改动就用这个DCP作为实现侧参考,配合-incremental跑。
  4. 每攒到一定程度(比如改动了较大的模块,或者更新了Vivado版本),重新生成一次基准DCP,扔掉旧的。

这套流程听着简单,但能把“增量”从碰运气变成可控操作。最关键的就是固定目录和命名规范。流程脚本化之后,人不用记今天该用哪份DCP,脚本从约定好的路径读就行了,出错概率直线下降。

4. 从13小时到5小时的优化实录

4.1 基线工程:PCIe、DDR、MIPI和图像处理堆出来的13h

先交代一下工程背景。用的是Xilinx Kintex UltraScale XCKU040,资源利用率不算低:LUT用了约61%,BRAM 70%,DSP 49%。时钟一共9个,最紧的是PCIe参考200MHz、MIPI像素时钟150MHz和DDR 300MHz这几组。模块方面,有PCIe DMA通道做数据上传,DDR3通过MIG做缓存,前面是MIPI D-PHY接收图像,中间是一整条图像处理流水线,包括滤波和几处乘加运算,另外还有一个跟STM32H743通过FMC通信的控制口。Biss-C编码器走的是低压差分信号,采样逻辑和跨时钟处理也在这颗FPGA里。

这样的设计,模块之间跨时钟路径非常多,只要约束有一点不干净,布线器就要花大量时间去优化那些本不该被优化的路径。基线13小时就是这么来的:NFS存储、默认4线程、策略还是Default、IP每次都是重新综合、还有一个历史遗留的Pblock箍住了图像处理模块。四个问题叠加,哪有跑得快的道理。

4.2 第一轮调整:线程、策略和运行环境

第一轮我没有改任何RTL和约束,只动了三件事:

  • 把工程从NFS迁移到服务器本地NVMe盘上,剩余空间预留了200GB以上。
  • 在Tcl脚本开头加上set_param general.maxThreads 8
  • 把实现策略从默认切到Flow_RuntimeOptimized,布局布线指令换成place_design -directive RuntimeOptimizedroute_design -directive RuntimeOptimized

一轮跑完,全流程从13小时降到8小时40分。WNS从0.21ns降到0.15ns左右,还都是正余量,TNS为0,没有出现时序违例。这个结果说明,工程原来的时间大量浪费在环境层面,跟设计本身关系反而不大。

当时有个小细节:迁移到NVMe之后第一件事,不是跑全流程,而是跑了一次增量验证。之前NFS上卡IO的情况立刻消失,综合阶段快了20分钟。IO瓶颈对EDA的影响,比很多人想象中大得多。

4.3 第二轮调整:IP缓存与增量实现

第二轮开始动流程结构。我把MIG、PCIe、MIPI D-PHY三个大IP的OOC综合缓存确认了一遍,不再让顶层run重复综合它们。然后对改动比较小的迭代,直接用上一版route_design.dcp做增量实现,不再每次从头place和route。

这一轮效果立竿见影:综合从1小时30分缩短到1小时以内,布线在增量模式下从3小时30分压到1小时45分,全流程到5小时5分。WNS继续往下走了一点,到0.11ns附近,余量开始有点紧张,但时序报告还是干净的。

这里我多说一句:增量实现在这个阶段能省这么多,是因为当时的代码改动集中在图像处理流水线里的一小块,顶层框和DDR、PCIe这些大块都没动。如果你今天改了顶层连接关系,明天又换了接口协议,增量能帮你的就非常有限了。所以增量该不该用,第一判断标准是“我到底改了多少、改在哪个层次”。

4.4 第三轮调整:Pblock和约束里的隐性炸弹

时间压到5小时出头之后,我其实已经满意了,但接下来碰到一次布线爆慢,逼着我把最后的问题挖了出来。

那次只改了一处RTL逻辑,增量跑却花了两倍时间。查report_congestion发现,DSP密集的图像处理区域有大量高拥塞net绕行,追根溯源是前人留下的一小块Pblock——它圈定的区域资源太紧,导致布局器把大量cell塞进临近区域,布线器只能在外围绕长线。我把那个Pblock删掉,让Vivado自己floorplan,同样设计下布线时间直接砍下来40分钟,WNS还从0.11ns回弹到0.18ns。

与此同时,我在report_clock_interaction里看到PCIe参考时钟和MIPI像素时钟之间有大量跨域路径分析。这两组时钟在实际设计中是不需要相互约束的,但工程里一直没有写set_clock_groups,工具不得不把它们当作同步路径处理,白花了很多优化时间。

补上约束之后:

set_clock_groups -asynchronous \ -group [get_clocks -include_generated_clocks pcie_ref_200m] \ -group [get_clocks -include_generated_clocks mipi_pixel_150m] \ -group [get_clocks -include_generated_clocks ddr_clk_300m]

布线阶段又缩短了差不多半小时。

这一轮做下来,调试用的快速流程稳定在4小时30分,WNS 0.117ns,虽然余量不算宽裕,但作为日常迭代足够用。如果要出发布版本,我会回到Default策略配合增量复用,全流程大约5小时10分,WNS回到0.185ns。

4.5 优化前后的对比表与可复用的核对清单

整条优化路径的时间变化如下:

轮次综合布局布线bitgen等全流程WNS
基线(NFS/默认参数)2h00m4h30m5h00m1h30m13h00m0.210ns
第1轮:本地NVMe + 线程1h30m2h30m3h30m1h10m8h40m0.152ns
第2轮:IP缓存 + 增量实现1h00m1h30m1h45m0h50m5h05m0.116ns
第3轮:修Pblock + 洗约束0h50m1h00m1h00m0h40m4h30m0.117ns
发布构建(Default + 复用)0h50m1h20m1h50m1h10m5h10m0.185ns

以后不管谁接手这个工程,我都会甩给他一份核对清单:

  • 工程目录必须放本地NVMe或SSD,不允许在NFS上跑全流程。
  • 编译前确认物理内存剩余充足,free -g看一遍。
  • set_param general.maxThreads要和机器物理核匹配,别无脑拉高。
  • 大IP用OOC,.runs目录别乱删。
  • 增量跑之前确认改动范围小,跑完必查WNS/TNS。
  • 约束里该写的set_clock_groupsset_false_path一个都不要省。

5. 提速之后必须做的代价审计与质量兜底

5.1 并行和RuntimeOptimized对时序余量的真实影响

提速这件事没有免费的午餐。RuntimeOptimized之所以快,是因为它减少了布局布线的迭代次数,用了更激进的启发式策略。代价就是最终结果和Default策略会有差异,最直观的就是WNS变化。我在这个工程上实测的结果是:同样一套RTL和约束,Default跑出来的WNS是0.21ns,RuntimeOptimized只有0.12ns左右,余量直接少了近一半。

如果你的设计本身就在时序收敛边缘挣扎,WNS常年是0.05ns、0.03ns这种水平,千万别用RuntimeOptimized。这种快速策略适合“我还有余量、我需要快速验证功能”的场景,比如调DDR读写逻辑、改图像算法中间某级流水,这时候编译越快越好,布局质量差一点无所谓。

到了发布阶段,该回到Default还是回到Default。5小时10分的发布构建对我这个工程完全可接受,没必要为了再省半小时拿一个余量很薄的bitstream去流片或交付。

5.2 一键可查的报告:WNS/TNS、拥塞度和DRC

每次跑完编译,特别是用了增量或者RuntimeOptimized之后,至少要看这几个报告:

  • report_timing_summary:看WNS、TNS、WHNS,这是时序质量的直接体现。
  • report_congestion:如果布线阶段耗时异常,先看拥塞图,通常能直接定位到是哪块区域在“堵车”。
  • report_utilization:确认资源利用率没有因为并行策略出现离谱的变化。
  • report_clock_interaction:看跨时钟域路径是不是符合业务预期。
  • report_route_status:看有没有undriven pin、unrouted net之类的低级问题。

我一般会把这几个报告的文本导出,和前一版做diff。WNS掉了超过50ps,或者TNS从0变成了负数,就得停下来追原因,而不是继续叠加新的优化手段。很多“编译越跑越慢”的坑,其实都是在快速流程里埋下质量隐患,后面又花几轮时间去还债。

5.3 什么时候绝不能用快速策略

总结几条我的硬性规则:

  1. WNS已经是负值的时候,不要用RuntimeOptimized,也不要盲目叠加增量。
  2. 最终发布、交付客户、或者做板级时序验收的时候,必须用Default或Explore策略重新跑一遍完整实现。
  3. 刚升级Vivado版本后的第一次构建,不要做任何增量,先全量跑出新的基准。
  4. 如果团队里有多个工程师共用一台编译服务器,并行线程数和并发构建数要约定好,否则互相抢CPU,大家一起变慢。

这些规则看着保守,但能避免大多数“优化到最后反而翻车”的情况。

6. 从设计源头把“编译慢”彻底降下来

6.1 RTL层的组合逻辑深度才是原罪

工具层面的优化再多,也解决不了RTL本身写得让布局布线很难受的问题。组合逻辑深度过深,是编译慢最典型的“设计债”。

比如我这套图像处理流水线里有一处卡尔曼滤波相关的计算,几个乘加串在一起,组合逻辑链能到八层以上。跑到150MHz这种频率下,setup就很难收,布局布线器为了压缩这条关键路径会反复尝试不同的cell摆放和绕线,一个模块拖累整个工程。

后来我给中间结果加了寄存器,把一条长链拆成两三级流水。时序好收不说,布线时间也跟着降——因为工具要优化的“关键路径”数量变少了,它自然跑得更快。这里有个反常识的点:多刷几个FF并不会增加多少编译负担,相反,组合逻辑层级降下来之后,布线器的压力会下降一大截。

不要为了省几个触发器,让工具在布局布线阶段多跑几个小时。

6.2 约束写对了,才能真正减少迭代轮数

约束问题对编译时间的影响,很多人排到最后才想得到,实际上它经常是最大的隐性杀手。

举个最常见的场景:两个异步时钟域之间本来不需要时序收敛,但你没有写set_clock_groupsset_false_path。工具不知道这层业务关系,就会把跨域路径全部纳入时序分析,然后花大量时间去优化一条本来不该被优化的路。跑出来的报告里还会出现一堆假violation,逼着你一遍遍“修”,每修一次就是一轮全量编译,时间就是这么螺旋消耗掉的。

约束的正确用法不是给工具加压,而是帮工具缩小优化空间。时钟关系写得越准确,工具越知道哪些路径必须死磕、哪些可以放弃,布局布线器的精力才能花在刀刃上。这个理解到位之后,你会发现很多“编译慢”的工程,其实是“约束不准确”的工程。

6.3 团队流程里的编译资产复用思路

最后说一点超出单机范围的思考。编译加速不只是个人的事,团队协作方式对编译时间的影响可能更大。

我现在的建议是:把编译做成自动化流水线,RTL代码合并后自动触发一次完整构建,把综合结果、实现报告、DCP全部归档。第二天谁需要看时序,直接拉报告,不用每个人都在自己机器上重跑一遍。IP的OOC缓存更要当成团队资产来管,谁改了IP配置要通过变更记录明确标示,没改的模块大家共用同一份缓存。

对于Zynq和MPSoC这类带PS端的项目,还有一条很实在的经验:PS端的软件改动,不要每次都触发整个PL重新编译。PL侧代码没动,硬件设计没变,固件更新就不需要重新跑一遍Bitstream流程。把这些不同环节的构建关系从流程上分开,团队的整体编译等待时间能降好几个量级。

更进一步,如果某个功能模块是独立的、可重构的,可以研究静态部分重构。把固定逻辑和可变逻辑拆开,调试时只编译变化的那个分区的网表和比特流,其余部分原样复用,这种架构级的加速,比任何工具参数都彻底。当然,这个方案对设计架构有要求,不是所有工程都能用,但一旦用上,你会觉得“等13小时”这个词彻底成为历史。

我在实际做这个优化项目之前,一直以为编译加速是IT运维或者EDA工具专家的事。真跑完这一轮之后才发现,里面大部分动作,比如看时间分布、调线程数、管理DCP缓存、清理约束、排查Pblock,都是我们普通逻辑工程师完全能自己动手做的事。下次再有人跟你说FPGA工程跑得慢只能换服务器,你可以先打开Report Runtime看一眼,再决定要不要信他。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询