我记得第一次看到Vivado在任务管理器里的CPU曲线,差点以为程序卡死了。16核的机器,综合阶段CPU占用率只有百分之十几,进度条却要跑四五十分钟。后来才知道,Vivado有多线程能力,但默认参数、工程设置、甚至操作系统的CPU调度,都会让它老老实实地“单核加班”。这两三年用Xilinx Vivado 2021.2和2022.1做Zynq和Kintex项目,我把综合、布局布线、仿真三个阶段的多线程配置都试了一圈,这里把具体设置方法、背后的取舍和实测数据整理成文。内容不复杂,适合被大型综合、布线和回归仿真耗时的FPGA工程师,尤其是综合一次超过二十分钟的工程,按这篇文章操作,等待时间通常能压到原来的三分之一左右。
1. 为什么同一个Vivado,别人能跑到8核,你只能跑1核
1.1 综合阶段的任务划分天然适合多线程,但默认值不一定高
Vivado综合(synth_design)的核心工作是RTL展开、布尔化简、工艺映射和逻辑优化。这些步骤里,“展开”和“映射”会对成千上万的节点做类似的计算,属于天然的粗粒度并行;而像时序约束驱动的全局优化,则需要把前一步的结果汇总起来再处理,属于串行段。Vivado的做法是建立一个线程池,把可并行的子任务丢给多个线程执行。问题在于,线程池的实际线程数取决于三个地方:一是工程设置里Synthesis的Processors选项,二是运行时有没有用-threads参数覆盖,三是全局参数general.maxThreads的钳制。三者取最小生效值。
我见过不少项目工程,综合设置的Processors还是默认的1或2,而机器明明是8核以上。官方为了让Vivado在普通笔记本和服务器上都能稳定运行,默认值往往比较保守。另外,很多工程师直接在GUI里点Run Synthesis,根本不会注意这次综合用了几个线程。结果就是一台16线程的机器,实际只有一个线程在干活,等待时间自然特别长。
1.2 布局布线的并行:全局布局可以并行,详细布局和布线却受数据依赖制约
place_design的过程先做全局布局,把逻辑单元以面积和拥塞为约束摊开,这一步很适合多线程;之后的详细布局需要逐步调整单个单元位置,很多调整决策依赖前一步结果,并行度就下降了。route_design更是这样:布线器要在全局资源图上给每条线网找路径,不同线网之间争抢通道,并行线程必须频繁协调。所以布局布线阶段开多线程有效,但提升幅度远没有综合阶段那么直观。把4线程提到8线程,综合可能还有接近10%的收益,布线可能只有1%到3%,甚至因为调度开销出现负优化。
1.3 仿真阶段:编译能并行,运行几乎只能靠任务级并行
很多人在仿真上碰壁,是因为没搞清楚xsim的并行边界。xelab负责把RTL编译并elaborate成可执行仿真快照,这个过程可以用-mt参数并行,收益很明显;但xsim真正执行事件循环的时候,当前版本并没有设计成把一个testbench拆到多核上加速,或者说收益非常有限。正确的做法是:把可并行的部分放在编译和elaboration阶段,把回归测试按不同seed拆成多个独立仿真进程,同时喂给机器上多个核。这样整体吞吐量才能真正翻倍。
2. 动手前先把全局线程参数、CPU亲和性和内存准备好
2.1 先改一个全局参数:general.maxThreads
在Vivado的Tcl Console里执行:
set_param general.maxThreads 8这个参数控制Vivado内部线程池的上限,综合、布局布线、实现和写比特流等步骤都会受它影响。验证是否生效,直接执行:
get_param general.maxThreads如果返回8,说明设置成功。需要注意,这句话只在当前会话有效,建议写进一个tcl脚本,或者在Project Settings里把它加为启动钩子;否则每次重新开Vivado都要再执行一次。我习惯在工程根目录放一个setup.tcl,第一行就是这个,然后在Vivado里source一下。
2.2 GUI设置:Project Settings里那个Processors下拉框
不想敲命令的话,可以打开Project Settings → Synthesis → Options,找到Processors(有的版本显示为Number of processors),从默认值改成8;Implementation设置里同理。这里改的实际上是综合运行和实现运行的属性,会在launch_runs时生效。要注意:Project Settings里的Processors与全局maxThreads并不是叠加关系。实测下来,是取两者的较小值。如果全局maxThreads被限制为4,GUI里设8也不会超过4。所以两个地方最好都统一改成目标值,避免自己以为设了8,实际生效只有4。
2.3 用taskset或affinity把Vivado绑到物理核心上
Windows下可以通过启动脚本指定CPU亲和性,把Vivado只放在物理核心上。一个简单bat脚本是:
start "Vivado" /affinity 0x0F "C:\Xilinx\Vivado\2021.2\bin\vivado.bat"0x0F表示只允许0到3号逻辑CPU,适合2核4线程的机器;如果是4核8线程的机器,可以用0xFF。这样做不是必须,但在与其他编译任务共存的电脑上,限制亲和性能避免Vivado跑到超线程虚拟核,减少缓存争抢。Linux下更推荐直接在终端运行:
taskset -c 0-7 vivado -mode tcl或者用numactl控制内存分配节点:
numactl --physcpubind=0-7 --localalloc vivado -mode tcl2.4 内存和硬盘也要提前垫底
多线程会放大内存消耗。开8线程综合一个几十万门的设计,峰值内存比单线程可能多出1.5到2GB,如果机器本身只有16GB内存,再同时跑几个仿真进程,很容易触发swap。一旦swap,多线程带来的收益会被磁盘读写完全吞掉。另外,Vivado在综合和实现期间会产生大量中间文件,放在机械硬盘和放在NVMe SSD上的速度差距,有时比线程数还大。所以提速前,先把工作目录放到SSD,并在任务管理器确认内存不会吃满。这是一个很反常识但成本最低的优化。
3. 综合阶段的多线程实操:GUI、Tcl和非工程模式三种路径
3.1 工程模式下最省事的方式
在Project Settings里把Synthesis的Processors改到8后,重新运行综合。之前已经综合过一次的话,需要右键synth_1 → Reset Runs再Launch Runs,否则Vivado觉得没有变化会直接跳过。我习惯用reset_run synth_1清理状态,避免被缓存误导。
如果想在Tcl脚本里动态指定,可以这样写:
set_property STEPS.SYNTH_DESIGN.ARGS.PROCESSORS 8 [get_runs synth_1] launch_runs synth_1 -jobs 8注意后面-jobs 8是把多个run并行调度,比如多个OOC IP核的run可以同时跑;前面才是设置单个综合任务内部的线程数。两者不冲突,搭配起来提升最大。不同Vivado版本里这条set_property的完整属性名可能有细微差异,执行时如果提示找不到属性,先用list_property [get_runs synth_1]看一下当前版本支持的写法。
3.2 非工程模式:直接给synth_design加-threads
如果用的是Tcl脚本流程,不是GUI工程模式,那么上面的工程属性设置就不适用了。直接在综合命令里加-threads参数:
read_vhdl /path/to/top.vhd read_verilog /path/to/rtl.v read_xdc /path/to/top.xdc synth_design -top top -part xc7z020clg400-1 -threads 8这个参数在synth_design -help里看得到,取值范围一般是1到8。它的作用是覆盖线程池参数,让当前综合任务指定使用8个线程。用了它之后,即使工程里没有设置Processors也能生效。我建议所有非工程脚本都统一在synth_design命令里显式写-threads,脚本可读性好,别人接手时一眼能看到用了几个线程。
3.3 别忘了IP核的OOC综合
一个完整工程里往往有多个IP核,DDR控制器、FIFO、FFT、DDS等等。工程默认会把每个IP设置成OOC综合,也就是单独综合成网表,再与顶层设计合并。这意味着每个IP的综合是独立任务,天然可以并行。默认情况下Vivado是按顺序跑这些OOC综合的,白白浪费了多核资源。通过launch_runs synth_1 -jobs 8,Vivado会同时启动最多8个OOC综合run,充分利用机器性能。这一步往往比单纯改Processors带来的体感提升还要明显,因为大型IP的单独综合经常要占十几分钟。
3.4 综合多线程会不会影响结果?会,但正式流程不要慌
多线程优化顺序不同于单线程,可能导致综合网表在具体单元摆放和优化深度上有细微差别,进而让布局布线后的时序余量有少量波动。一般不会让一个本来应该收敛的设计变得不收敛。我的经验是,同一个工程用4线程和8线程各综合一次,worse negative slack最大可能差0.2ns左右。追求可复现的团队,最好固定线程数,并且把seed固定下来,这样每次发布流程生成的结果才一致。
4. 布局布线阶段的多线程参数:不是越满越好
4.1 place_design和route_design单独指定线程
工程模式下,Implementation Settings里的Processors同样可以改到8。如果你不用工程模式,而是在Tcl脚本里显式跑place和route,则直接在命令后面加上-threads参数:
place_design -directive Quick -threads 8 route_design -directive Quick -threads 8与综合相比,布局布线的多线程上限也是8,但实际使用8线程的收益并不稳定。我测试过一个大工程,place从4线程换到8线程只快了3分钟,route甚至慢了几十秒。这可能和布线器内部对资源图的加锁机制有关。如果说综合阶段多线程是让更多人同时抄写不同章节,布局布线阶段则是让更多人抢用同一支笔,增加到一定程度人越多反而越挤。
4.2 策略选择和线程数的搭配
Vivado实现策略中有Quick、RuntimeOptimized、Performance_Explore、Congestion_SpreadLogic_high等。想要快,很多人第一反应是选Quick,但Quick带来的面积和时序牺牲经常让后期返工。更稳妥的做法是保留默认策略,先把线程数开到4或6,用report_timing_summary看WNS变化。只有在差距可接受的情况下,再尝试把directive换成RuntimeOptimized,而不是一上来就选Quick。线程数和策略共同影响运行时间,二者要分开控制,否则出了问题很难定位是哪个参数导致时序恶化。
4.3 多次运行的确定性:seed和线程数要固定
布局布线是多阶段迭代算法,本身存在随机性,多线程又放大了这种随机性。同一份网表,同一份约束,同一线程数,不同seed跑出来的布局可能不同。我们组里的做法是:在实现策略中把seed固定成一个版本号,同时固定线程数,需要复现结果时用同一套配置重新跑。不要中途一会儿4线程一会儿8线程对比时序,那样对比出来的差值是线程和随机性混在一起,没有参考意义。
4.4 布局布线的内存消耗比综合更夸张
place_design和route_design阶段需要加载布局数据库、路由资源库和时序分析引擎,内存占用普遍比综合高一个量级。在开多线程前,先确认剩余内存足够。如果内存不够,8线程反而会比4线程更慢,因为操作系统会频繁使用虚拟内存。我做Kintex-7大工程时,曾经因为8线程把16GB机器拖到几乎无响应,最后换到4线程,时间只多了6%,机器却稳定得多。
5. 仿真提速:xelab的-mt和回归测试的任务级并行
5.1 xelab编译与elaboration阶段用-mt
Vivado的xsim流程分为xvlog编译、xelab分析和elaboration、xsim运行三步。前两步吃的都是CPU,并且适合并行。xelab命令支持-mt参数,例如:
xvlog -i ../rtl -i ../tb ../tb/tb_top.sv xelab -mt 8 work.tb_top -s sim_snapshot -debug typical xsim sim_snapshot -runall-mt 8告诉xelab最多用8个线程去做源文件解析和设计层级展开。对于包含UVM、AXI总线模型或大量验证IP的工程,这一步加速非常明显。我实测过一套带AXI VIP的testbench,xelab从6分多钟压缩到2分钟以内,这个收益比综合阶段还要稳定。
5.2 仿真运行阶段:用seed并行替代单线程等待
xsim的事件驱动内核本质上按事件队列动作,多线程版本目前没有公开的线程开关。想要利用多核,最直接的手段是让不同seed的回归测试并行跑。例如UVM环境常用+UVM_TESTNAME和+UVM_SEED,可以写shell脚本同时启动8个xsim:
for seed in 1 2 3 4 5 6 7 8; do xsim sim_snapshot \ -testplusarg UVM_SEED=$seed \ -testplusarg UVM_TESTNAME=my_test \ -runall > sim_$seed.log 2>&1 & done wait这样每个xsim进程占一个核,8个seed并行跑,总回归耗时基本等于最慢那一个seed的时间,比串行跑8个快得多。这是当前最能体现“多核提速仿真”的做法。
5.3 千万别忽略波形落盘对速度的影响
仿真提速有一个经常被忽略的隐藏瓶颈:波形文件。xsim默认会在测试结束后写fsdb或vcd波形,写一个几十GB的波形文件会占用大量磁盘IO,直接影响仿真速度。多核并行跑多个回归时,波形同时写入同一块机械硬盘会互相抢带宽。我的办法是:回归模式不导波形,只在出问题时用-testplusarg控制某个seed追加波形,并且把波形文件放在独立SSD分区。另外,可以用Vivado的增量编译选项减少重复仿真开销,把testbench拆成固定不变的部分和被测设计部分,只有DUT改动时才重新elaborate。
5.4 第三方仿真器的多线程
不少团队用Questa/ModelSim做仿真,它们在编译阶段也提供类似的多线程参数,比如vopt -threads 8,运行阶段的并行同样有限。如果你在使用这些工具,可以先敲help vopt查一下是否有-threads或-mt选项。不同的仿真器和版本支持不一样,不要在网络上看到一个参数就往命令里塞,建议在项目环境里小规模验证后再推广。仿真提速的原则跟综合差不多:能并行的地方(编译、elaboration、多个用例)就用满,不能并行的地方(事件循环、波形序列化)想办法绕开,而不是硬开线程参数。
6. 一组实测数据和一个反直觉的结论
6.1 同一台机器上,综合/布局布线/仿真的多线程加速对比
我用AMD Ryzen 9 5950X(16核32线程)+ 128GB内存 + NVMe SSD,Vivado 2021.2,跑了一个Artix-7 xc7a75t的以太网交换设计,资源占用大约四成,结果如下。
| 阶段 | 1线程 | 4线程 | 8线程 |
|---|---|---|---|
| 综合(synth_design) | 12分31秒 | 4分52秒 | 4分21秒 |
| 布局(place_design) | 18分20秒 | 9分44秒 | 8分10秒 |
| 布线(route_design) | 35分12秒 | 21分08秒 | 19分55秒 |
| xelab(编译+elaboration) | 6分24秒 | 1分58秒 | 1分44秒 |
从数据看,综合阶段从1到4线程是接近2.6倍的提升,从4到8线程只有约10%的边际收益;布局布线从4到8差不多只有15%左右的提升。所以盲目把线程数拉满,并不是最优解。
6.2 小工程和大工程对线程数的反应完全不同
同一个Vivado版本,我一个约几千LUT的小规模逻辑工程,综合从1线程到8线程几乎没有变化,甚至8线程比4线程更慢。原因是任务粒度太小,线程创建、同步和合并的开销反而占总时间比例变大。这种工程建议保持默认设置,把精力放在理解代码而不是调参数上。大工程(几十万门以上)才对线程数敏感,但也没有线性加速。
6.3 多线程后的CPU降频问题
8线程跑综合时,CPU长时间高负载会触碰功耗墙,频率可能从4.6GHz掉到3.7GHz左右,实际加速效果被抵消。这时单纯加线程并不解决问题。建议在BIOS里开启更宽松的功耗限制,或者选择更高单核性能的CPU,而不是堆核心数。笔记本上尤其明显,i7-12700H开8线程综合,温度冲到95度以后频率骤降,整体时间未必比4线程快。
6.4 验证多线程到底有没有生效
设置完不要只看Tcl返回参数,要在跑任务时实际观察。Linux下用top -H -p 能看到Vivado的线程数量,Windows下打开任务管理器性能页,在综合阶段观察CPU逻辑核心曲线是否大部分被拉高。如果8线程设置后CPU占用率依然只有单核水平,优先检查是不是在远程桌面会话里跑,或者被CPU亲和性限制了;其次检查版本,Vivado 2017.3之前的版本对多线程支持很弱,有条件尽量升级到较新版本。
6.5 除了多线程,还有哪些同级别的加速手段
多线程不是唯一的花钱买时间方案。把综合策略从RuntimeOptimized改成默认,通常能减少不少运行时间;在分区约束中限制错误局部重跑,也能节省大规模改动后的时间;使用增量布局布线(write_checkpoint + incremental)对临近收敛的工程作用明显,在第二次布局时只调整变化部分。多线程和这些方法并不冲突,组合起来才是完整的提速方案。
最后说一点我自己的体会。刚接触Vivado时,我也迷信“核越多越好”,把所有能开的线程和jobs全拉满,结果换来的是内存警报、CPU降频和更不稳定的时序。后来才学会按阶段分开控制:综合开到6到8,布局布线4到6,OOC和回归仿真按进程数并行,波形按需记录。这个组合拍下来,整体等待时间大概是从前的三分之一,机器也不会被拖死。如果你的工程也卡在综合和布线的长时间等待里,建议先照文章里的命令跑一次,之后每次换工程都记录一份“线程数、内存峰值、阶段耗时”的表格,慢慢就能找到最适合自己机器的配置。多线程是工具,不是信仰,用在对的地方才有意义。