Innovus ECO修复实战:ecoAddRepeater插buffer/inverter详解

发布时间:2026/10/4 8:07:30
Innovus ECO修复实战:ecoAddRepeater插buffer/inverter详解
干过后端实现的人谁还没遇到过“就差几个buffer”的局面。ECO阶段尤其是功能冻结之后时序差一点、DRC爆两根、antenna有漏网之鱼你不可能再回头跑一遍place_opt这个时候最顺手、也最不惹事的修法就是用ecoAddRepeater插buffer或者inverter。这篇东西我不讲教科书直接把Innovus里这条命令的适用场景、命令细节、脚本套路和踩坑记录一次说清楚希望能让正在跟ECO缠斗的朋友少走两步弯路。1. ecoAddRepeater适干什么活不适合干什么活1.1 什么场景下你会想到它ecoAddRepeater这个名字其实已经说明了它的核心动作在ECO流程里往指定的net上插一个buffer或inverter实例用来改善信号或者时序。日常项目里我碰到最多的是下面几种情况。在功能冻结或postmask阶段发现net的max transition/cap违例但跑增量place_opt又会把布局动得乱七八糟这时候ecoAddRepeater是首选。还有做hold time修复特别是一些postmask ECO的场景工具不允许大改布局只能局部插delay buffer来延长路径。再一个就是antenna违规的修复通过插buffer切断长的金属走线或者用带antenna diode的cell来泄放电荷。还有一些特殊场景比如给时钟树上的某条分支做phase delay微调或者为了隔离噪声、调试时强制分断一条长net也会用到它。如果一个design的时序违例量级已经大到成百上千条路径或者congestion已经严重到插不进cell那ecoAddRepeater也救不了你这种情况老老实实回去改floorplan、重新place反而比在ECO里硬掰更快。简单说ecoAddRepeater适合“局部微创手术”不适合“全身大换血”。1.2 和addRepeater、addBufferOnNet的区别要拎清Innovus里能插buffer的命令不只这一条新人经常搞混。我用一张表把区别写出来你们以后选命令时直接对号入座。命令所属流程是否受ECO约束典型用途注意事项addRepeaterpre-mask常规实现不受限可配合优化全局时序优化时自动插buffer会参与后续优化可能被工具挪走或删掉ecoAddRepeaterECO流程常在freeze后严格受限保持最小改动局部修timing/DRC/antenna插入后默认不参与逻辑优化保留性好addBufferOnNet通用网表编辑不确定手工指定net加buffer常需要配合-insertDontTouch防止被优化掉addRepeater属于standard optimization的一部分工具可能根据自己的算法把它挪走哪怕是你在脚本里指定了位置。ecoAddRepeater就不一样它更像“一枚钉子钉进去”划定一个位置放上去就不太动它特别适合freeze后你不想让工具再折腾已有布局的情况。addBufferOnNet则更多用在网表级的编辑插完你一般要再手动设dontTouch属性否则后续任何一次优化都可能把它干没。1.3 它解决问题的底层逻辑为什么要插buffer/inverter本质是要把一个信号路径分成两段降低下游电容负载同时让驱动强度重新分配。一根net如果从driver到一个远端sink跨越了半个block线长的寄生RC会拖慢slewsink端很容易出现max tran违例。插一个buffer进net中间前一段是原driver带短互连后一段是buffer带目的地两边都轻松。更关键的是buffer/inverter自身只有几微米的物理尺寸改动极小。ECO的精髓就是“用最小扰动换最大收益”ecoAddRepeater恰恰把这一点发挥到极致。它不影响其他cell的位置不重摆已有逻辑只是“见缝插针”放一个标准单元再把原net的断口接到这个cell的input/output上。对ECO diff文件来说不过是多了一个instance和几条metal连接很多metal-only ECO的场合它甚至只需要在现有routing层做改动这对postmask ECO来说尤其珍贵。2. 命令语法和参数选择2.1 一次看清 ecoAddRepeater 的完整面貌这个命令的选项很多但常用的其实就那么几个。我在项目里是这么用的ecoAddRepeater \ -cell BUFX12 \ -net n1234 \ [-location {x y}] \ [-term inst/pinName] \ [-prefix ECO_BUF] \ [-narrow] \ [-allowPort] \ [-skipWarning] \ [-noLegalize] \ [-noDrVerify] \ [-inverterPair | -sameDriver]-cellar直接指定库里准备插的cell名字比如BUFX12、BUFX16、INVX8、DELBUFX2这些。不是所有cell都能随便插如果这个库cell的power pin没有完整定义或者高度和当前row不一致工具会报错。建议用那种高度匹配的、驱动强度适中的、并且带完整PG信息的buffer。-net是目标net名。Innovus里的net名字往往带层次或特殊字符建议你先用get_net确认实际名字再传参否则会得到一堆cant find net的报错。需要说明的是这里也可以不指定-net而是指定-term意思是“在这个pin所在的net上进进出出”。-location不指定时工具自动挑位置大多数情况下让工具自动选比手工指定更靠谱。手工指定的唯一好处是心理踏实坏处是万一位置不合法后续legalize和ecoRoute反而可能把它挪出天际还不如让工具自己一次到位。下面的脚本部分我再讲怎么精细控制位置。2.2 几个要命的选项细节-inverterPair这个选项很多人不清楚它的意思是如果原来的net本身有逻辑极性要求而你插的是一个inverter那么工具会自动再补一个inverter构成反相器对保证最终对外逻辑极性不变。这个设计逻辑很聪明因为ECO阶段你不想因为插了一个inverter而连带改动其他逻辑单元的pin连接关系。如果你明确知道自己不需要保持极性可以只用-sameDriver它让信号方向保持不变的同时允许插同相or反相cell但大多数保守做法是只要涉及inverter就必须用-inverterPair。-narrow指的是只允许工具在跟标准单元row垂直的窄条区域里放置这在postmask ECO或者部署区域非常受限的情况下非常有用。如果没加-narrow工具可能把buffer放到一个你完全没预料到的偏远角落等后面看ecoRoute结果时才发现绕了一大圈时序反而恶化。-skipWarning这个我建议别乱加。ECO阶段有些warning是有价值的比如告诉你某个位置有congestion、某个net已经断了之类的信息全跳过会导致你失去对关键告警的敏感性。我一般只有在批量跑大量修复脚本、确认warning无碍时才加。还需要注意-noLegalize和-noDrVerify。默认情况下ecoAddRepeater在插完cell后会跑一遍非法位置的修正和简单的DRC查验这能尽早发现问题。如果手动指定了特殊位置或者后面马上有精细调整可以临时跳过但如果只是常规使用别乱关否则你插的cell可能建立在overlap上后面出问题更难查。2.3 实际调用的标准流程参考工具插完cell不会自动接线所以完整流程至少要包含ecoRoute。我平时惯用的套路是先插再走线再检查ecoAddRepeater -cell BUFX12 -net n1234 ecoRoute -fixDrc # 如果涉及时序修复跑一次post-route optimization setDelayCalMode -eco setOptMode -eco optDesign -postRoute -hold这里有个容易忽略的事情很多版本的Innovus里ecoAddRepeater插完buffer后ECO route不一定立刻生效特别是新cell的pin如果和power/ground它们之间没有打上viaecoRoute -fixDrc就会帮你补上这些关系。先插、再route、最后再检查时序是比较稳妥的三部曲。3. Tcl脚本实战从单条命令到批量修复3.1 修max transition/cap违例的自动化脚本实际项目里不会一条条手工插。我会先拿到所有违例net然后循环处理。比如修max capset viol_nets [get_nets -quiet -hier -filter max_cap 0] foreach net $viol_nets { set cap [get_property [get_nets $net] max_cap] if {$cap 0.0} { ecoAddRepeater -cell BUFX12 -net $net -prefix ECO_CAP_ } } ecoRoute -fixDrc跑完后一定要重新报一下max cap看哪些net还是红的。一些net驱动能力差太多一个buffer不够需要插两级这时可以把violated net的driver chain不断往上加。不过也要小心如果同一根net反复插了三四次说明选型有问题直接换更强的driver cell可能更省事。修max tran的思路类似set poor_nets [get_nets -quiet -hier -filter max_tran 0] foreach net $poor_nets { set tran [get_property [get_nets $net] max_tran] if {$tran 0.0} { ecoAddRepeater -cell BUFX16 -net $net -prefix ECO_TRN_ } } ecoRoute -fixDrc report_design -physical注意一点插完buffer本身就是一台新的driver它自己又引出一条新的net如果原net的sink特别多插入点位置不同效果差异会非常大。工具自动选位置通常情况下会选在net的几何重心附近这是合理的默认行为不必过度干预。3.2 hold time修复场景hold time修复跟max tran还不太一样它更关心delay。插buffer的本质是通过增加cell delay和额外的net delay来“拉长”data path让它在capture edge之前不要变化太快。Innovus的optDesign -postRoute -hold其实也能做但ECO下我更愿意自己控制插哪些位置。通常的套路是这样先把所有hold违例路径找出来根据endpoint pin定位到net然后在这个pin的上游插入一个delay buffer。因为需要保证逻辑等效性这里建议用DELBUF类型的cell它有专门的延迟量而且不改变逻辑极性。如果库里没有DELBUF就用普通BUF也可以但要注意驱动强度选小一点这样cell delay相对大。太小了反而成了负载导致slew变差。示例逻辑set hold_fix_nets {} set viol_paths [get_timing_paths -delay_type min -max_paths 1000] foreach path $viol_paths { set endpin [get_property $path endpoint] set net_name [get_property [get_nets -of_objects $endpin] name] set slack [get_property $path slack] if {$slack 0.0} { ecoAddRepeater -cell DELBUFX1 -net $net_name -term $endpin -prefix ECO_HOLD_ } } ecoRoute -fixDrc optDesign -postRoute -hold关于位置除了用-location更常做的是用-term限定一个方向或一个pin范围去插。比如在endpoint附近插这样额外的net delay加在数据路径末端对启动路径影响最小也不用担心影响到别的sink。3.3 antenna违规修复脚本antenna违规一般发生在长的metal线上电荷在制造过程中聚集过多可能损伤gate oxide。修复手段之一就是在net中间插buffer或inverter把长的metal链拆成两段。不过更常被接受的是换用带antenna diode的cell或者绕线时直接跳层。如果用ecoAddRepeater要注意极性。插一个普通buffer不会改变逻辑但如果插inverter就必须配-inverterPair。我在脚本里通常会先判断python里说的antenna信息怎么取其实Innovus可以直接:set viol_net_list [get_antenna_violating_nets] foreach net $viol_net_list { ecoAddRepeater -cell ANTBDX1 -net $net -inverterPair -prefix ECO_ANT_ }如果你的库里没有专门的antenna cell只能插普通buffer那修复后要重新跑一遍antenna检查因为新cell的存在本身也会引入新的antenna风险。一般来说插完后net长度变小antenna值也会下降。如果下降不明显试一下换驱动强度更小的cell缩短门极面积间接降低antenna比例。3.4 一个更优雅的网表级操作addBufferOnNet dontTouch虽然题目问的是ecoAddRepeater但有时候你插一个buffer只是“过渡”后面还要继续编辑网表或者你就是想在结构上分出一个节点而不是真的做物理时序修复。这时候addBufferOnNet更灵活set buf [addBufferOnNet -cell BUFX12 -net n1234 -name ECO_BUF_X] set_dont_touch [get_cells $buf] true这个做法的优势是它对net的层次结构更友好能明确返回新实例的路径名方便脚本引用。缺点是它不关心物理摆放也不会自动place插完你还得自己调位置而且一旦忘记设置dontTouch后面任何优化都可能把buffer吞掉。所以我的经验是——如果是“临时解决方案”用addBufferOnNet如果是“打算直接流片”用ecoAddRepeater不让工具再乱动它。4. 位置控制、布线联动和后处理细节4.1 如何让buffer插到你真正想放的位置前面说了-location能指定坐标但实际中很多人只知道给坐标却不知道坐标怎么来。比如你想把buffer放在某个cell旁边那就要先取到那个cell的bbox再算出中心点。这套操作不难但要写得严谨set target_cell [get_cells u_block/u_reg_array_reg_0] set bbox [get_bbox $target_cell] set x_min [lindex $bbox 0] set y_min [lindex $bbox 1] set x_max [lindex $bbox 2] set y_max [lindex $bbox 3] set x_center [expr {($x_min $x_max) / 2.0}] set y_center [expr {($y_min $y_max) / 2.0}] ecoAddRepeater -cell BUFX12 -net n1234 -location [list $x_center $y_center]直接指定位置最大的坑是你算出来的点可能落在别的cell上面或者落在row外部、power stripe下面这时候工具会报location illegal。你可以配合-noLegalize看一下原始位置效果但一般不建议开开了的话你就要自己想清楚后续怎么legalize。一个更稳健的思路是先不放位置插完后再检查它具体落到了哪里必要时用physical cell move把它挪到一个更理想的位置。这样既避免一开始和工具闹别扭又能事后微调。不过我试过几次ecoAddRepeater的自动位置优化通常比我自己凭感觉给坐标要好所以手工指定坐标要慎用。4.2 插完buffer之后必须做的三件套插一个实例进design可不是加一个cell就完事后续还有一连串问题要处理。第一电源和地。新的cell需要有PG连接。如果所在row有完整的power stripeecoRoute -fixDrc一般会把它接上如果你选的位置在power domain的边缘可能要手动调用ecoRoute -power或者sRoute确保VDD/VSS打通。这个不检查的话后仿或者LVS阶段会冒出一堆unconnected pin。第二信号线的重新连接。ecoAddRepeater自动把原net拆成两段前面连到buffer的input后面从buffer的output驱动下游但这都是隐式的。你在GUI里看eco operation多了一个instancenet list也变了但物理连线是ecoRoute干的活。所以插完一定要跑布线我每次跑ECO后都会先来一发ecoRoute -fixDrc把短的、绕的、DRC的全部扫一遍。第三时序更新。不重新extract RC和跑timing你根本不知道这个操作到底把时序改好了还是因为插得太远反而把延迟加大了。我的习惯是extractRC update_timing report_timing -through [get_pins ECO_BUF_*/Y] -delay_type max很多人在ECO里只做物理检查不做时序更新这是大忌。ECO改动虽小但插入后的RC变化可能让本来不违例的邻居net产生新的违例。4.3 postmask ECO 的限制要提前想好有一种场景对ecoAddRepeater的影响很大——postmask ECO。金属层已经定版只有高层metal还能改新的buffer不能自由落在任何位置只能落在你已经预留的spare cell里或者工程改动范围内的空白区域。这种情况下你插的不一定是库里新生成的buffer而很可能是复用预先放好的、没有逻辑连接的spare cell。Innovus里如果你心里想的是postmask ECO那ecoAddRepeater的cell参数最好直接指向那些已经摆在floorplan上的spare cell名字比如SPARE_BUF_X1_Y2。插法一样但效果是它不会新增物理cell只是把spare cell的逻辑pin用metal接出来。如果你指向一个全新的buffer cell工具在postmask模式下往往会告诉你该位置没有可用的摆放空间或cell row不匹配。所以做postmask之前第一件事先确认你的spare cell清单和分布。这一点我在第一次做metal-only ECO时栽过跟头以为操控的是ecoAddRepeater实际却是在跟layout是否允许新增cell做斗争。4.4 设计刷新和输出管理每次ECO插完buffer我还会习惯性跑一下design refreshecoDesignPostRefresh这一条作用很微妙它把ecoAddRepeater产生的instance加入GUI视图同步一些db属性同时把后续可能需要的物理信息刷新。如果你没有跑这个GUI里有时候会看到ECO的cell出现在奇怪的位置或者ecoRoute后时序arc没有正确显示。在输出ECO脚本时也要注意ecoAddRepeater不是纯GUI操作命令顺序很讲究。我的惯例是先保存一份pre-ECO的netlist和def用ecoAddRepeater系列命令做改动跑ecoRoute;再保存post-ECO版本用diff对比两个netlist确认只有预期的instance和连接变化。这个习惯帮我挡掉过好几次误操作。ECO本来就是一个小改动的活如果diff里冒出来一大堆不相关的变化说明工具在背后替你做了很多额外决定这时候要回头查一查别急着往下走。5. 常见问题、报错惯例与调试技巧5.1 找不到net或者不认识instance我在命令行里最常遇到的报错是Cant find net xxx。大部分人第一反应是“明明名字就是这个”但Innovus里net名经常带层次和特殊符号。更诡异的是同名的底层net在不同block里都存在必须用-hier精确到层次路径。一个靠谱的找法是用Tcl过滤get_nets -quiet -hier * -filter full_name ~ *n1234*或者get_flat_nets -filter name n1234如果你拿到了完整的层次路径ecoAddRepeater里用那个路径就不会有问题。这里还有个细节tool里net上的[]会被当成list解析如果net名叫n12[3]这种你需要对中括号转义或者用花括号包住。踩过一次这个坑之后我每次写脚本都会在net名字外面套一波筛选逻辑确保传给ecoAddRepeater的是预处理过的确切名字而不是手敲的原名。另外一个容易翻车的是instance name。比如你想在某个实例附近插buffer先通过get_cells拿到的是u_block/u_ctrl_inst_0直接拿这个名字去写约束没问题。但如果这个cell是physical only的或者被工具标记了dont_touchecoAddRepeater不会处理。5.2 插完inverter反而把逻辑改反了这个问题特别经典我见过很多次插之前没想清楚逻辑极性插完inverter后下游逻辑全变了功能仿真对不上。虽然前面说-inverterPair可以避免但有一些细节容易漏。-inverterPair在概念上是插入一对反向器让整体极性不变。但在物理上它是两个cell摆放空间翻倍而且中间要留出连接线。在布局资源极度紧张的位置工具可能报无法分配足够空间。这时你有两种选择一是换一个更小尺寸的inverter pair二是改成用非反相buffer。如果你的本意只是增加delay不是改变逻辑那坚持用buffer更省事。如果本身就是要插inverter做逻辑调整那我建议你插完立刻做形式验证或者跑一遍connectivity检查。具体说就是比对原net驱动pin的logic level和新net路径的最终输出pin的logic level确保改变的只是被指定修改的那一段。5.3 新cell落在power stripe底下或者和别的cell重叠这个看起来是位置问题根源在于你没有做physical legality检查。ecoAddRepeater虽然默认会做legalize但有些版本或者某些特殊cell它并不会主动帮你检测所有物理约束。我遇到过一次工具把buffer放在一条电源地rail的正中间DRC报告出来几百个short。解决这类问题的排查路径是# 检查新cell的物理位置 report_placement -inst ECO_BUF_* # 看看周边有没有overlap report_placement -inst [get_cells -hier *] -detail # 有overlap时先手动移开 legalize_placement -cells [get_cells ECO_BUF_*]一般跑一遍legalize_placement就能解决。如果问题持续检查是不是库里的这个cell height与当前row不匹配比如row是12T的你用的是9T的buffer工具可能把它摆在row里但会和邻居不齐后续DRC、CDL全都受影响。选cell前先到库里面看一眼这个cell的site或row type和当前design是否兼容。5.4 插了buffer后时序反而更差这事谁遇到谁都头大但原因往往不复杂。最常见的问题是插入点过于靠近sink导致前面那段长线仍然挂在高负载下buffer几乎没有分流作用或者插入点离driver太远buffer的input slew退化得非常厉害相当于后面整条路径都被拖累。所以插完buffer后我一般会重点看两端report_timing -through [get_pins ECO_BUF_*/A] -through [get_pins ECO_BUF_*/Y] report_net -net n1234 -physical如果发现input slew很差大概率是插入点太靠后。解决方法是删掉这个buffer重新让工具自动选位置或者手动指定一个靠近原driver的位置。如果output那一侧还是有很多sink且slew仍差说明这一个buffer不够分可以再加一级。另外还有一个容易忽略的细节插buffer的cell型号。很多项目标准单元库里DELBUF/BUF型号很多从驱动能力0.5x到16x都有。给长线硬塞一个X2的buffer往往毛用没有线电容反而把buffer输出拖垮。选type时建议先report一下当前net的总cap再用lib里的buffer驱动能力做匹配别“能插就行”。5.5 如何选中一个名字很怪的PG term热词里有一个“innovus怎么选中标准单元名字为biasnw的pg term”这个问题跟ecoAddRepeater看似没关系但在插完buffer后如果你想手动检查PG连接经常会遇到这种怪名字的term。标准单元的电源地pin名字往往不是VDD/VSS而是类似VNW、VPW、BIASNW这种。想选中这类PG term你要用dbGet或者get_terms配合正则dbGet [dbPGTerm -if {.name BIASNW}] .name或者get_terms -quiet -of_objects [get_cells u_block/*] -filter name BIASNW通过这种方式你能把某个特殊PG pin从一堆cell里全部找出来再配合report_design或verifyConnectivity判断它们是否都正确连接。这个操作在ECO里很重要因为新增的buffer如果没有正确接入特殊PG pin某些biasing电路的漏电和阈值特性会异常后仿和流片都容易出问题。5.6 命令执行成功的假象最后说一个最坑的情况ecoAddRepeater执行不报错但什么都没插进去。这种“静默失败”通常是因为工具认为该net不需要修复或者找不到合适的物理位置。我不止一次跑完脚本后发现ECO list里空空如也回头排查才发现指定net的driver是tiehi/tielo或者是一个常数值工具默认不插cell。应对办法很简单插完要自己验收一步# 看看实际上插了多少个ECO buffer get_db [get_cells ECO_BUF_*] .name count [get_cells ECO_BUF_*]如果数量跟你预期的违例数对不上那就去查一下到底哪些net没被处理多半是net被标记了dont_touch、dont_use或者它是时钟树的net。时钟树的buffer/inverter一般不用ecoAddRepeater手动插而是通过时钟树优化工具手动插反而容易破坏时钟SKEW。6. 更精细的ECO操作思路与扩展想法6.1 用RVE和GUI联动比盲写脚本更快命令行方便批量操作但第一次在某个design上插buffer时我建议先打开Innovus GUI用ECO菜单里的Add Repeater功能手动插一个观察工具默认把cell放在哪里、net是怎么断开的、走线怎么连的。这能帮你熟悉这条命令在当前设计上的行为模式后面写脚本时心里就有底。有经验的工程师可能会用pick的方式选中某条net后直接右键选Add Repeater省去打命令的时间。但注意GUI操作里的默认参数不一定是批次脚本里你想要的比如它可能默认选一个你完全没听过名字的buffer型号或者默认加上-inverterPair这些都要回头检查log确认。6.2 多根net同时修正时如何避免连环反应在密集区域同时给多根net插buffer时新的cell会抢占本就紧张的空间前面的插入会改变后面net的物理分布。这种连环效应在手动单条操作时不算明显但批量跑完就容易看到有的buffer被挤到了几千微米外的角落有的net绕了半圈还更差了。处理这种场景我把net分成几批处理每批不超过20条插完立刻跑ecoRoute和DRC然后再处理下一批。这样一是定位问题时容易回溯二是避免工具同时动作太多导致布局质量大幅下降。批次之间如果发现某个区域已经塞满我会对后面的net改用较远位置插入或者先跳过等下一轮再尝试。6.3 如何配合spare cell流动复用很多公司pre-mask ECO的规范是优先复用spare cell少新增物理cell。ecoAddRepeater本身没有专用spare cell参数但你可以通过-cell指定到具体的spare buffer名。比如你floorplan阶段摆了1000个SPARE_BUF_X1那么在ECO修复net时把每次要插入的buffer名字轮询指向空闲的SPARE_BUF_X1实例。当然这些cell初始是没有逻辑连接的ecoAddRepeater会在它们未被使用的前提下把它们接进去。使用完记得把该spare cell的dontTouch属性撤掉让它变成正常逻辑cell否则后面CTS可能避开它反而影响时钟树质量。这个方法能省下不少面积也让ECO的物理改动控制在metal层内成本优势很明显。6.4 后续拓展方向如果ECO里buffer/inverter插入的频率高、数量大我建议自己封装一层Tcl proc把选net、选cell型号、插入、ecoRoute、report一套流程合并起来加上log输出和异常返回值。封装后不仅项目组成员能直接用后面写ECO脚本也更稳定。也见过有人把ecoAddRepeater和Machine Learning模型结合通过历史ECO数据训练插buffer位置预测那是比较前沿的玩法不过一般团队没这个条件。实用一点的是把“违例net列表”和“可复用spare cell列表”做成CSV脚本读取后自动匹配这样团队里其他人即使不熟命令细节也能安全操作。我个人在实际项目里最大的感受是ecoAddRepeater是一个门槛不高但细节很多的命令真正拉开效率差距的是对design物理状态和ECO约束的理解而不是命令选项本身。每次插完buffer我都要告诫自己先别急着往下走看一眼它在哪儿、连了谁、时序变没变把这“三板斧”养成习惯ECO的返工率会低很多。