ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Vivado多线程优化实战:综合、布局布线、仿真阶段的提速与避坑指南

Vivado多线程优化实战:综合、布局布线、仿真阶段的提速与避坑指南 很多用 Vivado 的人应该都有同感早上 9 点启动一个综合吃个午饭回来还在跑晚上下班前丢一个实现任务第二天早上起来不知道有没有跑完。尤其到了项目后期每一轮编译都是按小时甚至按天算的多核多线程的设置就成了实打实的“救命稻草”。不过实际用下来会发现Vivado 的多线程不是简单地把一个参数拉满就行——不同阶段对多核的利用方式差别很大设置不对或者线程数开过头反而会出奇怪的问题。这篇文章就围绕 Vivado 里的综合、布局布线、仿真三个阶段把我自己实测有效的多核多线程设置方法、参数位置、验证手段以及很多人会踩的坑一起整理出来。内容偏工程向适合天天跟 Vivado 打交道、被编译时间折磨的开发者。1. 先搞清楚 Vivado 哪些环节真正吃多核——别把时间浪费在错误的地方很多人在网上一搜“Vivado 多线程”看到的都是set_param general.maxThreads 8这一条命令然后复制粘贴发现有时候确实快了有时候却完全没变化。问题在于这条命令对综合、布局布线、仿真的影响程度完全不同。我先把这个底层逻辑讲透后面设置的时候才不会盲目。1.1 综合确定性优先能并行的其实不多综合阶段的目标是把 RTL 转成网表并完成逻辑优化。这个过程中RTL 解析、架构选择、逻辑综合、时序优化等步骤之间是存在强依赖关系的。尤其到了全局优化阶段后面的步骤必须等前面的结果出来这种串行依赖决定了综合不可能像布局布线那样大幅度并行。网上一直流传着“Vivado 综合默认单线程”的说法这其实不完全准确。Vivado 综合引擎内部有不少子任务已经支持多线程比如大扇出网络的优化、跨模块的常量传播等但这些并行子任务在整个综合流程里占比有限。我实测过同一个设计在 8 核机器上把线程数从默认改为 8综合时间能缩短 20%~35% 左右再快就很难了。如果想靠设置多线程让综合时间压缩一半以上基本不现实。有一种情况例外设计里有很多独立的子模块每个模块之间几乎没有跨模块逻辑。这时可以用后面会说的 OOC 模式或者多进程并行综合把整个综合任务拆成多个并行任务分别跑在多个核上这是真正能让综合阶段吃满多核的办法。不过这属于流程改造不是简单改参数。1.2 布局布线真正的多线程受益者布局布线阶段是整个 Vivado 流程中耗时最长的部分尤其布线经常能占到整个实现时间的 60%~80%。好消息是这部分也是多线程优化收益最明显的部分。布局阶段Vivado 会把器件上的可编程逻辑按区域划分多个布线资源区域可以交给不同线程并行处理线程之间通过同步机制协调边界区域。布线的全局布线、细节布线、时序分析迭代这三个核心环节都能较好地利用多核。实际项目中在 8 核机器上把线程数拉满布局布线总时间能缩短一半以上这是我在多个设计上验证过的数字。有意思的是布局布线的多线程优化效果和设计规模、拥挤度关系很大。规模越大、资源占用率越高的设计并行收益越明显小而简单的设计线程间的同步开销反而可能抵消掉并行收益。所以不要指望一个小设计在设置为 8 线程后能有质变。1.3 仿真编译能并行执行“天生单线程”仿真的是最容易产生误解的部分。很多人以为开了多线程仿真运行速度就能成倍提升这是不现实的。仿真执行是基于事件驱动的事件在时间轴上严格有序当前时刻的事件可能触发下一时刻的事件。这个本质决定了仿真循环很难做多线程并行这也是为什么几乎所有事件驱动仿真器ModelSim、Questa、VCS、xsim 都是如此在单个仿真实例的执行阶段都带不动多核。Vivado 仿真器 xsim 也不例外跑一个任务的时候,你把线程从 1 改成 8仿真执行时间几乎不会有变化。但仿真的编译阶段是可以多线程的。RTL 编译、设计单元分析、代码覆盖率数据预处理等环节可以并行这也是 xelab 工具提供了多线程编译选项的原因。换句话说加速仿真的正确思路是“让大任务变成多个小任务并行跑”而不是纠结于单个仿真实例的多线程。2. 综合与布局布线阶段的多线程设置——核心命令与放置位置既然布局布线是最大的受益者那设置的重点自然也在这一块。这里给出最常用、也最稳妥的设置方式。2.1 核心参数general.maxThreadsVivado 中用于控制综合和实现阶段线程数的关键参数是set_param general.maxThreads 8这个参数的作用范围覆盖综合、布局、布线、物理优化等流程。在大多数项目里只需要设置这一个参数就够了不需要针对每个设计阶段分别设置。这个参数的最大值通常可以设置为 8。需要注意的是不是线程数越高越好。我做过对比测试在 4 核物理机上设置 8 线程性能反而不如默认 4 线程在 8 核机器上设置 8 线程提升明显但设置 16 线程并不会带来额外收益反而因为线程调度和内存带宽竞争导致运行时间波动。所以经验是线程数设置为物理核心数以内一般取 8 即可。如果用的是 4 核 CPU设置 4 就够没必要硬拉。另外Vivado 不同版本对这个参数的行为略有差异。较老的 2017、2018 版本部分流程对该参数的支持不如新版本完整。如果升级 Vivado 后发现网络列表优化方式有变化先确认一下该参数在当前版本中的默认行为。2.2 在 GUI 和非交互式脚本里的设置位置GUI 模式下可以通过菜单Tools → Settings → General找到 Maximum number of threads 相关选项。如果找不到也可以在 Vivado Tcl Console 里直接执行set_param general.maxThreads 8作用是一样的。但更推荐的做法是在非交互式批处理脚本里设置这样可以保证每次跑流程的一致性。一个典型的流程脚本如下# run_flow.tcl set_param general.maxThreads 8 read_verilog top.v read_verilog module_a.v read_xdc top.xdc synth_design -top top -part xc7k325tffg900-2 opt_design place_design phys_opt_design route_design report_timing_summary -file timing_summary.rpt report_utilization -file utilization.rpt write_bitstream -force top.bit然后在命令行用如下方式启动vivado -mode batch -source run_flow.tcl -nolog -nojournal有人会把maxThreads放在synth_design或place_design之前执行这没问题。需要提醒的是这个参数要在运行综合和实现之前设置如果已经跑完综合再设置对当前流程后面的实现阶段依然有效但综合阶段已经回不去了。最保险的做法是放在脚本最前面或者放到 Vivado 启动时的初始化脚本里。2.3 如何验证多线程是否真的生效设置完成之后验证是否生效最直接的办法是看系统 CPU 占用率。在 Linux 下运行实现流程的同时开另一个终端执行htop如果看到多个核心的占用率同时飙升到较高水平说明多线程生效了。比如在 8 核心机器上布线阶段通常会看到 6~8 个核都在工作。如果只有一个核在跑那就要排查参数有没有被后续脚本重置或者 Vivado 版本对当前设计阶段不支持多线程。在 Windows 下也一样打开任务管理器在“性能”选项卡里观察 CPU 使用情况。要注意的是Windows 下的 CPU 占用率曲线可能不如 Linux 直观因为系统本身还有后台进程在占核最好让运行流程的进程独占性更强一些。另外place_design完成后可以看日志中是否有线程相关的统计信息。Vivado 某些版本的日志开头会打印线程数量或者在report_runtime里能看到各阶段耗时。如果日志里显示 place 和 route 的时间明显比单线程短那就说明设置生效了。3. 综合部分的进一步加速增量综合与 OOC 并行前面说了综合阶段对多线程的利用有限。但项目开发中综合的重复次数往往是最多的改一行 RTL 就重新综合一次累积起来的时间非常可观。想在这一块突破单纯靠多线程不够要配合流程手段。3.1 增量综合让改动部分重新综合Vivado 支持增量综合原理是基于上一轮综合结果做增量比较只对发生变化的 RTL 模块重新执行综合优化未变化的模块直接复用之前的网表结果。基础是综合过程会把中间结果保存在工程内的一个检查点文件中综合前做一次差异对比决定哪些部分可以复用。在工程模式下可以在 Settings 里勾选综合增量选项在批处理流程中综合命令带上-incremental开关即可synth_design -top top -part xc7k325tffg900-2 -incremental增量综合对多核不是直接关系但它确实能大幅缩短综合等待时间和后面的多线程布局布线搭配起来整个流程的编译效率能高不少。实测遇到的情况是大设计只改了一个内部模块的几行代码增量综合能把原来 40 分钟的综合时间压到 10 分钟以内。不过要注意增量综合对设计上下文一致性的要求比较严格如果改了约束文件、换了器件型号、改了顶层接口增量结果可能不可靠这时候要强制做一次全量综合。3.2 OOC 模式把模块拆开来并行综合OOCOut-of-Context模式是另一个非常实用的加速手段。它的思路是把某个子模块独立综合成模块级网表不嵌入顶层上下文中。因为模块之间没有交互多个 OOC 模块可以同时在多个 Vivado 进程中并行综合然后顶层综合时把这些模块当作黑盒引用。具体的做法是对每个要独立综合的模块执行synth_design -mode out_of_context -top module_a -part xc7k325tffg900-2并行跑多个这样的进程每个进程占一个或几个核。比如一个设计有 4 个大模块就可以开 4 个终端每个终端跑一个模块的 OOC 综合同时进行。等所有模块的 OOC 网表生成后再跑顶层综合把模块网表串起来。这样综合的整体吞吐量能得到明显提升。OOC 模式在大型设计中还有另一个好处它能隔离未完成模块对顶层的影响。某个模块还是实验版本另一个模块已经是稳定版本二者可以各自独立综合互不干扰。这一点在团队并行开发时特别有用。不过 OOC 模式也有代价顶层综合时这些模块是黑盒跨模块的逻辑优化基本做不了可能影响最终的时序结果。所以 OOC 模式更适合用于前期快速迭代验证或者模块边界清晰、跨模块逻辑很少的设计。如果模块之间有大量跨模块优化需求强行 OOC 可能反而让性能变差。3.3 综合线程数设置在 OOC 场景下的配合OOC 多进程并行时每个进程内部的线程数不要设置太高。比如一台 8 核机器如果同时跑 4 个 OOC 综合进程那每个进程设置 2 个线程就够了如果还设置 8 线程4 个进程加起来要 32 个线程不但跑不满还会因为核数不够导致大量线程空转整体时间反而变长。这是很多人在并行综合时最容易犯的错误——把多进程和多线程混为一谈以为两边都拉满才是最优解。4. xsim 仿真加速并行编译与回归任务的正确打开方式回到仿真这块。单实例仿真执行阶段多核确实救不了但这不代表仿真一点加速手段都没有。实战中我把仿真性能提升的重点放在两个地方一是编译阶段的并行化二是回归测试任务的并行调度。4.1 xelab 的多线程编译选项Vivado 仿真流程中对设计做分析、综合和链接的工具是 xelab。这个工具支持多线程编译可以通过-mt参数指定线程数。典型用法xelab -mt 8 work.top -O3 -debug typical其中包括 RTL 编译、单元库分析、代码展开等环节会启用多线程。实测中一个中等规模的仿真测试平台单线程编译可能要 10 分钟-mt 8编译能压到 3 分钟左右。线性度虽然不是完美的但观感上提升非常明显。注意这里必须以-mt加数字的方式指定线程数。有些版本也接受-mt on这种写法但为了兼容性直接给数字最稳妥。另外多线程编译对系统内存的要求会上升如果机器内存不大强行开 8 线程编译可能会触发内存交换反而更慢。4.2 增量编译改完代码后的秒级体验xelab 在编译时会生成中间缓存。如果只改了部分 RTL 文件重新运行 xelab 时它会对未修改的部分使用缓存只重新编译变化的部分这个机制和综合的增量编译类似。在大型仿真工程里这个功能很实用每一次仿真迭代等待时间都能明显减少。具体操作上建议在做仿真时不要在同一个目录里反复清理重来而是保持工程结构稳定。只要不手动删除 xsim.dir 等缓存目录增量编译就能正常工作。我见过有些同事习惯性在每次仿真前把仿真目录删掉重建等于把增量编译的优点白白浪费掉了。4.3 真正能利用多核的仿真正确姿势并行跑不同测试用例单个仿真实例的执行阶段不能并行但一个验证环境通常有成百上千条测试用例。把这些用例拆开放到多个 Vivado 仿真进程里并行执行这才是仿真多核利用的最大空间。常用的做法是准备一个回归脚本格式类似for case in test_a test_b test_c test_d; do xsim -R testbench_${case} -testplusarg TESTCASE${case} done wait用把这些仿真任务丢到后台并行跑配合 shell 的wait等待所有任务结束。如果是用 Makefile 管理回归可以直接用make -j8让多个目标并行执行。每个仿真进程是独立的占一个或几个核8 核机器并行跑 6~8 个回归任务完全没有问题。这种方式不仅利用多核还顺带解决了一个常被忽视的问题单个测试用例跑完需要几十分钟但如果 8 个用例并行跑总时间就是最慢那一个用例的时间而不是所有用例时间之和。回归周期从一天压到两三个小时在工程上是非常可观的收益。5. 从工程调度层面再做一层提速多实例并发与 CPU 拓扑优化走到这一步单工程的 Vivado 多线程你已经设置好了仿真并行也做了。但如果你手上同时有好几个设计或者好几个策略要跑还有一层加速空间值得挖掘。5.1 多个 Vivado 实例并行跑多策略Vivado 实现阶段允许通过不同的-directive参数选择不同的实现策略比如vivado -mode batch -source run_impl.tcl -tclargs Performance_Explore vivado -mode batch -source run_impl.tcl -tclargs AreaOptimized_high跑两个策略的 Vivado 进程并行运行最终选择时序最容易收敛的结果。对于时序收敛困难的工程这比“跑一轮,不行再换策略重跑”要快得多。这里要注意资源分配假如机器是 8 核跑两个 Vivado 实现实例每个实例内部设置 4 线程会比每个实例都设置 8 线程更合理。两个 8 线程进程同时抢 8 个核效果不如每个进程独立占 4 个核稳定。这一点和前面 OOC 并行综合的线程分配逻辑是一样的。5.2 双路服务器与 NUMA 拓扑的注意事项在双路 CPU 服务器上跑大型实现任务时需要注意 NUMA非统一内存访问结构的影响。这种服务器的内存分为多个节点每个 CPU 访问本地内存快、访问远端内存慢。Vivado 实现过程中需要频繁读写大量内存如果线程分散在两个 CPU 节点上内存访问延迟会上升性能提升会被抵消。最简单的处理方式是通过numactl把 Vivado 进程绑定到同一个 NUMA 节点上。比如numactl --cpunodebind0 --membind0 vivado -mode batch -source run_flow.tcl这样进程只在 CPU 节点 0 上运行内存也只从节点 0 分配避免跨节点访问的额外开销。如果一台机器要同时跑两个实现任务可以用numactl分别绑定到 node0 和 node1两个任务互不干扰都能拿到各自节点的完整内存带宽。这个优化在双路服务器上的收益比较明显单路机器上则无所谓。5.3 磁盘 IO 与内存带宽另一个容易被忽视的瓶颈多线程拉满后瓶颈常常会转移到磁盘和内存带宽上。Vivado 在实现过程中会频繁读写检查点文件、日志文件、缓存文件如果磁盘是机械硬盘多线程读写的随机 IO 会让整体速度大打折扣。建议在 SSD 上跑 Vivado 项目至少保证工程目录放在 SSD 上。我也遇到过网络磁盘映射到本地工程的情况多线程实现时网络 IO 成为瓶颈速度反而掉得厉害。内存方面Vivado 实现阶段的内存占用和线程数基本成正比。8 线程跑一个大型设计峰值内存轻松超过 16GB。如果机器内存只有 16GB开 8 线程可能会出现内存不足或频繁交换。开跑之前用free -h确认一下内存余量免得跑到一半被系统 OOM 杀掉。设置多线程这件事落到实际项目里我自己的体会是不要一开始就无脑把参数拉满先搞清楚哪个阶段最耗时然后针对性地优化。布局布线阶段拉高线程数收益最明显综合阶段优先考虑增量综合和 OOC 并行仿真则走并行回归和多线程编译的路子。等这些都做完了再去看磁盘、内存、NUMA 这些系统层面的因素。这套组合拳打下来一个原本在 8 核机器上要跑七八个小时的完整流程压到两三个小时是比较常见的结果。最后再分享一个小技巧批处理脚本里记得在synth_design之前把maxThreads设置好但如果有多个 Vivado 进程要并行跑一定要根据核数均分线程。我见过不少人在这一步吃了亏4 个任务各自开满 8 线程最后全部卡在 CPU 抢占上项目没快反而更慢了。
返回列表