尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Vivado增量实现实战:复用布局布线,加速FPGA时序收敛

Vivado增量实现实战:复用布局布线,加速FPGA时序收敛 做 FPGA 的人对下面这个场景应该都不陌生一个 30 万 LUT 的工程综合加实现一次要跑八个多小时结果你只改了两行状态机代码或者只是把某个计数器的位宽从 16 调到 17 位整条流程又得从头再来一遍。等一晚上早上打开电脑一看时序还差 0.04ns改一下再等一天。这种迭代节奏在大规模设计里基本上是不可接受的而 vivado 的增量实现Incremental Implementation就是专门用来打破这个死循环的。它的核心思路很朴素让工具记住上一次已经布局布线好的结果这次只重做真正改了的那部分没动的地方直接复用原来的物理信息。用得好八小时的流程能压到一个半小时而且时序结果往往比全跑还稳因为大部分走线压根没被扰动过。这篇文章我会把增量实现的机制、参数配置、实操步骤和踩坑经验完整讲一遍适合已经能跑通基本实现流程、正在被长编译周期折磨的中高级使用者也适合刚接手大工程、还没搞清楚复用率是怎么回事的同行。1. 先把时间账算清楚增量实现到底省下的是什么1.1 一次完整实现时间都花在哪了要理解增量实现的价值得先知道实现阶段的耗时结构。Vivado 的 implementation 大致分四步opt_design逻辑优化、place_design布局、phys_opt_design物理优化、route_design布线最后是 write_bitstream。对中等规模以上的设计opt 通常只占 5% 到 10%布局占 15% 到 25%布线往往是大头能吃掉 40% 到 60% 的总时间phys_opt 视策略而定可能再来 10% 到 20%。关键在于布局和布线都是全局性的问题求解过程。布局器要在整个芯片上给几十万个单元找位置同时满足时钟、区域、拥塞等约束布线器要在有限的金属资源里给几百万条网络找路径。这两个过程的算法复杂度跟设计规模高度非线性相关逻辑量翻一倍布线时间可能翻三倍。当你只改了 2% 的逻辑理论上 98% 的解都是现成的但传统全流程会把它们全部丢掉重算一遍这就是纯粹的浪费。增量实现做的事情就是把上一次的布局布线结果作为参考解喂给工具让它在保留绝大部分解的前提下只对发生变化的那部分网表重新求解。所以省下的时间不是线性的一点点而是把布线这个最大的时间消耗项整体砍掉大部分。1.2 增量实现的两条前提不过它不是什么魔法得满足两个硬前提才有效果。第一条是你必须有一份已经完成布局布线的参考检查点。这个检查点通常就是上一次 write_bitstream 之前的那个 routed dcp 文件体积视设计规模从几十 MB 到一两 GB 不等。没有它增量实现无从谈起——这是很多人第一次尝试时最容易忽略的地方直接勾了增量选项却发现工具压根没复用因为没有指定参考文件。第二条是本次改动必须相对局部。改动越小复用率越高收益越大。行业里比较通行的经验阈值是改动逻辑占总量 5% 以内增量收益非常明显5% 到 10% 之间要看改动的分布位置超过 10%收益会快速衰减甚至可能出现工具主动放弃复用、退回全跑的情况。这里的改动不只看 RTL 行数还要看影响的网表节点数——改一个顶层参数导致的位宽连锁变化影响面可能远大于你的直觉。1.3 哪些场景值得上哪些别硬凑我的经验是下面这几类场景用增量实现回报最高。一是时序收敛的最后一公里。设计已经基本收敛只差几条关键路径你在做的是插流水线、调综合属性、改约束、微调逻辑层次。这种场景每轮改动都很小但往往要迭代十几轮增量实现能把每轮从数小时压到几十分钟收敛周期直接缩短一半以上。二是调试阶段加 ILA 探针。这里要提醒一句插入或修改 ILA 会改变网表结构探针本身还会占用布线资源复用率会明显下降一般只能维持 60% 到 80% 不等的复用水平。但即便如此也比全跑快得多。所以调试期我通常会一次性把可能要看的信号探针都插上尽量避免反复增删。三是固件微调与寄存器改动。这类改动通常只影响地址译码和少量控制逻辑网表变化极小复用率能做到 95% 以上是增量实现最舒服的用法。而不适合硬上的场景也很清楚时钟结构大改换了 MMCM 配置、改了时钟域划分、顶层端口或管脚约束大幅调整、器件型号或速度等级变化、从别的工具版本迁移过来的检查点。这些情况下增量复用率会掉到很低工具还可能在复用的约束下做出比全跑更差的布局决策得不偿失。2. 拆开看机制Vivado 靠什么记住上一次的结果2.1 参考检查点里到底装了什么一份 routed dcpdesign checkpoint本质上是一个压缩后的设计数据库快照里面包含了网表结构、单元与网络的层次关系、物理约束、以及最关键的——每个 leaf cell 的绝对坐标和每条网络的详细走线路径包含用了哪些布线资源、经过哪些 switch box。增量实现启动时工具会做一轮比对把当前新综合出来的网表跟参考 dcp 里的网表做结构对比逐层判断哪些层次hierarchy的实例和网络没有变化。没有变化的那些就被标记为可复用它们的布局坐标和布线资源直接搬过来。变化的那些会被打散成待重新求解的集合交给布局器和布线器处理。这里有个细节值得说工具对比的颗粒度最终会落到 leaf cell 和 net 上但判断的起点是层次结构。所以一个模块如果内部改了整个模块的单元都会被视为脏的而如果一个模块没改但它的父层次改了端口连接这个模块同样会被视为脏的。这就是为什么有时候你觉得自己只改了一个小地方复用率却掉得厉害——连锁反应比想象中大。2.2 复用的颗粒度为什么改一个模块不等于只重做那个模块复用分几个层次理解它们对判断收益很关键。逻辑复用Instance/Net Reuse指网表层面有多少实例和网络跟参考设计一致。这个数字主要由综合结果决定。如果你开了增量综合这个数字会很高如果每次综合都从头跑即使 RTL 没改综合出来的命名和结构也可能有细微差异导致逻辑复用率打折。布局复用Placement Reuse指有多少单元沿用了参考 dcp 里的物理坐标。这个数字通常低于逻辑复用率因为新增或变化的逻辑会被塞进现有布局的缝隙里可能挤动周边单元。布线复用Routing Reuse指有多少网络的走线路径原封不动保留。这是最脆弱的一层。一条网络只要有一个端点单元被挪动整条网络就得重布。所以布线复用率一般是最低的那个数字也是决定最终耗时的主要因素。我在一个 20 万 LUT 的工程上实测过一组数据可以参考改动 3% 逻辑时逻辑复用约 97%布局复用约 92%布线复用约 88%整体实现时间从 6.5 小时降到 1.2 小时改动扩大到 12% 逻辑后布线复用掉到 61%实现时间只降到 4.8 小时性价比就很一般了。2.3 增量综合和增量实现是两套独立开关很多人会把这两个概念混在一起其实它们作用在不同阶段需要分别开启。**增量综合Incremental Synthesis**针对 synth_design 阶段需要一份参考的 post-synthesis dcp。它复用未改动模块的综合结果能省掉大部分综合时间。对综合本身就要跑一两个小时的超大工程这个收益很可观。**增量实现Incremental Implementation**针对 place 和 route 阶段需要一份参考的 routed dcp。它复用物理实现结果省的是布局布线时间。理论上可以只开其中一个也可以两个都开。实践中我倾向于两个都开——综合和实现都复用端到端时间最短。但要注意增量综合会引入一个约束如果改动的模块跟未改动模块之间的接口发生变化复用会失败工具会退回全量综合。所以我会在改动涉及模块端口的时候提前把相关层次的边界检查一遍。3. 实操全流程从拿到参考 dcp 到跑出增量结果3.1 第一步准备一份干净的参考实现参考检查点的质量直接决定增量的效果这一步千万别省事。我的做法是先把当前版本跑一次完整的实现流程直到时序收敛确认没有未约束路径、没有严重的时序违例、DRC 干净然后把这个版本的 routed dcp 单独归档出来放在工程目录之外的固定位置比如./ref_checkpoints/2024xx_v1_routed.dcp并写一个简单的文本文件记录它的来源版本、工具版本、器件型号、时序余量WNS/TNS/WHS/THS等元信息。为什么强调干净因为如果你拿一份本身就有时序违例、布线拥塞或者大量未约束路径的 dcp 作为参考增量实现会把这些缺陷一起继承下来后续修改很难摆脱。参考解的质量就是增量结果的下限。还有一点参考 dcp 使用的 Vivado 版本最好跟当前工程的版本一致。跨大版本使用虽然工具会尝试兼容但网表结构、器件模型、算法参数都可能变化复用率会明显下降甚至出现奇怪的实现错误。我在 2020.x 和 2022.x 之间跨版本试过一次复用率直接掉了 30 个百分点后来就再也不这么干了。注意routed dcp 只有在 write_bitstream 之前或之后才会有如果你在 route_design 完成后立刻中止了流程需要确认 dcp 确实写出来了。另外dcp 文件不要提交到 git 这类版本控制系统动辄几百 MB会把仓库撑爆用独立存储或者制品库管理更合适。3.2 第二步设置增量参数GUI 与 Tcl 两条路GUI 方式在 Flow Navigator 里选中 impl_1右键打开 Implementation Settings找到 Incremental Implementation 相关选项。你会看到两个关键设置一个是 Read Incremental Checkpoint用来勾选并指定参考 dcp 的路径另一个是增量模式的下拉选项一般有 off、auto、on 三档。auto 交给工具判断on 强制尝试复用off 关闭。Tcl 方式我个人更推荐因为可以写进脚本、方便复现# 指定参考检查点 set_property incremental_checkpoint {./ref_checkpoints/v1_routed.dcp} [get_runs impl_1] # 打开增量实现 set_property incremental_implementation on [get_runs impl_1] # 确认设置是否生效 report_property [get_runs impl_1]最后那行report_property是我强烈建议加的。不同版本 Vivado 的属性名偶尔会有差异与其凭记忆硬写不如让工具把所有属性打印出来搜一下 incremental 关键字确认属性名和写入的值都对得上。这个习惯帮我省过好几次排查时间——明明脚本跑完了结果发现属性名写错工具默默忽略了白白跑了一次全量。另外提一句auto和on的区别。auto 模式下工具会先评估复用是否有意义如果判断复用率会很低它会主动放弃并全量跑然后在 log 里留下说明。on 模式下则强制走增量路径即使复用率很低也照样跑。我一般先用 auto 试一轮看报告里的复用率如果稳定在 70% 以上后续迭代就改成 on 固定下来。3.3 第三步改设计、重跑综合、启动增量实现完整的迭代流程是这样的按顺序走修改 RTL 或约束保存。复位综合并重新跑reset_run synth_1然后launch_runs synth_1 -jobs 8。如果开了增量综合这一步会复用大部分综合结果通常十几分钟就结束。等综合完成确认没有新增的严重告警。启动实现launch_runs impl_1 -to_step write_bitstream -jobs 8。等待完成检查报告。有个容易踩的坑复位综合之后impl_1 必须先复位才能重新启动。如果你只 reset 了 synth_1impl_1 可能还停留在完成状态直接 launch 会报错或者说没有需要跑的东西。正确做法是reset_run impl_1之后再 launch或者干脆用launch_runs impl_1 -to_step write_bitstream让工具自己判断依赖关系跑之前先用get_property STATUS [get_runs impl_1]看一眼状态。还有一点关于 directive 的选择。增量实现的迭代过程中我通常会把布局布线的 directive 调成偏快速的档位比如 Quick 或 RuntimeOptimized因为这时候目标不是榨出最后一纳秒时序而是快速验证这一轮修改是否有效。等到逻辑稳定、要做最终签核版本时再切回默认或 Explore 档跑一次完整流程确认。这个快跑验证 慢跑签核的两段式节奏是我在大工程里效率最高的用法。3.4 第四步读复用率报告判断这次增量值不值实现跑完后Vivado 会生成一份增量实现报告Incremental Implementation Report在 GUI 的 Reports 菜单里能找到对应入口打开同时 impl_1 的 runme.log 里也记录了关键统计。报告里最该关注的几个数字我整理成表格指标含义健康区间参考偏低时的常见原因Instance Reuse网表实例的复用比例85% 以上综合结果结构变化、开了全量综合但 RTL 改动连锁Net Reuse网络的复用比例80% 以上端口连接变化、逻辑优化把网络合并或拆分Placement Reuse布局坐标的复用比例75% 以上新增逻辑挤压、pblock 约束变化、时钟资源调整Routing Reuse走线路径的复用比例65% 以上大量网络端点偏移、布线拥塞、跨 SLR 网络变动Runtime 对比相对全量的耗时比30% 以下复用率不够固定开销占比过高我自己的判断标准是如果 Routing Reuse 低于 60%那这次增量的性价比就很可疑了我会去看具体是哪些层次被判定为脏的通常能找到意料之外的连锁改动。如果连续几轮复用率都持续走低说明改动已经积累了太多是时候跑一次全量把基线刷新一下。提示增量实现不是跑一次就能一直用的。建议每迭代 5 到 10 轮或者每次改动累积到一定量之后跑一次全量实现用新结果替换参考 dcp。长期不刷新基线复用率会慢慢漂移最后变成名义上增量、实际上全跑。4. 复用率上不去典型症状与排查实录4.1 症状速查表下面这张表是我这两年攒下来的基本覆盖了增量实现里九成以上的异常情况。症状可能原因排查动作log 里出现放弃增量的提示工具判断复用无收益或参考 dcp 不兼容检查工具版本、器件型号是否一致看复用评估的具体数字复用率突然从 90% 掉到 40%顶层接口或时钟结构被改动用网表对比工具看层次差异重点查时钟和顶层端口综合时间没变短增量综合未启用或接口不匹配导致退回全量确认综合设置里的增量选项检查模块边界接口实现时间比全量还长复用的硬约束拖累了布局器求解关闭增量改跑全量对照确认是否存在拥塞时序比全量差一截新逻辑被塞进已有布局缝隙走线绕远检查关键路径的布线延迟考虑局部 pblock 或 phys_opt报告里有新增 DRC 违例复用布线产生新的拥塞点查看违例类型和位置评估是否需要局部重布4.2 时序反而变差了怎么办这是最常见的抱怨明明增量省了时间结果 WNS 比上一轮还差。原因通常是新逻辑被强行塞进现有布局的缝隙里导致几条关键路径的走线比全量布局时长了不少。我的处理顺序是这样的。第一先看是哪几条路径退化了。打开时序报告对比增量前后的关键路径重点看net delay而不是 logic delay。如果是布线延迟暴涨基本可以确认是物理位置问题。第二如果退化的路径集中在一个小区域可以考虑给这块逻辑加一个温和的 pblock给它圈出足够的空间避免被挤到边角。pblock 不要圈得太紧留出 20% 到 30% 的余量太紧反而会造成内部拥塞。第三如果退化是全局性的、分散在多条路径上那说明这次改动的实际影响面比预估的大硬做增量意义不大。这时候我会干脆跑一次全量把基线刷新掉。第四也可以试试在增量实现之后追加一轮 phys_opt_design让它做后布局的时序优化。这一步不会推翻复用结果只是在现有布局基础上做局部微调代价小、见效快我在处理最后那零点几纳秒的违例时经常用。4.3 资源占用和布线拥塞的漂移增量实现还有一个容易被忽略的副作用资源占用会缓慢漂移。因为每轮新增的逻辑都被塞进现有布局工具为了塞得下可能会把一些 LUT 合并、把一些寄存器挪到不那么理想的位置几轮下来LUT 利用率可能比全量高出好几个百分点布线拥塞度也会上升。对这种情况我的建议是关注两个数字整体 LUT/FF 利用率和各 SLR 或各时钟区域的拥塞指标。如果利用率在几轮迭代后涨了超过 3 个百分点或者某个区域的拥塞度明显抬头就说明该刷新基线了。带着膨胀的资源占用继续做增量后面很容易撞墙——要么布线失败要么时序怎么调都收敛不了。另外跨 SLR 的网络要特别留意。在堆叠硅片SSI器件上跨 SLR 的走线资源本来就紧张增量复用如果把这些网络端点的位置挪了重布的时候很可能找不到跟原来一样好的路径延迟会明显变差。所以对 SSI 器件我倾向于在跨 SLR 的关键路径上加适当的布局约束减少增量过程中的位置漂移。5. 放进真实项目里我的几条落地经验5.1 参考检查点怎么管前面提过不要把它提交到 git这里再说说具体怎么管。我的做法是在文件服务器或者制品库上建一个专门的目录按工程名 / 日期 / 版本号三层组织每个检查点旁边放一个同名的 .info 文本文件记录以下内容生成日期、Vivado 版本、器件与速度等级、这次实现用的是哪个 directive、最终 WNS/TNS/WHS/THS、这次对应的 RTL tag 或 commit hash、以及一句话说明这个版本相对上一版改了什么。这些元信息看着琐碎但在需要回溯的时候价值极高。有一次我们发现某一版增量结果异常靠这个记录快速定位到那次用的参考 dcp 是从一个 directive 差异很大的实现里来的换了检查点之后问题就消失了。没有记录的话这种问题得排查半天。保留策略上我通常只保留最近 3 到 5 个版本的 routed dcp更老的清理掉否则存储成本会失控。但那个 .info 文本文件我会长期保留做归档。5.2 和团队协作、CI 流程怎么配合增量实现放到多人协作或者自动化流程里有几个特殊注意事项。第一检查点要跟代码版本绑定。不要让每个人各用各的参考 dcp而是约定一个团队共享的基线检查点跟某个明确的代码 tag 对应。谁需要迭代都从这个基线出发。这样大家的时序结果才有可比性也避免有人拿了个不匹配的检查点导致复用率异常。第二CI 上的实现任务默认应该跑全量。增量适合人做局部迭代不适合做入库验证因为增量结果的复用率跟当次改动强相关不确定性太大。我通常会在 CI 里跑两套一套全量作为签核基准一套增量作为快速反馈只有全量通过才算真正通过。第三自动化脚本里要加复用率的检查。跑完增量后脚本从 log 里解析出 Routing Reuse低于阈值就在日志里打个醒目的告警提醒人去复查。这个小检查能拦住不少看起来跑完了但结果不可信的情况。第四如果团队里有人用的工具版本跟基线不一致增量复用往往会失败甚至产出不可预期的结果。所以在工程配置里我会明确写死推荐的工具版本并在脚本启动时校验版本不符直接报错退出不给人踩坑的机会。5.3 常用配置与命令速查把我日常最常用的几条整理在这里方便直接抄。# ---------- 生成参考检查点 ---------- # 全量跑完实现后routed dcp 在 impl_1 的 run 目录下 # 复制并归档到固定位置 file copy -force [get_property DIRECTORY [get_runs impl_1]]/impl_1_routed.dcp \ ./ref_checkpoints/v3_routed.dcp # ---------- 配置增量实现 ---------- set_property incremental_checkpoint {./ref_checkpoints/v3_routed.dcp} [get_runs impl_1] set_property incremental_implementation on [get_runs impl_1] # 确认属性写入成功 report_property [get_runs impl_1] # ---------- 迭代流程 ---------- reset_run synth_1 launch_runs synth_1 -jobs 8 wait_on_run synth_1 reset_run impl_1 launch_runs impl_1 -to_step write_bitstream -jobs 8 wait_on_run impl_1 # ---------- 结果检查 ---------- # 从 runme.log 里抓复用率相关行 set logfile [get_property DIRECTORY [get_runs impl_1]]/runme.log关于incremental_implementation的取值我一般这样用第一轮验证用 auto看看工具自己的判断确认复用稳定在 70% 以上后改成 on固定路径只有在做对照实验、需要强制走增量的时候才用 on 配合手动指定检查点。off 基本只在排查是不是增量导致的问题时会临时切一下。最后分享一个我觉得挺有用的小习惯每次启动增量之前先把本次改动的 RTL diff 大致过一遍心里估一个影响层次清单跑完之后拿这个清单跟报告里被判定为脏的层次对一下。绝大多数时候能对上偶尔对不上——那多半就是发现了意料之外的连锁改动早点发现总比等到时序崩了再回头找强。这个动作花不了两分钟但帮我抓到过好几次隐蔽的顶层参数污染问题。
返回列表