ARTICLE DETAIL

资讯详情

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

数字后端postRoute冗余hold buffer自动清理:Innovus实战流程

数字后端postRoute冗余hold buffer自动清理:Innovus实战流程 后端走到postRoute最烦的事情之一就是hold buffer越修越多。明明时序已经signoff过好几轮ECO两三次之后版图里到处是当年修hold留下的buffer面积吃了不少局部congestion反而更紧。有一次我图省事把所有名字里带hold的buffer一股脑删掉结果hold timing从-0.5ns直接劣化到-2ns那个月基本都在跟ECO死磕。后来我总结了一套在Innovus里做postRoute冗余hold buffer自动清理的流程从扫描候选到删除、重绕线、重新收敛一次跑下来控制在5分钟以内。这篇文章就把这套流程和判断逻辑完整写出来顺带把PHC识别技巧一起讲清楚。如果你正在做数字后端signoff收敛或者是flow owner被冗余cell搞得头大这篇应该能帮你省下好几个下午。1. 冗余hold buffer是怎么攒出来的三个典型来源和一个反面教材1.1 来源一多轮ECO叠加旧buffer没地方去项目走到postRoute以后很少有人能一把过。修完setup修hold修完hold发现setup又崩了于是再修setup。每一次optDesign -postRoute -hold在插入新buffer的时候都不会主动去评估“这个位置已经有一个老buffer能不能复用”而是优先在当前时序约束下找一条最稳的路径去插。结果就是老buffer留在原地新buffer插在旁边两个甚至三个buffer驱动同一个信号逻辑上完全等价——但物理上面积double。我见过最夸张的一次一个模块的hold buffer数量占了标准单元总数的3.5%实际真正有负载的不到一半。1.2 来源二多corner/多模式修复互相覆盖现在项目基本都是Multi-Corner Multi-ModeMCMM收敛。ss corner下hold比较差工具会插入一批bufferff corner下setup比较差工具又会做另一轮修复。问题在于同一个DB里承载了多个corner的修复结果工具不会自动做“跨corner去重”。尤其是UPF里定义了多个电压域跨domain路径上的hold buffer经常被重复插。这个现象在floorplan比较碎、电压域划分多的项目里特别明显。1.3 来源三路径结构被手动改过CTS之后做过useful skew、retiming、ecoRoute手动调整或者为了修EM/IR drop调整了某些金属层走线原路径上由optDesign插入的hold buffer就可能变成“历史遗留”——输入还在信号net上输出却已经不再驱动任何时序单元。这种最坑因为单看名字和属性完全看不出问题必须做结构检查才能发现。1.4 反面教材不判负载直接全删hold直接崩盘回到开头说的那次事故。当时我写了个脚本把所有名字带hold的buffer全选出来然后直接delete_eco_cell。删完以后兴冲冲跑hold report结果本来已经收敛的hold路径一下子冒出来几十条大的violation。原因很简单一部分buffer在逻辑上虽然是hold修复单元但同一时刻还在承担“驱动下一级logic”的角色把它们删掉等于把信号通路剪断了。所以“名字像hold buffer”和“真的可以删”是两码事。判断标准只有一个删除之后原有逻辑功能不被改变时序目标仍然满足。2. 清理之前先划线哪些buffer碰都不能碰不管脚本多好用动手前一定要先划红线。下面四类是我踩过坑之后总结出来的“禁删区”。2.1 时钟树上的buffer不是hold buffer很多buffer名字里带“buf”或“clk”如果只按“名字含hold”匹配不容易误伤。但项目多、命名不规范的时候会有例外。判定方法很简单查一下这个buffer的输出net是不是clock net。如果是直接跳过——删掉时钟buffer导致时钟树断裂整个模块都要重跑CTS那是灾难级的返工。2.2 dont_touch标记的修复单元Innovus在做multi-corner hold修复时往往会给插入的hold buffer加dont_touch保护防止其他优化步骤把它当作普通buffer挪走。清理脚本如果不管dont_touch属性强行删除后续重跑optDesign -postRoute -hold时工具会重新插入等于白删。而且由于插入位置可能变化还会带来额外的timing扰动。所以正式删除之前必须把dont_touch检查放在最前面。2.3 有真实负载的buffer降级而不是删除如果你查下来发现某个“疑似hold buffer”的输出实际驱动了一颗寄存器或组合逻辑门它就不属于冗余buffer。这类buffer正确的处理方式是“降级”downsize。比如原来是驱动12个负载的强驱动buffer实际只需要驱动2个负载可以考虑换成尺寸更小的cell面积自然省下来。降级操作要配合ecoRoute重新绕线跑完timing确认没有引入新的违例。2.4 动手前的一张checklist每次清理之前我都会人工确认一遍下面这张表避免脚本误伤检查项判定方式是否可删输出net fanout0dbGet查pin.net的pin数可删输出只接1个buffer且该buffer输出真实负载逐级trace可删从最前面那级删起输出net是clock net查isClock属性不可删dont_touch属性存在查instance属性不可删输出驱动register/combo查询输出pin的load不可删但可降级scan/DFT路径上的修复buffer查scan chain关系需单独确认这张表看着基础但大部分误删都是因为跳过了其中一行。3. 5分钟主流程候选收集、冗余判定、删除、重绕线与重收敛这一章直接给流程。我这里用的命令基于Innovus的dbGet风格shell接口大部分版本都能用。如果你的flow里做了命令封装按实际环境调整即可。3.1 第一步候选收集先按名字把疑似hold buffer捞出来。不同项目命名习惯不一样常见的有hold_*、*_hold_*、eco_hold_*、hld_*。建议先看一遍再决定用哪个pattern# 列出所有名字里带hold的instance dbGet top.insts.name *hold* -p如果公司flow里已经统一了命名规则直接用项目规定的pattern。如果没统一宁可在第一步多捞一些后面判定逻辑会自动过滤。3.2 第二步冗余判定这一步是整个脚本的核心。对每个候选instance做三件事第一看输出net的fanout。fanout0的直接进删除列表。第二fanout1且负载是另一个buffer/inverter的继续往下一级看。如果下一级最终驱动的是时序单元或组合逻辑就把最前面那级也就是当前这个fanout只有1的buffer放进删除列表因为它只是串联传递信号删掉之后把下一级buffer的输入直接接到source net即可等效。第三fanout1且负载都是真实单元的不做删除只记录到“降级候选”列表。用Tcl实现大概是这个思路set del_list {} set downsize_list {} foreach inst $cand_insts { # 跳过dont_touch if {[dbGet [dbGet top.insts.name $inst].dont_touch -p] 1} { continue } # 取出输出pin和输出net set out_pin [dbGet [dbGet top.insts.name $inst].instTerms.pin.direction -v OUTPUT -p] set out_net [dbGet $out_pin.net] # 跳过clock net if {[dbGet $out_net.isClock -p]} { continue } set fanout [dbGet [dbGet $out_net.instTerms.pin -p].count] if {$fanout 0} { lappend del_list $inst } elseif {$fanout 1} { set load_inst [dbGet [dbGet $out_net.instTerms.pin.direction -v INPUT -p].inst.name -p] # 如果唯一的负载也是buffer/inverter继续往深处看 if {[dbGet [dbGet top.insts.name $load_inst].cell.type -p] in {buffer inverter}} { lappend del_list $inst } else { lappend downsize_list $inst } } else { lappend downsize_list $inst } }第一次跑的时候建议先不要直接执行删除先看del_list和downsize_list两个列表人工抽查几个再放行。等验证过没问题再整段放到flow里自动化。3.3 第三步删除删除命令我推荐用delete_eco_cell它在Encounter时代就有了Innovus一直向下兼容对ECO buffer这类单元的删除很友好delete_eco_cell $del_list如果遇到两个buffer完全串联的情况也可以考虑ecoDeleteRepeater -net netName它会自动帮你把串联的repeater删掉并把网表重新接好。不过这个命令对net的拓扑有要求如果报错说“not a repeater chain”直接用delete_eco_cell配合后面的ecoRoute更省事。提示执行删除之前务必先跑saveDesign clean_hold_before_eco。这行命令能让你在误删之后三分钟内回到现场别省这一步。3.4 第四步ecoRoute与hold重收敛删除只是第一步删完之后网表结构和绕线拓扑都变了必须重新跑绕线和时序优化ecoRoute -nets [get_db nets -if {.num_terms 0} .name] optDesign -postRoute -hold有些人图快只跑ecoRoute不跑optDesign结果hold一下冒出来一堆violation——原因很简单删buffer改变了RC和到达时间原先处于临界状态的其他路径会因此劣化。所以optDesign -postRoute -hold这一步不能省这也是“5分钟”里最耗时的一步但绝对值得。3.5 完整脚本把上面拼起来一个可直接用的清理脚本大概长这样saveDesign clean_hold_before_eco set cand_insts [dbGet top.insts.name *hold* -p] set del_list {} set downsize_list {} foreach inst $cand_insts { if {[dbGet [dbGet top.insts.name $inst].dont_touch -p] 1} { continue } set out_pin [dbGet [dbGet top.insts.name $inst].instTerms.pin.direction -v OUTPUT -p] set out_net [dbGet $out_pin.net] if {[dbGet $out_net.isClock -p]} { continue } set fanout [dbGet [dbGet $out_net.instTerms.pin -p].count] if {$fanout 0} { lappend del_list $inst } elseif {$fanout 1} { set load_inst [dbGet [dbGet $out_net.instTerms.pin.direction -v INPUT -p].inst.name -p] if {[dbGet [dbGet top.insts.name $load_inst].cell.type -p] in {buffer inverter}} { lappend del_list $inst } else { lappend downsize_list $inst } } else { lappend downsize_list $inst } } puts Delete list: $del_list puts Downsize list: $downsize_list delete_eco_cell $del_list ecoRoute -nets [get_db nets -if {.num_terms 0} .name] optDesign -postRoute -hold report_timing -hold -max_paths 100 summaryReport -outfile ./hold_clean_report.txt这里再啰嗦一句不同版本对属性的字段名有差异跑之前务必在交互shell里把dont_touch、isClock这几个字段实际打印一次确认无误再批量执行。4. PHC识别技巧让工具替你指认真正的hold修复单元名字匹配和结构判断属于“从外往里猜”还有一种更直接的办法——让Innovus自己告诉你哪些单元是hold修复单元。这就是PHC识别。4.1 PHC到底是什么在Innovus用户圈里PHC这个叫法并不完全统一有的叫Physical Hold Cell有的叫Physical Hold Candidate但思路是一回事postRoute阶段做hold优化时工具会基于物理信息单元距离、负载电容、时序裕量生成并插入buffer这些buffer在数据库里会带上一组与physical hold repair相关的标记属性。它不是GUI里一个显眼的按钮而是一套可供dbGet/get_db查询的数据标记。只要我们能查到这套属性就不需要靠猜名字来捞候选了。4.2 通过dbGet属性识别PHC不同版本字段名有差异但思路一致找instance上跟hold修复相关的标志位。你可以这样探索# 查看某个buffer的所有属性带hold关键字的字段留意一下 dbGet [dbGet top.insts.name *hold*].?? *hold*实际项目里我常用的一种方式是查询is_hold_fix或者eco相关的字段。如果在交互shell里看到字段名类似is_hold_fix、is_hold_cell、eco_hold_buf都可以用来替代第3章的“名字匹配”步骤# 伪代码按实际版本字段调整 dbGet top.insts.is_hold_fix -p用属性过滤比名字匹配可靠得多因为工具自己打的标记不会因为项目命名不规范而漏网。4.3 通过marker layer与命名规则联合识别除了数据属性Innovus在布局布线上也会给hold修复相关单元打marker。打开GUI的Markers面板过滤HOLD关键字工具会直接把相关单元在版图里高亮。这种视觉化方式适合人工抽查脚本里也可以直接读marker表格dbGet top.markers.groupName *HOLD*我建议的联合使用方式是这样的先用marker高亮圈定“工具认为跟hold修复有关”的区域再用属性清理脚本精确删除。这样既不会漏也不会误伤。三种识别方式的对比识别方式优点缺点适用场景名字匹配简单直接脚本容易写命名不规范会漏项目命名统一时属性匹配PHC准确率高工具自带标记字段名随版本变化正式清理脚本marker高亮可视化直观适合抽查不适合全自动批量人工复核、问题定位4.4 衍生技巧GUI里快速选中biasnw这样的PG term既然聊到选中和查看单元顺便说一个绕不开的小操作GUI里选中某个标准单元的power/ground terminal。后端工程师在查IR drop、UPF电压域、PG连接的时候经常需要在版图里选中某个名字很长的PG term比如biasnw。如果手动画框找效率很低。正确姿势是直接在Innovus command line窗口里用命令选中# 选中整个标准单元名字为biasnw dbSetSelected [dbGet top.insts.name biasnw -p] # 选中该单元上名为BIASNW的PG pin / instTerm dbSetSelected [dbGet [dbGet top.insts.name biasnw].instTerms.name BIASNW -p]这里注意一点top.insts.name匹配的是instance自己的名字top.insts.cell.name匹配的是库里的cell名字。如果biasnw是库单元名应该这样选dbSetSelected [dbGet top.insts.cell.name biasnw -p]两种都试一下选中后在GUI里看高亮就知道对不对了。这个方法同样适用于大批量选中比如你要把某个电压域内所有带BIASNW端子的单元全部高亮把dbSetSelected放进foreach循环跑就行。5. 清理之后的验证与三个常见翻车现场脚本跑完不等于完事验证阶段才是真正暴露问题的地方。我整理了三个最常见的翻车现场和处理方法。5.1 翻车现场一清理完hold反而更差了这个我见过很多次包括我自己在内。原因通常是两类。第一误删了有真实负载的buffer。虽然脚本里有fanout判定但如果dont_touch属性没查到、或者某个buffer驱动的负载比较特殊比如macro pin、IO pin、feedback路径就可能漏判。遇到这种情况最快的处理方式是回滚到删除前的checkpoint。所以我每次清理前都会强制saveDesign一个独立文件删完不对就立刻恢复。第二删完没有跑optDesign -postRoute -hold或者跑了但hold margin本来就紧删除后RC变化让某些路径从正裕量变成负裕量。这种只能重新优化没有捷径。5.2 翻车现场二删完出现short和congestion删除buffer之后它的输入net上往往还留着一段走线如果只做ecoRoute而没有做局部清理这些stub wire在某些情况下会跟其他信号产生short或者让congestion局部变差。处理方法是在ecoRoute之后加一步针对受影响net的局部修线ecoRoute -nets affected_net_list -fixDrc recheck_eco -drc如果工具报告short优先看是不是stub wire引起的用GUI沿着出错的net走一遍比在report里瞎猜效率高得多。5.3 翻车现场三物理验证报dangling pin删掉ECO buffer后原本属于这个cell的PG hookup金属线可能变成dangling wire/pinDRC和LVS阶段就会冒出来。后端常用做法是在物理验证之前统一做一次dangling wire清理dbDeleteAllDanglingWires注意这个操作要在所有逻辑ECO都结束之后再做否则后面再插cell可能又需要这些PG线。顺序不要搞反。5.4 验证命令清单跑完清理和修线之后我会用下面这组命令做最终确认report_timing -hold -max_paths 1000 report_timing -setup -max_paths 1000 checkDesign -all summaryReport -outfile ./post_clean_report.txtsummaryReport里的标准单元面积和单元密度就是这次清理的直接收益。前后对比一次如果面积省了、congestion松了、hold没崩那这次的清理就是成功的。这套流程我前后跑了两个项目基本稳定。要说经验最核心的一条是脚本只占30%剩下70%是判定标准和边界确认。别看到名字像hold buffer就删尤其跨corner、跨domain的路径先确认负载关系再动手。另一个建议是把它藏进flow里做成可重复执行的cleanup步骤每次signoff之前自动跑一遍省下的时间和面积都比想象中多。最后再提醒一句跑之前记得saveDesign跑完记得对比summaryReport这两步能省掉你后面所有的后悔药。
返回列表