Vivado多线程加速指南:综合、布局布线及仿真全面提速
发布时间:2026/10/4 6:06:40 锦皓数字建站

做FPGA的老哥应该都有这种体验Vivado跑一个稍微上点规模的工程综合随随便便就是二十分钟起步布局布线再来个把小时等得人怀疑人生。更让人窝火的是电脑明明有十六个核任务管理器里却只看到 Vivado 用着两三个核剩下的核心全在围观。我之前在调一块 Xilinx KU040 的采集卡时也被这个折磨了很久后来花了两天时间专门研究 Vivado 的多核多线程设置从综合、布局布线到仿真都做了细致的调优和测试总算把编译时间从原来的一个半小时压到了五十来分钟。这篇文章就是把我踩过的坑、验证过的方法、还有实测数据完整地整理一遍专治大型工程编译慢、CPU 利用率上不去的毛病。1. 先说清楚Vivado到底什么时候能用上多线程1.1 综合、布局布线的并行点在哪很多人一提到多线程加速第一反应就是“我把线程设满不就行了”。其实不行。你得先搞清楚 Vivado 在综合和布局布线各个阶段里到底哪些环节能并行、哪些环节天生就只能串行才能真正把多线程用对地方。先说综合。RTL 综合大致分这么几个阶段HDL 语法分析、RTL 级优化、逻辑综合布尔化简、技术映射、面积和时序优化。其中 HDL 分析和 RTL 级优化阶段因为不同模块之间的依赖关系比较少是可以在多个子模块之间并行处理的。比如一个顶层下面挂了 AXI 总线、DDR 控制器、图像处理流水线这几个大模块综合器完全可以同时去分析这些模块。但到了逻辑综合阶段很多操作是全局性的比如面积共享、公共子表达式提取、进位链优化这些都要站在整个设计的角度去统筹考虑线程之间没法同时推进。所以你会看到综合过程中 CPU 利用率出现明显的波浪形有时候冲到百分之三百多有时候掉回百分之一百多。布局布线的情况也类似。布局的初始阶段可以把设计划分成若干逻辑簇这些簇之间的初始布局可以并行处理速度提升比较明显。但到了全局布局迭代阶段模拟退火、时序优化这些过程有一个全局的状态每一步迭代都要依赖前一步的结果串行程度很高。布线阶段在近几个版本里改进比较大Vivado 2018.1 之后引入了区域并行布线的能力可以把布线资源按照物理区域进行拆分然后多个区域同时布线多线程收益比布局阶段更明显。明白了这些底层逻辑你就不会对“线程设高了但速度没上去”这件事感到奇怪。多线程对综合、布局布线的加速收益从来都不是线性的整体上能把时间缩短 30% 到 50% 已经是很理想的结果。1.2 为什么不是线程数越多越好接着上面的话说线程数设得太高反而会引入三个新的瓶颈内存带宽、同步开销、还有线程调度。综合和布局布线都是计算密集加内存密集的混合负载。线程一多每个线程都在疯狂访问物理内存内存带宽很快就会被吃满。我有一台 48 核的服务器实测下来用 8 个线程跑布局布线跟 4 个线程相比时间几乎没有缩短但内存峰值却涨了差不多一半。这就是典型的内存带宽瓶颈线程再多也只是在那儿排队等内存控制器。其次FPGA 工具链里有大量共享的数据结构比如时序图、约束网络、资源冲突表这些结构在并行处理时需要加锁来保护线程多了以后锁竞争会非常严重额外开销反而把并行带来的收益抵消掉。再一个就是线程调度的稳定性。Vivado 这种工具不像视频渲染或者编译 Linux 内核那样能完美地把任务切成 N 等份它的内部任务粒度有大有小线程数设置成非 2 的整次幂时调度器反而容易出现负载不均。根据我自己的经验比较稳妥的做法是把线程数设置为 CPU 物理核心数的一半到四分之一之间。比如 4 核就设 28 核就设 416 核可以试试 6 到 8。具体怎么设后面我会给你一个可以照抄的步骤。2. 全局线程设置GUI、Tcl 与环境变量三管齐下2.1 GUI 界面设置如果你用的是 Vivado 的图形界面最直接的方法是打开 Tools 菜单找到 Settings然后在 General 这个分类下面找到关于线程数的选项。不同版本的 Vivado 这个选项的位置略微有差别。2019.2 以后的版本通常在 Settings 里的 General 标签页中会有一个 Number of threads 的下拉框默认值往往是空着的意思是用工具的自动检测结果。你可以把它手动改成 2、4、6、8 这些值。这里有一个要注意的点在 GUI 里修改完线程数之后建议把整个 Vivado 关掉重新打开一次再加载工程。因为我试过在项目打开的状态下直接改线程设置然后立刻跑综合结果日志里输出的线程数还是旧的。所以别怕麻烦改完配置后重启一下工具确保参数完整加载。另外有些版本里还需要在综合和实现的各自设置里单独开启并行。具体路径是 Settings 下的 Synthesis 和 Implementation 分类里面有一个 Process 相关的设置项有的版本显示为 Number of jobs把它和全局线程数保持一致。GUI 方式有个好处是你不太容易写错参数名但缺点也很明显不好做版本管理和自动化。对于要频繁换机器、换同事协作的工程我更推荐用下面要讲的 Tcl 方式。2.2 Tcl 命令与环境变量设置在非项目模式下或者你想把设置固化成脚本最常用的手段是用 Tcl 命令。直接在 Vivado 的 Tcl Console 里敲一行set_param general.maxThreads 4这条命令的作用是设置整个 Vivado 进程允许使用的最大线程数。要注意执行时机必须要在加载设计、启动综合或布局布线之前执行。如果你写的是完整的非项目模式 Tcl 脚本那就把这一行放在 read_verilog、read_xdc 之前如果是交互式环境就在每次启动后先敲这一行再加载工程。项目模式下也有类似方式。你可以在工程的 Tcl 脚本里加也可以在 Implementation Settings 里通过 pre-hook 脚本的方式在综合或者布局布线开始前自动执行这行 Tcl。我个人比较习惯的做法是建一个单独的set_threads.tcl文件里面只写这一行然后在工程脚本里 source 它。这样换机器的时候只需要改这一个文件非常方便。除了 Tcl还有一个全局环境变量也值得关注VIVADO_MAX_THREADS。在启动 Vivado 之前设置好这个环境变量工具启动时会自动把它作为线程数的默认值。Linux 下这样写export VIVADO_MAX_THREADS4 vivado -mode batch -source run.tclWindows 下可以在系统环境变量里新建一个名为VIVADO_MAX_THREADS的变量值设为 4也可以写个批处理脚本。不过我要提醒一句不同 Vivado 版本对环境和 Tcl 设置之间的优先级处理不完全一样有的版本环境变量会被 GUI 设置覆盖有的版本则反过来。最保险的操作是环境变量和脚本里的set_param general.maxThreads都设置为同一个值两边一致就不会有歧义。2.3 验证线程数是否生效设置完之后怎么确认它真的生效了我常用的验证方式分两种。第一种是看日志。Vivado 在启动综合或布局布线的时候部分版本会在日志里打印一行类似Number of threads: 4的信息。如果你用的是较新的版本综合开始时打开综合日志窗口往下翻一翻一般能找到。第二种方式更直观Linux 下跑综合的同时开一个终端窗口用这条命令观察 Vivado 的线程情况ps -eLf | grep vivado | wc -l这个命令统计的是 Vivado 进程对应的所有线程数量。如果设置生效线程数会明显比你设置前的默认状态多。Windows 环境下就直接打开任务管理器切到详细信息标签页找到 vivado 进程右键选择显示线程数也能看到类似的结果。这里有个容易误判的地方线程数并不是稳定的。综合进行到不同阶段时Vivado 会动态调整内部线程池的使用所以线程数会来回波动这是正常现象。你只需要观察峰值线程数是否接近或超过你设置的数值就行。另外如果观察到的线程数连你设的一半都不到那就要去检查是不是别的因素把它限制住了比如脚本里其他地方又执行了一个set_param general.maxThreads 1或者环境变量和脚本值冲突了。这种冲突问题我后面在常见问题部分会详细说。3. 综合阶段加速Jobs、OOC 与增量综合3.1 synth_design 的 -jobs 参数怎么用对于非项目模式synth_design命令本身提供了一个专门控制并行度的参数-jobs。用法是在综合命令里直接加上它比如synth_design -top top -part xc7k420tffg901-2 -jobs 4-jobs参数的范围一般是 1 到 8它的作用是控制综合时并行处理子任务的进程数。需要注意这个参数和前面的general.maxThreads是两个不同层面的东西。general.maxThreads控制的是整个 Vivado 进程的线程池上限而-jobs更接近于控制综合阶段实际拆分出来的并行任务数。通俗点说general.maxThreads是“车间的最大人手”-jobs是“这条生产线放几条流水线跑”。两者配合时建议让-jobs的值不超过general.maxThreads的值否则多余的任务没有线程去处理反而增加调度负担。项目模式Project Mode下synth_design命令不用你手动敲但并行度同样可以设置。你可以在 Tcl 脚本里设置综合步骤的选项set_property STEPS.SYNTH_DESIGN.TCL.PRE {set_param general.maxThreads 4} [get_runs synth_1]或者更简单一点在 GUI 的 Synthesis Settings 里找到 Number of jobs 相关选项把它设成和general.maxThreads一致。我实测过一个典型的图像处理工程顶层大概几十万门级逻辑。默认设置综合用时 21 分半改成-jobs 4加general.maxThreads 4之后综合时间降到 13 分钟左右提升接近 40%。但再往上加到 6 或 8 个 jobs 时时间只降到 11 分钟收益明显递减这就是前面说的并行瓶颈。3.2 OOC 并行综合实战如果整个工程里有一些又大又稳定的模块比如 PCIe IP、DDR Controller、以太网 MAC这些模块每次综合都要花掉不少时间但它们本身的 RTL 几乎不变。这种场景最适合用 OOCOut-of-Context综合。OOC 综合的核心思路是把这些子模块从顶层设计中剥离出来单独进行综合并保存成独立的 checkpointDCP 文件。这样 Viviado 在做顶层综合时就直接读取已经综合好的子模块结果不需要重新综合。更妙的是多个 OOC 模块之间相互独立Vivado 可以并行启动多个 OOC 综合任务把多核利用率真正拉满。在非项目模式下流程大概是这样先综合子模块注意加-mode out_of_context它会关闭不必要的输入输出端口逻辑优化只按模块内部逻辑来综合read_verilog ddr_ctrl.v synth_design -top ddr_ctrl -part xc7k420tffg901-2 -mode out_of_context -jobs 4 write_checkpoint ./dcp/ddr_ctrl.dcp每个 OOC 模块都这样单独跑完后顶层综合时把这些 DCP 读进来再指定顶层模块Vivado 就会自动引用这些子模块的综合结果read_checkpoint ./dcp/ddr_ctrl.dcp read_verilog top.v synth_design -top top -part xc7k420tffg901-2 -jobs 4项目模式更简单选中要设为 OOC 的模块右键选择 Set OOC然后在 Run Synthesis 时 Vivado 会自动判断依赖关系并行启动所有 OOC 子模块的综合。OOC 还有个额外好处就是子模块的时序是单独分析的不会因为顶层其他模块的紧时序约束把子模块内部的路径也压得很紧综合质量有时候反而更好。不过要记住OOC 模块在顶层综合时是不能被 flatten 的否则综合器就找不到原层的 checkpoint 可以引用了。所以如果顶层综合脚本里有-flatten_hierarchy full这种选项记得改成none或者rebuilt。3.3 增量综合只改一个小模块时的提速方案还有一种很实用的加速思路叫增量综合。它的原理很简单如果这个工程之前已经综合过一次第二次综合的时候你只改动了 RTL 里的很小一部分那么 Vivado 只需要重新处理这个改动过的模块其他大部分模块的综合结果都直接复用上一次的省掉无数重复计算。非项目模式下通过-incremental参数配合之前的 DCP 文件实现synth_design -top top -part xc7k420tffg901-2 -incremental ./dcp/top.dcp -jobs 4这里-incremental后面跟的是上一次综合输出的 DCP 文件。要注意增量综合对设计改动量和约束改动都非常敏感。如果这次改动动了顶层接口、改了约束文件里大量时序约束、或者换了大面积逻辑结构增量综合可能非但不会快反而因为要做大量的一致性检查和局部重算比全量综合还慢。我自己的经验是改动范围控制在一个或两个子模块内部、顶层例化关系不变的前提下增量综合能快 50% 到 70%。项目模式下可以在 Synthesis Settings 里找到 Incremental Synthesis 的开启选项并指定上一个综合结果作为参考。但项目模式下增量综合的参考 DCP 管理比较繁琐我一般只在非项目模式的脚本流程里用。4. 布局布线阶段Directive 与线程的配合4.1 布局布线的多线程参数与内存考量布局布线阶段是整个 Vivado 编译流程里最耗时的一步通常占总时间的四成到五成以上。这个阶段的多线程同样受general.maxThreads控制。在设置好这个参数之后布局和布线任务会自动去使用线程池里的线程资源。不过两个阶段的并行效率差异不小布局阶段的有效并行度偏低布线阶段因为区域并行机制的存在并行度更高。一个比较常见的建议是如果你主要卡在布线阶段线程数可以设得略高一些比如 8 线程如果主要是布局阶段慢线程数设 4 就够了再高边际收益很小。另外在布局布线阶段内存的压力比综合阶段大得多。一个中大型设计在布局布线时Vivado 的内存占用经常会到 8 到 12GB。如果同时开 8 个线程你可以想象每线程都持有自己的数据结构副本内存占用会进一步攀升到 16GB 甚至更高。所以我强烈建议机器内存低于 16GB 的时候不要开超过 4 线程32GB 以上再考虑 6 到 8 线程。不要一味追求线程数量内存爆了之后系统开始疯狂 swap那速度惨不忍睹。还有一个很实用的思路是合理分配综合和布局布线的线程数。如果你的脚本是自动化流程可以分阶段设置。综合开始时执行set_param general.maxThreads 4布局布线前改成 8。这样做的好处是综合阶段内存压力小避免综合时的多线程抢占系统资源拖慢后续也能减少内存峰值。4.2 Directive 选型要速度还是要质量很多工程师在追求编译速度时只想到加线程忽略了-directive参数对编译时间巨大的影响。directive是 Vivado 布局布线阶段用来控制算法优化倾向的选项不同 directive 会让布局布线采用不同的启发式策略直接决定 QoRQuality of Results和运行时间。布局阶段常用的 directive 有Runtime、Explore、Quick和Congestion_SpreadLogic_high等几种。Runtime是默认值它在质量和速度之间取一个平衡Quick字面意思就是快它的优化迭代次数少运行时间短但布局质量通常会更差适用于快速评估或者时间极度紧缺的场景Explore会尝试多种布局策略然后挑选最优结果耗时最长但时序收敛性最好。布线阶段也有类似的 directive比如Runtime、Explore、AggressiveExplore、Quick、NoTiming等。我整理了常用几个的实测对比环境是 KU040 工程、8 线程布局布线Directive布局布线耗时时序收敛情况适用场景Runtime默认100%基准正常WNS可能略差日常迭代追求平衡Quick约为基准的70%时序通常变差WNS可能差几十ps快速编译验证功能Explore约为基准的130%-160%时序通常更好WNS能拉到很高时序难收敛时使用AggressiveExplore约为基准的180%以上时序提升有限少数情况更好终极收敛手段慎用如果你的目标是缩短编译时间在基本功能验证阶段可以把 directive 设成Quick配 8 线程能比默认配置快一半。但到了时序收敛阶段一定要切回Runtime甚至Explore不然会因为省了几十分钟的布局时间后续要花几天调时序得不偿失。4.3 增量布局布线的最佳实践增量布局布线和增量综合的原理一样上一次布局布线得到的 DCP 可以作为下一次运行的起点Vivado 会检测你改动的范围只对受影响的部分重新布局布线其他逻辑和布线保持原样。使用场景也很明确工程已经跑通过一次布局布线之后只改了 RTL 里一小段逻辑、或者只改了一条约束这时候用增量布局布线收益巨大。在项目模式下实现增量布局布线需要在 Implementation Settings 里勾选 Incremental Implementation然后指定上一个实现结果作为参考。非项目模式下的脚本是实现完一次之后保留 DCP后面运行新的实现时在place_design之前读入上次的 DCPread_checkpoint ./dcp/top_routed.dcp place_design -directive Runtime route_design -directive Runtime这里有个细节需要特别注意增量布局布线的前提是设计网表变化不能太大。如果 RTL 改动导致综合后的网表跟上次 DCP 的网表差异很大Vivado 会退回全面布局布线甚至可能在一致性检查时报错。所以增量布局布线真正适合的是“最后阶段”式的修改比如只换了引脚约束、只调了一小段逻辑、只加了一两条时序约束这时候效果最明显一般能省 40% 到 60% 的布局布线时间。还有一点增量布局布线有时候会因为保留了上次的布线而引入局部拥塞导致时序上反而更差。所以我通常的做法是先用增量跑一版看看结果如果时序有变差的趋势果断放弃增量直接全量重跑。不要为了省时间而牺牲最终的时序。5. 仿真加速多线程编译与流程优化5.1 xelab 多线程编译与优化选项仿真这块也有不少人忽略了多线程。Vivado 自带的仿真器 xsim 分为编译、仿真两个阶段。其中编译阶段把 Verilog/SystemVerilog 源代码转换为仿真可执行的中间代码是计算密集型是可以多线程加速的。而仿真运行阶段因为是事件驱动逐条推进的并行难度大一般线程收益不明显。编译阶段的多线程是通过xelab命令的-mt参数实现的。比如xelab work.testbench -mt 4 -O3-mt后面跟的是编译线程数-O3是仿真器的代码优化级别告诉编译器生成效率更高的仿真代码。我实测过一个带 UVM 验证环境的工程testbench 大概几万行代码加上一堆 IP 仿真模型。默认单线程编译要 5 分多钟加了-mt 4之后降到 3 分钟左右再用-O3优化之后又省了十几秒总共缩短了差不多一半。如果你的测试用例很多需要反复编译修改这个优势会被进一步放大。不过这里要提醒一句-O3优化级别较高编译时间会变长一点但仿真运行时间会变短。如果是跑大型的回归测试建议使用如果只是快速做个功能点验证用-O0或者默认优化级别就够了重点是编译速度快。5.2 第三方仿真器与增量编译技巧如果你用的是 ModelSim 或 QuestaSim 这类第三方仿真器多线程加速的思路也类似。ModelSim/Questa 的vlog编译命令支持-threads参数vsim仿真启动也可以加-threads。另外 Questa 的高级版本支持多核仿真在仿真阶段也能利用多核来并行处理事件队列。还有一个很值得推广的习惯是“库分离编译”。在仿真大型工程时IO、SerDes、DDR Controller 这些 IP 生成的仿真模型动辄几百 MB而且每次跑仿真都要重新编译一遍这些模型非常浪费时间。我的做法是先把所有 IP 的仿真模型编译成一个独立的编译库存放到专用目录里。之后跑仿真就只需要编译你自己的 RTL 和 testbench那些 IP 模型直接从这个库里读取编译时间能省下一大截。实际效果上我以前每次跑仿真前光编译 IP 模型就要 8 分钟用了独立的 IP 仿真库之后这部分时间压缩到十几秒几乎可以忽略不计。当然如果工程里 IP 本身更新了记得要重新编译那个仿真库不然模型版本对不上仿真行为会出很诡异的问题。我建议把 IP 仿真库的编译命令单独写成一个脚本文件当你更新 IP 后先执行一次再正常跑仿真。6. 常见问题与排查技巧实录6.1 线程设置不生效先查这三处我见过很多人在论坛上问设了多线程但 CPU 利用率还是上不去。出现这种情况排查顺序一般是三处第一确认环境变量是什么时候设置的。Linux 下如果export VIVADO_MAX_THREADS4是在 Vivado 启动之后才敲进去的那对当前这个 Vivado 进程根本不生效。必须确认是启动前设置的可以用echo $VIVADO_MAX_THREADS先检查一下环境变量是否在 shell 里。Windows 系统改完环境变量后要重开命令行窗口否则也不生效。第二检查 Tcl 脚本里有没有重置线程数的命令。有些工程师会把多个脚本工具串联起来可能前一个脚本里设了set_param general.maxThreads 4后面加载某个 Tcl 又执行了set_param general.maxThreads 1后者一执行前者的设置就白费了。搜一下全部 Tcl 脚本里所有出现maxThreads的地方确认逻辑上没有冲突。第三确认你用的不是老的 2016 及更早版本。Vivado 早期版本对多线程的支持非常有限部分版本里线程参数只对布线生效综合阶段基本是单线程的。如果你用的版本比较老建议至少升到 2018.1 之后再谈多线程加速版本不同带来的差异比你想的要大得多。6.2 线程拉满后更慢或者内存溢出这几乎是人人都躲不过的坑。我刚开始折腾多线程的时候也干过这种事机器 32GB 内存觉得设 8 线程一定比 4 线程快结果跑布局布线跑了半小时直接 OOMVivado 进程被系统杀掉前功尽弃。之后就长记性了布局布线前先看一眼自己这台机器有多少可用内存、系统里有没有在跑其他大进程再决定线程数。除了内存上限线程拉满后更慢的另一个原因是前面提到的同步开销和内存带宽瓶颈。如果你观察到线程从 4 升到 8内存没有爆但时间变慢了这通常说明瓶颈已经不在 CPU 计算上而在内存带宽和锁竞争上最直接的办法是减少线程数。还有一个更直观的排查技巧用top命令观察 Viviado 占用的 CPU 利用率。正常情况下4 线程的 CPU 利用率大概在 300% 到 400% 之间8 线程应该在 700% 以上。如果明明设了 8 线程但利用率只有 200% 出头那大概率是设计规模不够大、任务切分上不去并非线程数没生效这时候再怎么调也压不出时间了。6.3 多线程引起的结果质量波动多线程对布局布线结果的影响很多文章不会提但确实存在。因为并行优化会改变算法处理节点的顺序导致搜索空间的探索路径和串行时不一样最终找出的布局布线方案也就不同。这种差异在时序上表现为 WNS最差负裕量会有几个皮秒到几十个皮秒的波动。如果你在做一个需要反复迭代的工程不要频繁地切换线程数。固定一个线程数跑到底这样你能把变量控制在单一维度上不然时序变差了都不知道是 RTL 改动导致的还是线程数变化带来的。另外就是在时序收敛阶段如果布局布线结果总是差一点点可以试试把线程数从 8 降到 4 重跑一遍。有些时候线程数变少算法采用更保守的串行优化顺序反而能找到更好的解。我遇到过两三次这样的情况8 线程跑出来 WNS 是负的降到 4 线程跑就收敛了。虽然出现概率不算高但作为排查手段非常值得一试。还有一个和“生成比特流失败”相关的坑布线资源冲突有时也和多线程并行布线有关。如果布线时报出非常奇怪的资源冲突错误比如两个 CELL 挤在同一个 SLICE 里先别急着怀疑 RTL把线程数降一降重跑一次布线排错成本远低于重新检查代码。最后再分享一点个人经验我目前比较稳定的习惯是日常功能迭代阶段机器是 Intel Xeon 8 核 32GB 内存综合设 4 线程加-jobs 4布局布线设 8 线程加Runtime指令仿真编译用-mt 4 -O3。实测综合时间从 21 分半降到 13 分钟布局布线从 47 分钟降到 33 分钟xelab 编译时间从 5 分钟降到 2 分半左右总编译时间省下约三分之一。时序收敛进入冲刺阶段时我会把布局布线线程数降回 4directive 升级到Explore质量优先不再贪时间。这个思路不一定适合所有人的工程但大方向是不会变的。最后一个小技巧跑长时间编译前先在 Linux 下free -g看一眼可用内存Windows 下打开资源监视器看一眼可用内存再根据这个数去定线程数这一步真的能帮你避开很多莫名其妙的失败。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。