
低功耗芯片的后端实现里最让人头疼的往往不是时序收敛而是这种查半天才发现的隐形漏电。我最近就栽在这么一条路径上一根挂在 iso 输入端口前面的 buffer被一条 set_dont_touch 钉死硬生生把隔离单元本该挡住的漏电路径给放开了。先说清楚这里的 iso 不是镜像文件是 low-power 设计里的 isolation cell隔离单元。这条路径的问题非常有代表性这篇就把来龙去脉讲透iso 的 input 端为什么不能插 bufferdont_touch 又是怎么变成漏电帮凶的最后给出完整排查链路和可直接抄的约束纪律。1. 现象复盘常开域的静态漏电为什么凭空多了三成1.1 项目背景三个电源域和一个可关断外设当时在做一个 MCU 级别的 SoC规模不大但电源域划分很典型一个常开控制域 PD_AlwaysOn一个数字核心域 PD_Core还有一个传感器接口域 PD_Sensor。PD_Sensor 负责采集外部传感器数据通过一组并行 status 信号和常开域交互平时大部分时间都处于 power down 状态只有在需要采样时才上电。常开域里只剩下 RTC、唤醒逻辑、一小部分 IO 控制逻辑功耗预算卡得很死静态漏电要控制在 2uA 以内。UPF 里的电源意图也写得比较常规。对 PD_Sensor 到 PD_AlwaysOn 的输入端口设置了 isolation cellclamp 到 0也就是说 PD_Sensor 断电之后进入常开域的 sensor 状态信号会被隔离单元钳死不能把 X 传进常开域内部逻辑。这套策略跑了很多轮都没出问题直到某次功耗回归数字对不上了。1.2 功耗回归异常数字对不上那次回归用的是同一套库、同一份 UPF、同一个工艺角唯一的变化是网表里多了几个综合阶段为了修 max_transition 插进去的 buffer。跑完 PrimeTime 功耗分析报告显示常开域静态漏电 2.6uA比预算多了 0.6uA。0.6uA 单看不算大但在常开域这种所有 cell 都要精打细算的场景里已经属于必须查清楚的级别了。刚开始我以为又是多阈值电压替换没做干净。翻了 VT 分配报告常开域里确实还有少量 SVT 单元但这些单元都在带 dont_touch 的 retention 逻辑里本来就有豁免之前也是这么定的不该突然多出来 0.6uA。接着怀疑 power switch 没有关干净把 PD_Sensor 的 switch 控制逻辑查了一遍没发现异常。后来逐个 cell 看 leakage 明细发现 PD_AlwaysOn 域里有两个 buffer 的 State-Dependent Leakage 标注特别扎眼。1.3 最初的排查方向全都不对这两个 buffer 在库里标称漏电每个只有几十纳安但功耗报表里给出来的却逼近 400nW。同一个库、同一个 VT、同一个尺寸居然差出一个数量级这基本只有一种解释cell 输入端没有停在确定的 0/1 电平上。我顺着网表往上追发现这两个 buffer 都挂在 PD_Sensor 过来的 input 端口上处在端口的 isolation cell 前面。到这里问题结构才清晰起来这两个 buffer 是常开域的 buffer输入却连着可关断域的端口信号。PD_Sensor 一断电端口变 Zbuffer 输入浮空于是 VDD 到 VSS 之间出现一条直流通路。更麻烦的是网表里这两个 buffer 都被打了 set_dont_touch后续所有能救它的优化手段全被锁死。这也就是标题里说的一条 dont_touch 背后的漏电。2. 隔离单元的真正职责input 端为什么天然不该放 buffer2.1 iso 到底在钳什么isolation cell 的工作核心用一句话说就是在电源域边界上把从可关断域漂过来的不定态钳到固定电平。可关断域断电之后它的输出端口不会乖乖停在 0 或 1而是变成高阻 Z甚至因为漏电停在中间电平。这个中间电平进入常开域内部逻辑后会导致后面一连串 cell 的输入都处于半开半关状态每一个都可能产生贯通电流。iso cell 内部实现通常是 AND 门或者 OR 门。clamp 到 0 用 AND 门clamp 到 1 用 OR 门控制端接 iso_en。iso_en 生效时无论数据端是什么状态输出都被钉死在预设值。可以把它理解成一道门禁可关断域断电之后门禁落下不管里面的人是什么状态外面都看不到。2.2 input 端的常规构成信号进来先过隔离对一个接收端来说正确结构非常单纯input pin 直接接到 iso cell 的 data 端iso cell 的输出再接内部逻辑。中间不应该有任何组合逻辑或 buffer。为什么这么严格因为 iso cell 的意义在于把未知挡在边界之外。如果 iso 前面挂了 buffer那这个 buffer 就成了隔离边界之外的不受控单元。发送域断电时buffer 的输入可能浮空而 buffer 本身还接着常开域的电源于是它就成了一个漏电点。iso cell 能钳住的是它自己的输出钳不住它前面 buffer 的输入悬空问题。我在实际项目中见过的正确做法基本都遵循同一套标准隔离边界上的输入网络要么直接连线到 iso要么只允许存在库特性明确支持浮空输入的专用单元。普通 buffer 不属于后者。2.3 把 buffer 插在 iso 前面的后果两条漏电路径网表中那个错误的拓扑长这样input pin (from PD_Sensor) - BUF_X2 (PD_AlwaysOn 域) - ISO_AND2_X2 (clamp 0) - 内部 DFFPD_Sensor 断电之后input pin 变成 Zbuffer 输入端悬空。CMOS 工艺下栅极浮空时电压会停在某个中间电平PMOS 和 NMOS 同时部分导通形成从 VDD 到 VSS 的贯通电流。这个电流就是 State-Dependent Leakage 里异常偏大的部分和 cell 标称漏电完全不是一回事。这里其实有两条漏电路径。第一条是 buffer 本身的贯通电流这是最主要的。第二条出现在 iso cell 的输入端在 iso_en 拉高之前iso 的 data 端还短暂地维持着 buffer 输出的中间电平iso 内部也会出现短暂的过渡电流。虽然 iso_en 生效后输出被钳到 0但它输入端的中间电平并不会自动消失。两条路径叠加就是报表里那 0.6uA 的来源。2.4 工具为什么会主动这么插其实工具不背这个锅。综合阶段DC/Genus在做时序优化时input 端口如果存在 max_transition 违反或者 input_delay 设置比较紧工具很自然的处理方式就是在端口后面插一个 buffer 来增强驱动。这个操作本身没有问题因为综合阶段 UPF 里的 isolation cell 通常还没插入工具看不到这个端口将来要被隔离这层信息。后端做 low-power 实现时工具按 UPF 在指定端口位置插入 iso cell但它不会去检查隔离边界外是否已经存在 buffer。如果这个 buffer 没有被设 dont_touch后续的 optimization 还有机会把它挪到 iso 后面甚至直接吸收掉。一旦被 dont_touch 钉住所有可能性都被封死问题就固化成了漏电。buffer 位置断电后结果是否产生漏电工具能否自动修复input pin 到 iso 之间buffer 输入浮空iso 钳制其输出会buffer 贯通电流可删/可挪时能修dont_touch 时不能iso 到内部逻辑之间buffer 输入被 iso 钳在 0/1不会可正常优化无 buffer直接接 iso整个路径都被钳住不会无需修复3. 一条 set_dont_touch 如何把修复路径全部堵死3.1 当时加 dont_touch 的理由看到约束脚本的时候我第一反应是这谁写的。查完 blame 才知道这条 dont_touch 不是针对 input buffer 专门加的而是项目里积累了很多轮的一个批量约束所有以input_buf命名的单元统一 set_dont_touch。原因也算合理。那批input_buf里有相当一部分是跨时钟域异步信号的同步缓冲网络制程越往后越脆弱后端工具在 CTS 或者 optimization 阶段一旦动了这些手工保护的 cell异步路径的时序就可能被悄悄破坏。所以项目组定了规矩凡是手工例化、名字带input_buf的单元一律不优化。问题在于这种批量约束根本没有区分异步保护网络和普通输入驱动 buffer。综合工具自动插出来的那两个 buffer同样被命名成了input_buf*于是自动享受了保护待遇。这是一次典型的约束误伤。3.2 dont_touch 在低功耗实现里挡掉的四件事dont_touch 这个约束在数字后端里几乎是最高优先级它的意思很清楚这个 cell/net 是祖宗工具不许动它。听起来很安全但在低功耗实现里它一次挡住四件该做的事。第一不能删。工具发现这个 buffer 在 iso 前面完全冗余想优化掉做不到。第二不能挪。想把 buffer 从 iso 前面挪到 iso 后面等于是移动了这个 cell 的位置做不到。第三不能合。后端做逻辑优化时想把 buffer 和 iso cell 做合并或吸收做不到。第四不能换 VT。低功耗 flow 里常做的 VT swapping把这个 buffer 从 SVT 换成 HVT 来降漏电同样做不到。这四件事单独拎出来每一项都能救半条命但 dont_touch 把它们全关上了。尤其不能换 VT这条哪怕不解决浮空问题至少能把漏电数值压下去一部分但约束在工具连这个折中方案都不会尝试。3.3 为什么综合与后端工具不会替你纠正这里面有个工具分工的现实问题。综合工具做时序优化时它根本不管 UPF插 buffer 只为了满足 input_delay 和 max_transition。后端工具做 UPF 实现时它按部就班地插 iso cell但也默认 dont_touch 是不可触碰的红线。两个工具各干各的没有一个阶段专门去检查隔离边界外是否躺着受保护的 buffer。更隐蔽的是CLP/MVRC 这类电源意图检查工具虽然能报出隔离网络上存在未覆盖的组合单元但这类告警在项目里经常是 warning 级别。如果 UPF 本身写的没错工具不会因为 buffer 的存在而报 fatal error它只会提示isolated net 前面有不受控逻辑。在项目进度压力下这种 warning 很容易就被放过去了。3.4 UPF 与 dont_touch 的隐性冲突很多人以为 UPF 里写了set_isolation工具就会自动搞定边界上一切可能漏电的东西这是误解。UPF 描述的是应该在哪隔离而不是隔离边界外必须没有 buffer。dont_touch 和 UPF 之间更没有形式化的冲突检测机制它们一个属于 SDC 领域一个属于电源意图领域日常 flow 里根本不会放在一起做交叉检查。所以最阴险的地方就在于所有检查都通过了UPF 正确SDC 合法时序收敛CLP 也只在 warning 级别提了一句。但芯片一旦跑起来常开域就永远多了那 0.6uA 的静态漏电。这就是真实世界里 low-power 问题最难追查的原因它不是一个工具能发现的而是多个流程片段拼出来的。4. 完整排查链路从功耗报表一路查到约束脚本4.1 第一步功耗报表定位异常 cell整个排查过程我建议第一步不要直接扑到 UPF 和 SDC 上而是先让功耗报表自己说话。把 PrimeTime 的 leakage 报告按 cell 逐个 dump 出来按漏电值排序异常 cell 立刻浮出水面。我当时看到的就是这个效果Cell: BUF_X2_A9TP_ALWAYSON Leakage Power: 412.3 nW Location: PD_ALWAYS_ON / u_iso_buf_001 Cell: BUF_X2_A9TP_ALWAYSON Leakage Power: 387.6 nW Location: PD_ALWAYS_ON / u_iso_buf_002同一行库里的同类 buffer正常漏电只有几十纳瓦这几根却到了 400nW 级别说明它们的输入状态不是正常的 0/1而是停在了中间电平。这一步基本就把嫌疑范围锁定在输入浮空这个方向上接下来要做的只是确认浮空点在哪。4.2 第二步网表结构让问题显形拿着这两个 cell 的 instance 名字回到网表往前 trace拓扑结构一目了然。BUF_X2的输入直接连到PD_Sensor域的 output port输出接到ISO_AND2_X2的 data 端。再打开 UPF看到对这两个端口明确设了set_isolationclamp value 0。结构上已经坐实了buffer 站在端口和 iso 之间属于常开域输入却来自可关断域。读 UPF 的时候还注意了一点set_isolation的作用对象是端口和网络它没指定 cell。工具在插入 iso cell 的时候只负责在指定端口后面挂上隔离单元不会因为端口前面多了一个 buffer 而报错。换句话说工具的合规视角里隔离结构是完整的但物理上漏电已经发生。这和工具行为完全吻合。4.3 第三步仿真波形确认浮空输入结构分析是怀疑波形才是实锤。我用 VCS UPF flow 跑了一遍 PD_Sensor 上下电场景在波形里盯着这条路径PD_Sensor 关断瞬间input pin 变成 Zbuffer 输入悬空栅极电压开始漂移输出进入中间电平一段时间后 iso_en 拉高iso 把输出钳到 0但 buffer 输入这个 Z 状态会一直维持到下次上电。波形上还能明显看到 buffer 输出端在钳位生效前有一段 X 窗口那段窗口里 iso 的 data 端就是中间电平iso 内部也在过渡。buffer 输入端的 Z 和中间电平就是那 400nW 漏电的直接来源。这一步把理论上的可能漏电变成了确实漏电。4.4 第四步凶手是最不起眼的 set_dont_touch到这一步问题已经明确但不查清楚为什么工具没修等于白查。回到约束里搜才发现 SDC 里躺着这样一条批量约束set_dont_touch [get_cells -hier -filter name ~ *input_buf*]范围太宽了把所有名字含input_buf的 cell 都保护起来了。报错不会。warning也没有。它只是安安静静地站在那里让后面所有优化手段全部失效。排查结束后我在总结里写了一句功耗异常往往不是某个 cell 的问题而是结构和约束共同作用的结果缺一环都不会漏。5. 修复方案、取舍和同类雷区的规避5.1 三个可用修法及各自代价方案一直接把 buffer 删掉让 input pin 直接接 iso cell。这是最干净的修法删掉之后浮空输入不存在了漏电立刻消失。但需要重新跑一轮从 place 到 route 的流程验证一下删掉 buffer 之后输入端的 max_transition 是否仍然满足。实际项目中这个 buffer 的 fanout 只有两个iso cell 的输入电容也不算大删掉后完全没问题。方案二把 buffer 从 iso 前面挪到 iso 后面。这样 buffer 的输入在断电后会被 iso 钳在 0不再浮空漏电也能消除。这个方案保留了驱动强度适合那些内部负载较大的路径。代价同样是要重新做实现而且时序行为会有一点变化需要重新确认。方案三只把 dont_touch 去掉把决策权交还给工具让它在后续优化里决定是删是挪。这个方案改动最小但风险也最高工具可能因为别的原因重新插一个 buffer也可能在优化之后仍然留下一根受保护的 buffer结果问题没解决。我的建议是如果项目还在实现阶段可以直接走方案一如果已经接近 signoff结构改动越小越好考虑方案二。方案改动范围风险适用阶段删 buffer网表 实现过渡时间需重新验实现阶段buffer 移到 iso 后网表 实现时序需重新确认接近 signoff去 dont_touch 交工具约束结果不可控仅临时验证5.2 input 边界上其他容易埋雷的插入物这根 buffer 只是导火索input 边界上类似的雷还有不少。delay cell 修 hold 是重灾区性质和 buffer 完全一样挂在 iso 前面的 delay cell 一旦在常开域里断电后输入端浮空同样漏电。异步信号的同步 buffer 网络也是高频雷区这些网络常被 dont_touch 保护如果其中某一段恰好跨过了电源域隔离边界问题比普通 buffer 更难查。另外input 端口到 iso 之间的组合逻辑也需要注意比如为了做时钟门控或毛刺过滤插入的 AND/OR 门。它们的输入一旦悬空内部同样存在贯通电流。我这里说的只是常开域里、输入端连着可关断域这一类真正的经验是凡是隔离边界前出现的非常规单元都应该上 review 名单。5.3 流程上的几条约束纪律踩过这次坑之后我给团队立了几条规矩现在项目里一直在用。第一条任何 set_dont_touch 都要和 UPF 隔离网络做交集检查。dont_touch 名单里如果出现了隔离边界前的 cell不管什么理由都必须单独 review不允许出现在批量约束的大网里。第二条隔离边界上的输入网络尽量保持纯连线。input pin 到 iso cell 之间不允许例化普通 buffer 和组合逻辑如果确实需要驱动增益必须放在 iso 之后。第三条综合脚本对 input 端口自动插入的 buffer默认不设 dont_touch。只有明确手工保护的信号网络才允许加保护而且要注释清楚理由。第四条把 low-power 检查里isolated net 上存在不受控组合单元这类告警从 warning 提到 error 级别。这类告警平时看着无关痛痒但往往是漏电问题最早的信号值得用一条 error 去拦住所有分支。5.4 一条可复用的检查命令配合流程纪律我写了一段检查脚本放在综合和后端之间每次跑完都自动执行一遍把所有隔离网络前挂了 dont_touch cell的情况全筛出来foreach_in_collection port [get_ports -filter direction in] { set cell [get_cells -quiet -of_objects $port -filter dont_touch true] if {[sizeof_collection $cell] 0} { set net [get_nets -of_objects $port] set iso [get_cells -quiet -of_objects $net -filter is_isolation_cell true] if {[sizeof_collection $iso] 0} { echo WARN: dont_touch cell on isolated input net: $port } } }这段脚本不复杂但能把这一类问题在进入后端之前就拦住。真实的 low-power 项目里工具之间互相不知道的事太多了靠人记不如靠脚本查。这次排查最后只改了几行约束但前后花了两天。最让我感慨的是这 0.6uA 不是某个 cell 的错而是综合、后端、约束三块各做各事之后漏掉了一个角落。现在每轮检查我都会多看一层隔离边界外还有什么常开逻辑也建议你把类似检查直接写进 flow 里别等功耗报表来给你惊喜的时候再补窟窿。