做数字后端的人,没有谁不碰ECO的。尤其是项目进行到中后期,SDC更新、功能补丁、或者PR做完后端整合后timing报告里突然冒出一串max transition/cap违例——这个时候你不可能把整个flow重新跑一遍,唯一现实的选择就是做一次精准的ECO。Innovus里setEcoMode加ecoAddRepeater这套组合,就是我处理这类问题的主力工具,它的作用是在不动全局的前提下,批量往目标net上插buffer或换buffer,把违例点一个个按下去。
这套方法我用了好几年,从Innovus 15版本一路用到21版本,踩过的坑不算少。今天就把最关键的操作方法拆成5个步骤,连同参数解释、脚本模板、排错思路一起放出来。适合正在做Innovus后端集成、在收敛阶段反复改ECO、或者刚接手别人项目需要快速修时序/SI问题的人参考。
我不想只贴几条命令就完事。命令本身查help就能看到,但为什么这么设、不这么设会出什么问题、批量操作时哪些细节会让你半夜回来加班——这些才是真正值得记下来的东西。
1. 先搞清楚:setEcoMode和ecoAddRepeater在ECO里的定位
1.1 为什么批量插buffer这种事会找上你
时序收敛的中后期,最常见的需要插buffer的场景基本就三类。
第一类是max transition/cap违例。长net、大fanout net、绕线绕太远的net,信号沿变缓,驱动cell的负载远超规格,必须插repeater把长net分段。第二类是SI/crosstalk修复。相邻net耦合电容太大,出现glitch和delay push-out,需要在受害net上增强驱动。第三类是antenna问题,金属层制造过程中的antenna面积超限,ECO阶段插buffer或二极管来解决。
这三类问题通常不是单条net出现,而是十几条甚至几十条net一起报出来。一条条手动插buffer不仅效率低,而且容易看漏。批量处理不是可选项,是基本操作。
另外还有一种情况是真正意义上的“替换”:ECO时发现综合出来的某个buffer驱动强度太大布不开,或者VT选得太激进漏电不达标,需要整批换成同尺寸且不同驱动的单元。这类操作同样可以在ECO流程里通过命令组合完成。
1.2 setEcoMode管什么,ecoAddRepeater管什么
一句话概括:setEcoMode是给工具设定“我只想动这部分、其他别碰”的边界条件,ecoAddRepeater则是真正动手插入buffer的执行命令。
很多人以为setEcoMode是必须要设的前置条件,不设也能跑ecoAddRepeater。是的,能跑,但后果可能不是你想要的。ECO的核心诉求是“局部改动、最小影响”,如果不先把工具的行为约束住,后面ecoRoute等操作会按照默认策略来,工具自由度很大,可能顺手把你旁边无关的cell也优化掉一圈。单条net时影响还小,批量处理时改动扩散就是灾难。
ecoAddRepeater则是那种非常纯粹的“动作型”命令:给它一个cell、一个net、一个位置,它就给你插一个repeater上去。它本身不做任何全局优化,行为可预期,适合批量操作。
1.3 为什么不是place_opt直接一把梭
有朋友会问:place_opt不是也有-incremental增量模式吗,为什么不用它来修?
place_opt -incremental虽然是增量模式,但它依然会做全局范围内的时序优化、面积优化和单元调整。在ECO场景里,你只想修那几条net,不想让工具把scan chain reorder、不想让工具自动换cell大小、更不想让工具把clock buffer也顺手改了。工具自由度越大,改动范围就越不可控,验证成本就越高。
setEcoMode加ecoAddRepeater的思路是“外科手术”:锁定边界,精准操作。这才是ECO该有的样子。
2. 动手前的准备:目标net定位与buffer选型
2.1 从报告里圈定待处理对象
动手之前,先要把待处理的net清单整理干净。常见的信息来源有几个:
report_timing -max_transition或report_constraint -max_transition,找到违反max transition的路径和对应netreport_net -weak或者GUI里按属性筛选,找出驱动偏弱的net- 如果是PT或Tempus出的ECO建议,通常会自动生成包含ecoAddRepeater指令的TCL文件
拿到清单后不要急着跑,先做一轮去重和分类。同一根net上已经插过buffer的,先确认现在是否还违例;相邻位置有多根net要一起处理的,可以考虑合并处理,避免连环影响;还要检查这些net是否跨了hierarchical boundary,跨了的话要额外注意physical hierarchy归属。
这个整理过程看起来琐碎,但对批量ECO来说价值很大。一次有效的ECO,应该是在动手前就对要动的每一条net都有了明确判断,而不是跑一条看一条。
2.2 buffer cell选型:驱动强度、VT与物理尺寸
选buffer不是随便挑一个库里的cell就行,选错类型在批量场景里会放大风险。
修复transition问题,优先选驱动强度在2到4倍之间的buffer,别一上来就选X64这种大驱动。过强的驱动在SI场景里往往适得其反,驱动越强,翻转电流越大,对相邻net的耦合噪声影响也越大。
VT选择上,ECO阶段优先用标准VT。低VT虽然速度快,但漏电大,在ECO这种小改动场景里不值得引入额外的功耗风险。
物理尺寸同样关键。选的cell不能太大,否则放不进原row site的缝隙里,ecoroute时会出现placement非法。另外,如果选inverter做repeater,要注意极性,inverter必须成对插才能保持逻辑极性不变,出错的概率比buffer高不少,非必要不选inverter,直接上buffer最稳。
2.3 插入位置怎么定:坐标、row site与blockage检查
位置选择是批量插buffer时最容易翻车的环节。
我的建议是,能用-onNet就用-onNet,让工具根据net拓扑自动找位置,省心且不容易出错。如果确实需要手动指定坐标,那就要认真检查:这个坐标是否落在hard macro内部、是否被blockage覆盖、是否在region约束之外。
还有一个常见问题:坐标单位搞错。Innovus里指定坐标时用的是微米(micron),而很多从报告里直接拖出来的数据是数据库单位(database unit),两者差了至少一个数量级。我见过有人把数据库单位直接填进-loc,结果buffer插到十万八千里外的场景,十分离谱。
如果条件允许,位置最好选在source pin和sink pin的等分点附近,这样走线才顺,buffer的分段效果也最好。
2.4 设置ECO模式前的环境确认
在真正执行ECO之前,有几项确认花不了几分钟,但能避免大麻烦。
第一,数据库先存档。saveDesign或者至少导出一份-def,ECO做完后如果发现不对还能退回去。第二,确认当前没有未跑完的route任务。第三,确认所有要用的库文件、LEF文件已经加载完整,不然中途发现缺cell很尴尬。第四,确认设计里有没有影响放置的soft macro、region、bump对象。
这一步属于“磨刀不误砍柴工”,批量操作前花五分钟做检查,比操作完花一小时debug划算得多。
3. 实测:用setEcoMode+ecoAddRepeater批量替换buffer的5个关键步骤
3.1 步骤1:setEcoMode把工具关进“ECO笼子”
进入ECO模式前,先保存当前数据库,然后执行推荐配置:
# 先存盘,再进ECO模式 saveDesign -def eco_before.def.gz # ECO模式设置 setEcoMode -ecoMode ECO setEcoMode -allowEcoDeleteStdCell false setEcoMode -allowEcoReroute true setEcoMode -refinePlace false逐条解释一下这些参数的含义:
-ecoMode ECO让工具进入ECO模式,告知后续操作都是局部改动,工具会据此调整自身的优化策略。
-allowEcoDeleteStdCell false是安全开关。默认情况下ECO模式里不让工具自动删标准单元,防止工具在优化过程中把不该动的cell删掉。如果做替换场景时确实需要先删旧buffer,建议手动用ecoDeleteRepeater或deleteInst定向处理,而不是放开这个限制让工具自己去判断。
-allowEcoReroute true允许工具对受影响区域重新绕线,配合后续的ecoRoute使用。这里要注意,它只是打开了许可,不等于立刻执行。
-refinePlace false很关键。如果你的目标是精准改动,就不希望工具做全局placement refine,否则邻近区域的cell位置会被调整,原本不想动的部分也被动了一遍。
如果你的Innovus版本支持-modifyOnly,可以进一步把工具的可动对象限制在指定net和instance上:
setEcoMode -modifyOnly {net inst}不同版本对这个选项的支持程度不一样,具体语法以当前版本的help setEcoMode为准。但思路是通用的:把工具的活动范围压缩到最小。
3.2 步骤2:整理待处理net清单并校验对象存在性
设置完ECO模式后,下一步是整理目标清单。我习惯用一个外部文件保存待处理net,这样不用改脚本主体,每次只要更新清单文件就行。
# 从文本文件读入待处理net set fp [open "eco_net.list" r] set net_list {} while {[gets $fp line] >= 0} { set line [string trim $line] if {$line eq "" || [string match "#*" $line]} { continue } lappend net_list $line } close $fp # 校验每个net在数据库中真实存在 foreach net $net_list { set netObj [dbGet -p top.nets.name $net -quiet] if {$netObj eq ""} { puts "WARN: net $net not found, skip" } else { puts "INFO: net $net found" } }这个清单校验的过程经常能提前拦截问题。比如由于之前的ECO已经改变过某些net的命名,或者net被优化合并了,实际数据库中根本找不到。如果不做校验直接跑,脚本会报一堆错,排查起来反而浪费时间。
3.3 步骤3:单条ecoAddRepeater命令的准确用法
先看两条最典型的用法:
# 方式一:指定坐标插入 ecoAddRepeater -cell BUFFD16BWP16H90 -net n_123 -loc {120.5 80.2} -prefix ECO_BUF_ # 方式二:让工具沿net自动找位置 ecoAddRepeater -cell BUFFD16BWP16H90 -net n_456 -onNet -prefix ECO_BUF_逐项拆解参数:
-cell指定要插入的库单元,必须是已经加载library中存在的repeater类型单元。我建议用dbGet提前确认这个cell名在当前库里真实存在,很多项目有多组库,同一个buffer在不同库里的命名后缀完全不同,写错名字报错还好,万一同名不同功能的cell被插入那就严重了。
-net指定目标网络名,对应步骤2里校验过的net列表。
-loc {x y}指定坐标,单位是微米。坐标不能落在blockage、macro或非法区域内。
-onNet与-loc二选一。-onNet让工具根据net当前拓扑自动决定插入位置。实测下来,在net不太拥塞的地方,-onNet的结果基本能满足要求,而且省去了查坐标的麻烦。
-prefix给新增instance名字加前缀。强烈建议加上,否则工具按默认规则命名,后面想定位自己插的buffer时非常痛苦。统一前缀还有个好处:后续验证时可以用dbGet -p top.insts.name ECO_BUF_*把所有新插入的cell一次性找出来。
另外注意,如果插的是inverter,必须成对插才能保持polarity。实际ECO中非必要不上inverter,直接上buffer避免极性混乱。
3.4 步骤4:TCL循环批量执行,工程化你的ECO命令
批量操作的核心不是把命令复制粘贴几千行,而是用一个可重入的脚本框架。我常用的模板长这样:
set fp_in [open "eco_plan.txt" r] set fp_done [open "eco_done.log" a+] # 读取已完成的net列表 set done_net {} if {[file exists "eco_done.log"]} { set fp_old [open "eco_done.log" r] while {[gets $fp_old line] >= 0} { set line [string trim $line] if {$line ne ""} { lappend done_net $line } } close $fp_old } while {[gets $fp_in line] >= 0} { set line [string trim $line] if {$line eq "" || [string match "#*" $line]} { continue } # 行格式: netName cellName locMode x y # locMode 为 onNet 时, 忽略 x y set fields [regexp -all -inline {\S+} $line] set net [lindex $fields 0] set cell [lindex $fields 1] set locMode [lindex $fields 2] # 断点续跑: 跳过已处理的net if {[lsearch -exact $done_net $net] >= 0} { puts "SKIP: $net already done" continue } if {$locMode eq "onNet"} { set status [catch {ecoAddRepeater -cell $cell -net $net -onNet -prefix ECO_BUF_} msg] } else { set x [lindex $fields 3] set y [lindex $fields 4] set status [catch {ecoAddRepeater -cell $cell -net $net -loc [list $x $y] -prefix ECO_BUF_} msg] } if {$status == 0} { puts "OK: $net" puts $fp_done "$net" flush $fp_done } else { puts "ERROR: $net -> $msg" } } close $fp_in close $fp_done这个脚本看起来简单,但几个设计点非常实用。
第一,用catch捕获每条命令的执行结果,不会因为单条命令出错就把整个脚本停掉。第二,每成功一条就写入done日志,脚本中途崩溃后重跑,会自动跳过已完成的部分。第三,日志里保留了每条net的结果,统一grep ERROR就能看到失败项。
实际执行时,我会分批跑,每批100到200条,跑完看一遍日志再继续。不要一条脚本从头跑到尾,中间出了系统性问题,后面几百条都是在错误基础上跑,白白浪费时间。
3.5 步骤5:ECO布线、合法性修复与验证落地
ecoAddRepeater只是插入了cell并更新了逻辑连接,物理连线还没走。此时直接去做DRC或者出GDS,一定会报一堆open net。所以接下来必须跑ECO布线:
# 对改动区域增量布线 ecoRoute # 如果只想针对部分net布线,也可以指定net列表 # setNanoRouteMode -routeWithEco true # ecoRoute -nets $processedNets跑完之后,做几项基本检查:
# DRC检查 verify_drc -limit 100 -report eco_drc.rpt # 连通性检查 verifyConnectivity -type all -report eco_conn.rpt # 时序更新 update_timing report_timing -max_transition -max_cap -nworst 100 report_timing -groups {reg2reg clk2reg} -setup -hold特别提醒:插入buffer后,原本的天线违例位置会变化。原因是buffer把长net分段后,每一段的金属面积变小了,某些原来违例的net可能好了,但buffer入口处的短net可能产生新的天线风险。所以ECO后要重新跑一轮antenna检查,不要因为插了buffer就想当然认为antenna问题都解决了。
3.6 5个步骤之外:最容易忽略的环节
批量ECO里,命令之外还有几个容易被忽略的环节,代价都不小。
第一,ECO完成后另存一个新数据库,不要覆盖原始库。这样如果后续发现ECO结果不理想,还能回到改动前的版本重新来。
第二,确认SDC是否有变化。ECO如果动了clock path或async path,hold修复的结果可能受影响,需要重新报一遍hold时序。
第三,检查don't touch属性。有些net专门设置了set_dont_touch,ecoAddRepeater对这些net的行为在不同版本里表现不一致,有的直接报错,有的静默跳过。在清单整理阶段就提前过滤掉这类net,免得跑完才发现少了十几条。
4. 常见问题排查与避坑实录
4.1 无法place到合法位置:坐标、region、blockage连环坑
最常见的报错是cannot find legal location或illegal placement。遇到这个提示,按顺序排查:
- 坐标是否落在了hard macro内部或blockage区域
- 坐标所在的row site是否已经被其他cell占用
- 所选cell尺寸是否大于当前row site的剩余空间
- 当前有没有region约束限制了这个区域的cell类型
前两个问题通过调整坐标就能解决,第三个问题需要换小尺寸的buffer,第四个问题要看ECO区域是否被其他约束锁住。
如果是-onNet也报无法放置,那多半是net周围太拥挤,没有可用的空白位置。这种情况我会考虑先对该区域做一次局部congestion分析,换到稍微远一点但更宽松的位置,宁可走线绕一点,也比插不下去强。
4.2 插完buffer反而时序更差是什么鬼
插完buffer后timing不降反升,通常有三个原因。
第一,buffer选型太强,驱动过大造成SI耦合增大,周边net的delay被拖累。第二,插入位置离source pin太近,没有起到分段作用,RC网络反而更复杂。第三,多条net的buffer恰好挤在同一片区域,形成局部拥塞,绕线绕得乱七八糟。
我在实操中最常遇到的是第二种。手动指定坐标时,下意识往前半段放,结果buffer和source之间只有一小段距离,分段效果不明显。现在我的做法是优先用-onNet让工具自动权衡,如果必须手动指定,就尽量放在整条net的中间位置,并且参考一下全局绕线图确认这个点周围不拥挤。
4.3 setEcoMode参数太宽松引起改动扩散
这个坑非常隐蔽。有次做批量ECO,插完buffer跑ecoRoute,结果发现旁边无关net的走线也全变了。后来查原因,是我当时把setEcoMode -refinePlace设成了true,工具在局部refine时把邻近区域都重新优化了一遍。
ECO的核心价值就在于“最小改动”,如果你让工具自由发挥,那和重新跑一遍增量PR有什么区别。我的经验是:
-refinePlace设成 false,除非你明确需要工具帮你整理局部摆放-allowEcoDeleteStdCell设成 false,替换buffer时手动删旧cell,而不是让工具自己判断- 如果版本支持,用
-modifyOnly把可动对象圈定到指定net和instance
4.4 批量脚本跑一半中断,如何断点续跑
批量脚本跑一半崩溃简直是家常便饭。可能是license掉了、机器负载太高、或者某条net触发了工具的异常。
我的应对方式是前面那个脚本模板里的断点续跑机制:每成功一条就写入done日志,重跑时自动跳过。这个机制帮我在很多个项目里省下了重跑时间,尤其是几百条net的大批量EC O,一次中断不续跑,来回折腾的浪费非常大。
另外建议,脚本里给每条net打印明确的OK/ERROR标记,跑完后统一grep ERROR排查,不要盯着全屏日志一点点翻。
4.5 版本差异:不同Innovus版本行为不一
我在从17版本切到19版本时,发现同一个ecoAddRepeater命令,-onNet的自动选点行为明显更聪明了,老版本里偏远的插入位置在新版本里基本不会出现。
比较关键的一个调整是:老版本里ecoAddRepeater不带-prefix时生成的instance名比较随机,后续定位非常费劲;新版本提供了更规范的命名方式。如果项目里要来回切版本,最好在脚本里都显式指定-prefix,同时用renameInst做必要时的统一改名。
还有setEcoMode的-ecoMode参数,不同版本对ECO和默认模式的定义有细微差别。最靠谱的做法是:换版本后先跑一遍help setEcoMode和help ecoAddRepeater,对照help确认关键选项的行为再批量执行。这不是小题大做,是吃过亏的人的肺腑之言。
5. 我的几点实战体会
做了这几年ECO,最大的体会就是:ECO的成功标准不是插入了多少个buffer,而是除了你动的地方,整个设计像什么都没发生过一样。
批量操作前把目标清单整理清楚,把setEcoMode的参数设对,用可重入的脚本框架执行,最后认认真真做一遍DRC、timing和antenna验证。这套流程看起来平淡,但正是这些平淡的工程细节,决定了ECO是高效收尾还是灾难现场。
另外一个很实用的习惯:每次ECO完成后,把这次清理出的net清单、buffer选型、遇到的问题和解决方式记到项目notes里。同样的net列表在下一次ECO时经常还会出现,有了历史记录,处理起来会快很多。
如果你也在为批量buffer替换头疼,希望这篇文章里的5个步骤和避坑经验能帮你少走点弯路。最后送大家一句话:ECO无小事,改动越少,睡得越安稳。