ARTICLE DETAIL

资讯详情

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

Innovus ECO定点修复:ecoAddRepeater插入buffer/inverter指南

Innovus ECO定点修复:ecoAddRepeater插入buffer/inverter指南 做数字后端的人应该都有这种经历整个chip的时序已经收敛得差不多了floorplan也基本稳定就差几条长net的max_cap违例或者某个antenna violation卡在DRC里。这个阶段你肯定不想为了一条net重新跑一遍optDesign——一跑就是好几个小时还很可能把周围本来干净的cell全部挪动一遍。更合理的做法是直接定位到那条net手工插一个buffer或者一对inverter把负载断开、把长线打断用最小改动把violation修掉。Innovus里干这件事最顺手的命令就是ecoAddRepeater。我最早用这个命令是在一个28nm的项目上当时为了修一条跨了整个block的高扇出信号前前后后试了手动addInstance加连线、也试过直接把cell丢给preroute最后发现还是ecoAddRepeater最省事。它把创建一个instance、切断原net、重连所有pin、保留net名这一串操作打包成一条命令省下的不仅是脚本量更重要的是避免了手动操作时漏连pin或者把net拆碎的问题。这篇文章把ecoAddRepeater从适用场景、命令参数、实操流程到翻车细节完整梳理一遍适合正在用Innovus做后端收敛、或者刚接触ECO流程的工程师参考。1. 什么时候会想起ecoAddRepeater三类绕不开的修复场景很多教程一上来就讲语法但实际工作中该不该用这个命令永远比这个命令怎么敲更重要。我回顾自己用ecoAddRepeater的几次典型经历基本集中在下面三类场景里。1.1 时序收敛末期的定点修复项目跑到后端后期floorplan定了、时钟树综合完了、绕线也基本闭合了这个时候时序报告里常常还剩少量violation。这些violation往往不是结构性的而是某条net负载偏重、某段走线太长这类局部问题。这时候你的诉求是只动这一条net不影响周围其他逻辑不改变已经收敛的时钟结构。如果重新跑一遍optDesign工具会基于全局优化目标调整大量cell的位置和尺寸结果可能把本来已经收敛的路径又扰动出新的violation。手动定点插入repeater则完全不同新加一个buffer只改变了目标net上的驱动关系对周边逻辑的影响范围非常有限。我记得有次修一条hold violation就是在data path上定点插了一对inverter插入后只重新跑了局部时序就过了整个block的其余部分完全没动。这种最小改动能力恰恰是ECO命令存在的意义。1.2 max_trans/max_cap violation的快速止血长net加高扇出是max_cap/max_trans violation最常见的来源。比如一条net从driver出来走了400um中间挂了七八个sink pin到了末端信号边沿早就变缓了。这种问题在post-route阶段特别常见尤其在信号跨block边界或者穿过congestion区域的时候。处理方法就是在走线中段插入一个buffer把原来一段长net变成两段短net。第一段是原driver到buffer input距离短了第二段是buffer output到剩余sink虽然距离可能还是有点长但buffer的输出驱动能力比原driver强能够扛得住。这里的关键是选对buffer尺寸选太小了后半段slew还是差选太大了新cell本身input cap大反而把前半段拖累。实际项目里我一般从X2或X4开始试然后看timing报告里哪一段变差了再往上调。1.3 天线效应的定点打断第三个常见场景是修antenna violation。芯片制造过程中长金属net会像天线一样收集电荷等离子体刻蚀时电荷积累到一定程度可能会击穿栅氧化层。物理设计阶段的修复手段最直接的就是把长net打断让天线变短。打断的方式可以人工跳线也可以在中间插入inverter或buffer。这里有个经验修antenna时大家更倾向于插inverter而不是buffer。原因很简单库里面inverter的cell种类通常比buffer多小尺寸inverter的面积和驱动能力选择更灵活而且两个inverter可以放在完全不同的位置灵活地切断两段不同的金属长度。我自己在实际项目中修antenna时就经常把一对inverter一个放在走线起点附近另一个放在终点附近中间天然形成一个断点metal length立刻被劈成两半。逻辑功能上两个inverter级联等于一个buffer极性不改变。1.4 顺带一提hold修复里的Buffer插入上面三个场景是ecoAddRepeater最常见的用途但还有一个容易被忽略的场景是hold时序修复。hold violation本质上要求data path上的delay增大而插入buffer或者inverter对就是最简单可靠的delay添加方式。相比在CTS阶段大动干戈在post-route后用ecoAddRepeater在特定路径上插入一两级cell加delay的效果非常可控。需要注意的只是cell delay相对较小如果hold slack差得比较多可能需要串好几级这时候不能光靠手动插得考虑工具自动修复了。2. ecoAddRepeater的语法与参数拆解命令手册之外的关键点命令本身不难难的是把每个参数吃透知道哪些参数不加会出问题哪些参数加了反而添乱。我习惯先查一遍help ecoAddRepeater但真正决定脚本质量的是你对每个参数背后行为的理解。2.1 一条命令完成建cell、断net、改连接先看最基本的调用方式ecoAddRepeater -cell BUF_X4 -net data_net_1 -location {100.0 50.0} -insertName eco_buf_001 -noFix这条命令做了三件事创建一个叫eco_buf_001的BUF_X4实例把data_net_1这个net在指定位置断开然后把新cell串接进去——input接到原来的driver端output接到原来的sink端。注意这里说的断开不是把net删掉而是改变net的连接拓扑net的名字仍然保留。这正是ecoAddRepeater和手动addInstance specifyConnection的本质区别。手动方式需要你自己管理中间节点的连接关系很容易出现net断成两截、原driver悬空这类问题。ecoAddRepeater把这些细节全部处理掉了你只需要关心插什么cell、插在哪里。这也是我把这个命令推荐给所有做后端的工程师的原因——它把高频操作封装成了原子操作出错概率大幅降低。2.2 核心参数逐个说-cell、-net、-location、-insertName命令的参数不少我按使用频率逐个说下我的理解和建议。参数含义我的建议-cell指定要插入的库单元名称先用get_lib_cells筛选写全名别用通配符含糊带过-net目标net对象建议用get_nets获取对象后传入不要手敲net名字符串-location插入位置的坐标注意这个坐标是cell的origin不是cell中心-insertName新instance的名字强烈建议每次手动指定方便后续追溯和eco脚本管理-noFix插入后不固定cell位置需要后续继续优化的场景用freeze阶段则不加这个参数-numStages插入级数插inverter时注意偶数级保持逻辑有的版本支持一次插多级-cell参数的选择直接决定修复效果。我习惯用get_lib_cells先看看库里有哪些可用的buffer和inverterset bufferCand [get_lib_cells -quiet *hvt/BUF_X*] set inverterCand [get_lib_cells -quiet *hvt/INV_X*]列出来之后我会关注每个cell的area和input pin cap。这里有个常见的认知误区不是驱动越大的cell越好。大驱动cell本身的input cap也大把它串在原driver后面原driver的负载反而增加了。如果原driver本身驱动能力就不强插一个大cell等于雪上加霜。稳妥的做法是从X2开始试看timing报告再微调。2.3 buffer和inverter怎么选从cell列表到实际对比很多初学者会问一个很实际的问题修一条net我到底是插buffer还是插inverter这个问题的答案取决于你的需求和库里的资源。对比项Buffer两级Inverter逻辑极性保持保持两级级联单级面积通常略大可选用小尺寸inv两级总面积可控摆放灵活性一个cell只有一个位置可以是两个分散位置打断能力更强驱动强度选择受库中buffer种类限制小尺寸inv选择通常更丰富典型场景max_cap/max_trans修复antenna打断、hold修复从上面的对比能看出来如果只是单纯需要增加一级驱动能力buffer确实更直接一个cell搞定。但如果你的核心诉求是打断一段长线或者需要在时序路径上增加可控delay两个inverter反而更灵活。我个人的经验法则是修复max_cap/max_trans优先看buffer修复antenna、修hold、或者在floorplan上间隔很远的两个点之间打断net优先考虑inverter对。2.4 插入后连接关系是怎么变的理解ecoAddRepeater插入后的连接变化是避免后续踩坑的基础。以buffer为例插入前是driver - net_A - sink1/sink2插入后变成driver - net_A_pre - buffer_inbuffer_out - net_A_post - sink1/sink2。也就是说原来那条net被截断成了两段但工具会沿用原来的net名来指代包含sink的那一段而driver到buffer input这一段会隐式存在一个内部连接。这个行为对你写脚本有什么影响最直接影响是如果你在插入buffer之后再用get_nets去查原始net名拿到的对象可能已经变成了后半段net而前半段可能需要通过buffer的input pin去回溯。我早期写批量脚本时就栽在这个上面对着已经改变拓扑的net做后续操作结果报了一堆no such net的错误。所以我的习惯是每次ecoAddRepeater之后重新get_nets刷新一次net对象再进行下一步操作。还有一个和-numStages相关的细节。如果你插的是inverter不要天真地以为工具会自动帮你保持逻辑极性。-numStages 1插一个inverter逻辑就是反相的你必须再插一级才能恢复。有些版本支持-numStages 2一次插两级但我建议你还是分步做每插完一级就检查一下连接关系逻辑错了可以及时发现。ECO阶段最怕的就是连锁错误一步错后面全错。3. 从拿到violation到插入完成的完整实操流程说完了参数我们来走一遍完整的实操流程。这一章我按照真实项目的处理顺序来写先定位问题net再决定插入方案执行ecoAddRepeater最后做legalize和ecoRoute收尾。3.1 先定位问题netreport_timing / report_net怎么用拿到时序报告不要急着找命令先把violation对应的net找出来。我的做法是先用report_timing看路径report_timing -through net_xyz -nosplit这里-through可以精确定位经过某条net的所有路径看哪些endpoint因为这条net的delay而violation。如果问题确实是这条net本身驱动不足report_net会给你更直接的信息report_net -net net_xyz -verbose这个命令会列出net的total capacitance、driver的transition、sink的个数等关键数据。我判断是否需要插buffer的指标很简单total cap是否接近或超过driver cell的max_capdriver到最远sink的物理距离是否超过300um左右具体数值和工艺、金属层有关但300um是一个常见的经验阈值sink端接收到的transition是否已经明显劣化。定位完之后再打开GUI高亮这条net看一眼它的实际走线形状。这一步花不了30秒但能帮你确认插入点的可选范围。有些走线会穿过macro上方的blockage区域或者挤在拥塞通道里这些位置都不能作为插入点。3.2 场景A修复一条高扇出net的max_cap违例假设我定位到一条叫din_valid的net驱动cell是一个INV_X1挂了12个sink总电容超标40%。修复方案是插一个buffer把负载分成两半。第一步从库里选一个合适的bufferset bufCell [get_lib_cells tt1p0v25c/TYP/BUF_X4]第二步计算插入位置。最省事的办法是取所有sink pin坐标的几何中心然后吸附到最近的合法row上。示意脚本set netName din_valid set netObj [get_nets $netName] set pinBbox [dbGet [dbGet -p top.nets.name $netName].box] set locX [expr ([lindex $pinBbox 0] [lindex $pinBbox 2]) / 2.0] set locY [expr ([lindex $pinBbox 1] [lindex $pinBbox 3]) / 2.0] ecoAddRepeater -cell $bufCell -net $netObj -location $locX $locY -insertName eco_buf_din_valid -noFix注意这里我的-location用了网表bbox的几何中心在实际项目中这个点很可能落在cell上或者blockage里。所以我不会直接用这个坐标而是把它当初始值在GUI里手动微调选一个附近没有摆放cell、不属于placement blockage的位置。也有一些平台有自动找空位的脚本但没脚本的情况下用手动微调也够用。第三步插入完成后跑一下legalize并重新布线legalize_placement ecoRoute -net $netNamelegalize_placement负责把新插入的cell放到合法的site上ecoRoute则把打断后的两段net重新连起来。这两步缺一不可后面4.3还会细说。3.3 场景B定点打断长net并保持逻辑两级inverter再来看一个打断长net的例子。假设有一条控制信号从block的东边一路走到西边总长度超过700umantenna报告好几个pin都有风险。我的处理方案是在走线1/3处和2/3处各插一个inverter把一段700um的长net切成三段每段都不超过250um。set invCell [get_lib_cells tt1p0v25c/TYP/INV_X1] set netObj [get_nets ctrl_signal] # 第一级inverter放在走线前段 ecoAddRepeater -cell $invCell -net $netObj -location $x1 $y1 -insertName eco_inv_ctrl_a -noFix # 重新获取net对象 set netObj [get_nets ctrl_signal] # 第二级inverter放在走线后段 ecoAddRepeater -cell $invCell -net $netObj -location $x2 $y2 -insertName eco_inv_ctrl_b -noFix这里有个非常关键的细节第一级inverter插入之后第二级的-net参数必须重新获取。因为第一级inverter已经改变了ctrl_signal这个net的拓扑第二级插入的位置是当前net上的另一个断点而不是原始net上的断点。有些工程师在这里偷懒沿用第一次获取的net对象结果第二级插完之后发现连接关系完全不对inverter串错了位置甚至串成了回路。还有一个我自己常用的验证方法插完之后马上在GUI里高亮这条net沿着driver到sink看一遍确认inverter的极性方向正确。两级inverter的方向性如果搞反了表面看连接没错但逻辑上就废了。高亮检查30秒能省掉后续排查很久。3.4 插入后的legalize与ecoRoute衔接ecoAddRepeater做完之后工序远没有结束。这个命令只负责创建instance和修改连接关系不会自动帮你把cell放到合法的site上更不会自动绕线。我见过不止一个同事插完buffer后直接跑LVS结果报出一堆open就是漏了ecoRoute。我习惯的操作顺序是ecoAddRepeater -cell $cell -net $netObj -location $x $y -insertName eco_cell_xxx -noFix legalize_placement ecoRoute -modifyOnly $netName其中ecoRoute -modifyOnly只会绕我指定的net不会触碰其他已经收敛的走线适合这种定点修改的场景。如果你的改动比较大或者net数量多也可以直接跑ecoRoute全局重绕但那样耗时会长很多而且有扰动其他net的风险。我的原则是能局部绕就不全局绕ECO最大的价值就是改动可控。如果插入的位置附近有placement blockage或者macrolegalize_placement可能会把新cell推到一个出乎意料的位置。这种情况我会重新检查一下位置再跑一次ecoRoute确保走线没有异常绕远。4. 实测中容易翻车的细节与规避方法这一章是我最想写的部分。命令语法、流程框架都是公开的但真正让你在项目里少加班的是那些不会写进手册的坑。下面这几个都是我实际踩过的。4.1 坐标定位为什么插完cell会和旁边单元重叠我刚用ecoAddRepeater的时候最常遇到的问题就是插完cell之后和旁边的cell重叠。原因其实不复杂-location参数指定的是cell的左下角origin坐标不是cell中心坐标。当你用net的bbox几何中心作为插入点时如果这个位置附近已经有别的cell占位新插入的cell就会跟它重叠。另外还有一个隐蔽的问题是site row和电源轨的关系。就算坐标没有和已有cell重叠如果Y坐标没有对齐到power grid的row上新cell的VDD/VSS就无法和电源轨对齐后续连pg的时候会非常痛苦。我的规避方法是三步走第一步用dbGet查出附近已有cell的box范围避开占位区域第二步在GUI里打开placement blockage层避开所有blockage和macro边缘第三步确认插入点能被legalize_placement正常吸附到row上。如果工具版本支持也可以直接把坐标吸附到最近的sitesnapFpSite -pin $x $y这个命令在Innovus里可以帮你把坐标对齐到合法的site grid配合ecoAddRepeater使用能省掉很多后期legalize麻烦。不过不同版本行为略有差异我建议你在自己的环境里先做个小实验验证一下。4.2 net名带hierarchy时如何安全引用ECO阶段面对的net经常是hierarchical的名字里可能带/或者转义字符。这种情况下直接手敲net名很容易出问题。我踩过的一个坑是这样的有次从时序报告里复制了一个net名看起来是top/block_a/inst_b/data_out直接传给-net参数工具报错说找不到net。后来才发现在Innovus的命令行环境里这种带hierarchy的net名需要经过get_nets正确解析而且要处理转义。安全写法是set netObj [get_nets -hier top/block_a/inst_b/data_out] ecoAddRepeater -cell $bufCell -net $netObj -location ... -insertName ...这里的关键变化是用get_nets -hier获取net对象而不是直接传字符串。还有一个经验如果这条net的名字在多个hierarchy层级都存在get_nets可能会返回一个list你需要确认自己拿到的是不是目标net否则插错位置比不插还麻烦。我一般会在get_nets之后打印一下net的名字和物理位置确认无误再执行插入。4.3 插入后的PG连接不要想当然这是最容易被忽略的一个点。ecoAddRepeater创建了一个新的标准单元但它会不会自动给你把VDD/VSS连上我的实测经验是不要想当然。不同版本、不同flow下行为不一样有的版本在插入时会自动处理pg有的版本则不会。如果你做完ecoAddRepeater和ecoRoute之后没有检查pg极可能出现新cell的VDD/VSS悬空在后端LVS阶段报出一堆奇怪的错误。稳妥的做法是插入之后主动检查并补一次pg连接connect_pg_net或者根据你flow里的电源连接方式通过sroute把电源网重新连一遍。我的习惯是凡是ecoAddRepeater插了cell后面一定跟一句connect_pg_net或者全局sroute宁可多跑一步也不要留下悬空pg的隐患。这里还有个小细节如果新插入的cell用的是特殊的power domain比如always-on domain的buffer你要确保ecoRoute时不会把它的pg误接到其他power domain上。跨domain的信号修复建议先用check_pg_net确认power domain边界再决定插入的cell类型。4.4 时序反而变差的几个隐藏原因有时候插完bufferre-run timing发现violation不减反增。我总结下来最常见的是下面几个原因。第一个原因是插入点离原driver太远。buffer input接的是原driver的输出如果原driver到buffer input这段走线比原来更长了那原driver的slew可能变得更差相当于把问题前移了。这时候要么把buffer往driver方向挪要么给前面的driver也换一个大驱动cell。第二个原因是buffer选得过大或过小。过大的buffer自己input cap大把前一级拖垮过小的buffer输出驱动不足后半段slew依然差。参考做法是先用X2试看哪一段是瓶颈再决定往上还是往下调。第三个原因比较隐蔽插入的cell被工具加了dontTouch或者fixed属性导致后续optDesign无法优化它。如果你的流程是在ecoAddRepeater之后还会跑一轮optDesign一定要记得用-noFix参数否则这个cell被固定住工具只能绕着你插的cell做优化反而可能做出奇怪的走线。5. 把ecoAddRepeater放进日常ECO流程一些工程化经验命令用熟了之后你会发现自己写的ECO脚本越来越套路化。这一章聊聊怎么样把这些套路固化成流程减少项目之间的重复劳动。5.1 和ecoRoute、optDesign -eco的正确配合顺序我早期做ECO有个坏习惯插完buffer就急着跑optDesign想让工具把时序彻底优化干净。结果工具一跑把我手动插的buffer又挪走或者换掉了等于白做。后来才理解手动ecoAddRepeater和工具自动优化是有分工的——前者做定点干预后者做全局优化。如果你希望工具尊重你插入的cell就得用-fix让它固定住如果你希望工具接手后续优化那就用-noFix并且不要对插入后的位置抱有过高期望。我目前最顺手的流程是这样的先用ecoAddRepeater做定点修复比如打断长net、解除max_cap然后用legalize_placement确认位置合法再用ecoRoute -modifyOnly把涉及到的net重新绕通最后再跑一轮optDesign -eco做整体微调。这个顺序能保证手动干预和工具优化之间不冲突。5.2 批量修复多条net的脚本模板单个net的修复会了批量处理其实就是在外面套一个foreach循环。但有几个细节要注意我直接给一个比较完整的模板set bufCell [get_lib_cells tt1p0v25c/TYP/BUF_X4] set netList [list din_valid ctrl_signal data_bus_7] foreach netName $netList { # 每次都重新获取net对象避免拓扑改变后引用失效 set netObj [get_nets -quiet -hier $netName] if {[sizeof_collection $netObj] 0} { puts Warning: net $netName not found, skip continue } # 计算插入中心并稍微偏移降低重叠概率 set box [dbGet [dbGet -p top.nets.name $netName].box] set locX [expr ([lindex $box 0] [lindex $box 2]) / 2.0] set locY [expr ([lindex $box 1] [lindex $box 3]) / 2.0] ecoAddRepeater -cell $bufCell -net $netObj -location $locX $locY -insertName eco_${netName}_buf -noFix legalize_placement ecoRoute -modifyOnly $netName }这个脚本并不复杂但已经能处理大部分批量插入的需求。真正重要的一点是循环里那句注释——每次迭代都必须重新获取net对象。因为第一次插入会改变net的拓扑第二次如果还用旧的引用轻则插入位置偏差重则直接报错。批量修复时还有个小经验插入前先把所有net按物理位置排序优先处理距离相近的net可以减少legalize和ecoRoute的扰动范围。5.3 什么时候不该手动插尊重工具的使用边界ecoAddRepeater很好用但它不是万能的。我见过有些工程师拿到几十条violation第一反应就是写个foreach循环批量插buffer结果插了三十多个cell拥塞恶化、时序更差最后不得不全部ecoDeleteInstance清掉重来。我的判断标准是如果violation数量是个位数而且每条都能明确定位到net和原因手动ecoAddRepeater是最好的选择精准、可控、改动小。但如果violation数量超过二十条或者分布在多个区域、原因各不相同这时候应该考虑跑optDesign -eco或者针对性的工具自动修复流程让工具基于全局成本函数去优化。手动插入的优势是定点可控代价是缺乏全局视角数量一多就容易顾此失彼。另外如果是纯antenna violation的批量修复现代Innovus在布线阶段本身就有天线效应修复选项优先在route时解决比post-route手动插inverter效率高得多。ecoAddRepeater更像是补刀手段而不是主力的修复工具。5.4 建立自己的cell选型表和插入记录项目做多了之后我发现一个简单但很有价值的习惯在每个项目里建立一张ECO cell选型表把常用的buffer和inverter记录下来包括cell名、面积、驱动强度、适合的场景。有了这张表每次修复就不用临时去翻library。举一个我常用的选型表示例Cell面积驱动我通常用在什么场景INV_X1小弱antenna打断、hold微调INV_X2中中打断长net、轻量修复BUF_X2中中max_cap轻中度修复BUF_X4大强max_cap重度修复、长net分段另一个习惯是记录插入日志。在脚本里把每次ecoAddRepeater的net名、cell名、坐标、插入时间写到一个日志文件里。这样做的好处是如果后续需要回退ECO或者要对比不同修复方案的效果你随时能查到自己当时做了什么而不是靠脑子回忆。配合Innovus的ECO history机制可以很方便地回到修复前的状态重新尝试。写得再多不如自己动手跑一遍ecoAddRepeater这个命令单看语法十分钟就能学会但真正用好在项目里解决问题还是需要亲手踩几次坑。我自己的体会是第一次在GUI里插buffer时一脸懵不知道坐标怎么给、连完线怎么看等到第二个项目时已经能闭着眼写批量脚本了到第三个项目我已经能给team里的新人讲清楚为什么修antenna要优先选inverter而不是buffer。工具本身不复杂复杂的是你对时序、物理实现和工艺的理解。把这些理解转化成一条条合理的ECO命令才是后端工程师真正的价值所在。上面这些经验来自我带过的多个项目也踩过不少坑希望能让你少走几步弯路。
返回列表