ARTICLE DETAIL

资讯详情

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

DRV残留477条net?警惕don‘t touch hierarchy锁死后端优化

DRV残留477条net?警惕don‘t touch hierarchy锁死后端优化 place_opt跑完log里DRV summary一片红477条net的max_transition/max_capacitance违规挂在报表上report_timing一拉全是high fanout net和长距离跨模块走线。第一反应是congestion又炸了结果打开floorplan一看物理分布并不算极端工具却一个buffer都没插进去。这种“能修却修不动”的DRV十有八九是约束层面出了问题而最常见的元凶就是hierarchy上的don‘t touch属性。这篇东西写给正在被后端收敛问题折磨的工程师尤其是刚接触数字IC后端、对约束传播机制还没形成条件反射的朋友。我会从477条net这个具体现象入手把don’t touch hierarchy影响DRV修复的底层逻辑讲透再给出一套可以直接用的排查和修复流程最后分享一些我在项目里积累的预防手段。1. 477条net的DRV报告到底暴露了什么信号1.1 一个“能修却修不了”的DRV现象先还原一下现场。某块die size不算紧张的block标准单元利用率大概70%左右place_opt跑完后打开报告Report : qor Design : block_top Version: XXXX Date : ... DRV Summary -------------------------------------- | Type | Endpoint| Nets | -------------------------------------- | max_transition | 312 | 477 | | max_capacitance | 89 | 156 | --------------------------------------477条net的transition违规312个endpoint这个量级放到一个几十万instance的block里不是小数目。正常情况下place_opt之后DRV应该被清理到接近零剩下零星几条多半是physical-only cell、macro pin或者特殊走线造成的。但这次的情况是面积不紧张、utilization不高、congestion map也不红工具却大面积放弃治疗。这时候如果只盯着DRV数字去调size_cell、插buffer基本是徒劳。因为工具在优化阶段已经尝试过修复但被某种约束按住了手脚它比你先知道这些net“动不得”。问题的根源不在物理实现而在逻辑约束层。1.2 为什么DRV残留比时序违例更值得警惕很多经验不足的工程师会把DRV当成一种“时序违例的下位替代”觉得transition慢一点、cap大一点只要setup/hold能过就行。这是非常危险的误解。transition违规直接改变cell的delay计算基准。一个驱动cell的输出transition如果超过库文件的限制delay查表会进入外推区时序计算变得不可信更关键的是它会引发两个实际问题串扰噪声风险transition越慢信号在传输线上的“暴露时间”越长耦合电容对相邻net的影响越容易被放大可能直接把一个不该翻转的信号打翻。功耗和EM风险过大的capacitance意味着驱动cell需要输出更多瞬时电流来充放电长时间高负荷会加速老化严重时直接触发EM电迁移signoff失败。所以DRV问题不是“难看”的问题是真正会影响芯片能不能正常工作、能不能通过可靠性signoff的问题。477条net的DRV残留意味着芯片上存在477个潜在的可靠性风险点这个数量绝对不值得用“后期再看”的心态去忽略。DRV还有一个特点错过place_opt这个修复窗口后续阶段修复成本会指数上升。CTS阶段修DRV会影响时钟树结构route阶段修DRV要处理物理绕线到了ECO阶段更是要手动一颗一颗cell去换所以必须在这个阶段就搞清楚工具为什么不动手。2. don’t touch hierarchy是如何“锁死”优化引擎的2.1 先分清cell don’t touch和net don’t touch要理解477条net为什么修不动先要分清don’t touch属性的两种作用对象。Cell don’t touchinstance属性告诉工具这个instance不能被替换、不能被删除、不能改变pin连接关系。优化时工具做完timing分析发现某个buffer可以换大一号来修transition一看属性动不了于是跳过。Net don’t touchnet属性告诉工具这条net的拓扑结构不能变不能在上面插buffer不能改变走线连接关系。DRV修复的主要手段就是“改变net拓扑”——插buffer或者换驱动cell。net don’t touch直接把这两种手段都锁死了。即便工具发现了一条net存在transition违例也知道加个buffer就能解决但只要net属性带don’t touch它就会放弃并把这条net留在DRV报告里。而hierarchy上的don’t touch通常是同时作用于hierarchy内部所有instance和所有net的。一条命令下去一个模块内部全部逻辑都被“冷冻”了从工具视角看这里面的任何timing问题、DRV问题它都无权处理。这就能解释一个现象DRV分布往往不是随机的而是成片出现在某个子模块内部——因为那恰恰是don’t touch约束覆盖的范围。2.2 优化引擎在DRV修复时对don’t touch的判断机制具体到EDA工具Innovus或者ICC2都适用的处理逻辑DRV修复大概有三板斧按优先级排列换驱动cellsize up把驱动逻辑门的驱动强度调高比如从BUFX2换成BUFX4。插入buffer在net中间插一级buffer把一段长走线拆成两段每段分别承担负载。逻辑复制/拆分高扇出对于驱动能力不够的大扇出net把驱动逻辑复制一份分别驱动部分负载。这三板斧在don’t touch面前的处境完全不同size up要替换cell属于改变instance性质被cell don’t touch禁止插buffer要改变net拓扑被net don’t touch禁止逻辑复制要动driver的逻辑结构被hierarchy don’t touch禁止。一旦hierarchy上设置了don’t touch这三条优化路径全部被堵死。工具面对一个fanout为80、走线跨越整个block的net明明知道修法代码逻辑层面却被告知“这里不是你的地盘”只能跳过并上报DRV。477条net就是这么攒出来的。2.3 set_don’t touch的传播一个约束如何影响一片逻辑再深入一步don’t touch的传播机制比很多人想象的要“野”得多。看这段常见约束set_dont_touch [get_cells u_sub_module]这条约束只作用于u_sub_module这个instance本身工具不会把它自动传播到u_sub_module内部的每一颗cell。但它已经足够危险如果u_sub_module是一个关键子模块的顶层instance任何需要替换或优化它的操作都会受阻。更常见也更隐蔽的写法是set_dont_touch [get_cells u_sub_module/*] -propagate-propagate会沿hierarchy向下传播把u_sub_module内部所有instance、所有net全部打上don’t touch标记。这种约束通常是为了保护一些手工优化过的关键路径比如memory周边逻辑、PLL相关路径但传播范围往往超过预期。还有一种情况后端工程师在CTS阶段为了固定时钟树结构会对clock tree cell设置don’t touch结果脚本里用了get_cells -hierarchical配合某种过滤条件把数据路径的一部分逻辑也圈进去了。这种错误很隐蔽因为CTS阶段不会立刻暴露问题等到place_opt的DRV修复阶段工具才发现数据路径上有一大片区域不能动只能让DRV继续挂着。下面用一个简化的例子说明传播逻辑假设结构 block_top ├── u_ctrl (hier) │ ├── u_reg_i │ ├── u_reg_q │ └── u_and_gate └── u_data (hier) ├── u_buf_1 └── u_buf_2 执行 set_dont_touch [get_cells block_top/u_ctrl/*] -propagate 效果 block_top/u_ctrl/u_reg_i - dont_touch block_top/u_ctrl/u_reg_q - dont_touch block_top/u_ctrl/u_and_gate - dont_touch 这些cell之间的所有net - dont_touch 但block_top/u_data/* - 不受影响看起来边界很清晰但实际工程中hierarchy边界往往不是按功能清晰划分的。一个子模块里可能混合着需要保护的关键逻辑和可以优化的普通逻辑一刀切下去问题就埋下了。3. 现场排查从477条net到根因的完整链路3.1 第一步DRV分布与物理位置分析遇到DRV残留第一件事不是去翻约束先把DRV的物理分布和逻辑分布拉出来看。打开Innovus GUI高亮所有DRV违规的net观察它们的分布规律。常见的几种分布模式随机零散分布多半是congestion或者个别特殊net导致工具能力不足不是被约束锁死的。集中在某个hierarchy内部高度怀疑don’t touch进入属性查询阶段。集中在某个区域但跨多个hierarchy可能是floorplan规划问题比如memory之间狭窄通道、power stripe阻挡物理因素为主。477条net的违规绝大多数落在u_sub_module内部而且这个模块是作为第三方IP集成进来的边界清晰这基本就是“被约束保护”的典型特征。输出一下DRV net的hierarchy归属可以直接用Tcl遍历set drv_nets [get_nets -hierarchical -filter max_transition_slack 0] foreach net $drv_nets { set owner [get_attribute $net parent] puts [get_full_name $net] owner: [get_full_name $owner] }把结果按owner分组统计就能看到违规net在哪些hierarchy下聚集。如果出现“一个子模块占了90%的DRV net”这种比例就不用再看物理分析了直接查约束。3.2 第二步属性查询确认don’t touch标志锁定了hierarchy范围之后用属性查询确认don’t touch到底加在谁身上# 检查hierarchy上的instance属性 get_property [get_cells u_sub_module] dont_touch # 或者用report_attribute report_attribute [get_cells u_sub_module] -app -dont_touch如果是net级别的# 检查某条net的dont_touch属性 get_property [get_nets u_sub_module/data_net] dont_touch工具里还有一个更直接的命令——report_dont_touch可以列出设计里所有带don’t touch属性的objectreport_dont_touch -verbose查到结果基本分两种instance和net上都有don’t touch说明是set_dont_touch [get_cells xxx] -propagate这种带传播的写法只有net上有instance上没有说明约束加的是某条具体net或者用了set_dont_touch [get_nets xxx]某个hierarchy的边界pin上有don’t touch内部没有说明是从上级模块“继承”下来的——父模块被设置了don’t touch子hierarchy边界被自动打标。第三种情况最容易漏掉。只查子模块内部属性会发现干干净净但父模块一个don’t touch照样让子模块内部逻辑全部锁死。3.3 第三步向上溯源找出“谁给了don’t touch”确认don’t touch存在后下一步是找出约束来源。这一步要翻三类东西SDC约束文件grep -rn set_dont_touch看约束是什么阶段加的加在什么对象上。特别要注意有没有-propagate和-hierarchical组合。CTS脚本时钟树综合阶段为了保持时钟树结构经常会对clock cell设置don’t touch。如果脚本里的get_cells过滤条件没写好很容易误伤数据路径。UPF/低功耗约束power domain边界上的isolation cell、level shifter经常被设置don’t touch但如果约束里的scope写大了会把整个power domain的逻辑都罩进去。这类问题的根因往往不是某个人的“恶意操作”而是多个人、多个阶段约束叠加的结果。比如前端工程师为了稳定仿真对某个模块加了don’t touch后端CTS工程师又为了保住时钟树结构对子模块内部逻辑加了一层两段约束叠加覆盖范围就远超最初想象了。在实际项目里我还见过一种更隐蔽的情况约束写在了一个被source的公共脚本里而这个脚本会被多个block共用。某个block的flow里设置了某个变量导致公共脚本里的一段set_dont_touch [get_cells $sub_block/*] -propagate被执行了但写公共脚本的人根本不知道这个变量在这个block里会被赋值为关键子模块的名字。这种“跨脚本、跨配置”的约束污染排查起来极其费时间但一旦有了怀疑方向用report_dont_touch就能快速锁定。4. 修复手段与取舍不是所有don’t touch都能去掉4.1 如果don’t touch是“加错了”确认根因后最简单的修复就是去掉错误约束重新跑一遍优化。假设查出来是公共脚本误伤可以做局部解除# 解除整个hierarchy的dont_touch谨慎使用 remove_dont_touch [get_cells u_sub_module/*] -propagate # 或者只解除特定net的 remove_dont_touch [get_nets u_sub_module/data_net]然后跑增量优化set_app_options -name opt.common.enable_si_aware_drv_fix -value true opt_design -drv_fix_only-drv_fix_only这个选项很实用它告诉工具“别去折腾时序先把DRV给我修了”运行时间比全量re-opt短很多适合这种“只差最后一脚”的场景。但这里有个非常关键的经验不要全局remove_dont_touch。设计里总有合理的保护需求比如某些hard macro周边、某些手动balanced的clock tree结构。全局解除等于把之前的保护全部推倒可能引发新的时序问题而且这种问题往往比DRV更难查。正确做法是精确解除问题hierarchy范围内、确认无保护价值的don’t touch修完DRV之后把这一段约束单独收进一个exception文件方便review。4.2 如果don’t touch是“必须保留的”更麻烦的情况是don’t touch被明确要求保留不能解除。比如IP vendor明确规定集成时不能改动内部逻辑或者为了时序收敛前后端协商好某个模块必须手工维护。这时候就不能指望工具自动修了要人工介入思路是在don’t touch边界外做文章在hierarchy外部插入buffer检查这个模块的input pin上是否存在transition违例。如果有可以在模块外部靠近input pin的位置插一个buffer先改善输入transition。这不需要动模块内部任何东西。在hierarchy外部调整负载如果module的output net驱动了大扇出可以在output pin外部做buffer tree把扇出拆开。这一样不违反内部don’t touch。局部逻辑复制如果不能变内部结构可以在外部复制一份驱动逻辑让内部net的扇出减少。这属于“绕道走”。举一个实际案例一个加密模块IP vendor强制要求整个模块don’t touch。place_opt后发现模块输出到外部的高扇出net出现DRV因为IP模块自身输出驱动不强外面又接了几十个寄存器。解法是在IP output pin外挂两级buffer tree原本一条net带30个负载变成一级buffer带4个、下一级分别带8个。模块内部一根线都没动DRV清零。4.3 物理层面的一些“妥协操作”有时候约束既不能解除、逻辑上又无法绕开还能用手工物理ECO来兜底手工替换cell如果DRV集中在某个可访问区域的驱动cell可以在GUI里手动把BUFX2换成BUFX4。这个操作不依赖工具优化引擎不会受don’t touch约束影响——don’t touch是约束工具的行为不是约束人的行为。调整cell位置优化绕线某些DRV其实不是驱动能力不够是走线绕太远了。手动把负载cell往驱动cell附近挪一下缩短走线长度cap就降下来了。局部congestion疏通如果net的绕线空间被堵死只能绕远路手动挪开附近几个不关键的cell给这条net腾出通道。不过手动ECO的颗粒度很小477条net如果都要手工修工作量是灾难级的。所以这类方法只适合“剩下个位数net、无法自动化处理”的收尾阶段。5. 防止477条net这类问题复现的流程建议5.1 在SDC/约束review阶段就识别don’t touch风险根治永远比补救高效。项目上我坚持一个做法每次跑flow之前先report_dont_touch把结果存成日志随QoR报告一起输出。这样DRV一出问题第一件事就是对比两份报告这次的don’t touch覆盖范围跟上次比有没有变化这个习惯曾经帮我快速定位过一次跨团队协作问题——前端团队为了仿真debug方便悄悄给一个子模块加了set_dont_touch -propagate没通知后端。如果只看DRV分布很难第一时间联想到约束变更但对比don’t touch报告差异立刻暴露。另外建议在SDC review checklist里加一条所有set_dont_touch必须注明理由和时间。看起来是流程上的小事实际排查时能省掉大量“这条约束是干嘛的”这类考古工作。5.2 把DRV修复能力做进flow的自动化检查这里的“DRV修复能力”不是指修DRV而是指检查“DRV是否存在但工具不修”的状态。一个简单可落地的Tcl脚本逻辑# place_opt之后运行 set drv_nets [get_nets -hierarchical -filter is_hierarchical true max_transition_slack 0] set blocked_nets 0 foreach net $drv_nets { set dt [get_property $net dont_touch] if {$dt 1} { incr blocked_nets puts DRV on dont_touch net: [get_full_name $net] } } puts Total DRV nets: [sizeof_collection $drv_nets] puts Blocked by dont_touch: $blocked_nets把这段脚本挂到flow的post-place_opt阶段一旦出现“DRV net里带don’t touch属性”的情况立刻告警。这种自动检查比人工翻报告高效得多能让问题暴露在最早的时间点。5.3 一个实用的regression清单最后分享一份我项目里实际在用的don’t touch相关检查清单每次flow run都要过一遍当前设计里总共有多少个object带don’t touch属性分布在哪几个hierarchy这些don’t touch中哪些是SDC明确要求的哪些是脚本隐式加的哪些是传播出来的place_opt后DRV net里有多少条落在don’t touch区域内比例是否超过阈值比如10%新增的don’t touch约束是否经过review确认覆盖范围没有超出预期不同阶段前仿真、综合、place、CTS的don’t touch集合是否有diff检查第5条特别建议做成自动化的。综合阶段加的don’t touch可能因为设计更新已经失效或者被一条新的set_dont_touch覆盖了但这些变化往往不会主动通知后端。定期做一次“don’t touch集合diff”相当于给约束环境做定期体检。回到开头那个477条net的场景我当时的操作链路是这样的先确认DRV集中在某个hierarchy然后report_dont_touch发现整个模块都被打标再翻SDC确认是集成脚本里残留的set_dont_touch -propagate跟模块团队确认后解除约束跑了十几分钟opt_design -drv_fix_onlyDRV从477清零。整个过程真正花时间的不是修复而是排查——如果一开始就建立起don’t touch检查机制可能连排查都省了。做数字IC后端最怕的不是物理上的难而是约束层面的“暗锁”——工具明明有能力修却被告知不能碰。搞清楚don’t touch hierarchy的传播机制和影响边界你不仅能快速解掉477条net这类问题还能在设计流程里提前规避它。下次再看到一大片DRV残留先别抱怨工具花十分钟查一下don’t touch说不定答案就在那里。
返回列表