☰
数字IC后端DRV修不动?先查dont_touch属性再查congestion
2026/10/3 1:20:23 网站建设 项目流程

做数字IC后端的人,大概率都经历过这种场面:跑完route_opt,打开report,密密麻麻一排DRV违规,数量不多不少,477条net。你反复跑了三轮optDesign -drc,违规数纹丝不动,连status都懒得变化。这种时候千万别急着怀疑congestion或者floorplan,先停下来想一个问题:工具到底是修不动,还是它被禁止去修?

DRV问题在PR阶段太常见了,但"大面积、持续、无法修复"的DRV,九成以上不是物理问题,而是属性问题。我那次项目中,把477条net全部翻出来之后发现,它们的共同点是全都挂在几个被打上dont_touch的hierarchy上。正是don't touch hierarchy这个用来锁定网表的属性,把工具修复DRV的手脚全绑住了。这篇文章就把这个case拆开聊:DRV到底是什么、dont_touch怎么阻止优化、如何快速定位,以及怎么安全地解除封锁把违例清掉。

1. 先搞清楚DRV到底是哪一种违例

1.1 后端工程师口中的DRV通常指哪些指标

首先统一一下概念。PR工具里报的DRV,全称是Design Rule Violation,但在物理实现和STA语境下,它跟你做DRC(Design Rule Check)查metal spacing、min width是两码事。工具报的DRV,一般特指三种约束违例:

  • max_transition:信号翻转沿的最大允许时间,也就是slew不能太慢。
  • max_capacitance:驱动pin能带的总负载电容,包括线电容和扇出pin电容。
  • max_fanout:驱动单元允许连接的扇出数量。

这三个值在lib库里一般有默认定义,同时SDC里也可以通过set_max_transition、set_max_capacitance、set_max_fanout覆盖。工具在place、CTS、route阶段会不断检查并在优化中修复这些违例。

max_transition和max_capacitance是最常见的两类DRV。transition太慢会影响时序计算准确性,也增加短路功耗;capacitance超过驱动能力极限,信号上升时间只会越来越差。修DRV的手段也基本固定:加buffer、增大驱动cell尺寸、调整逻辑结构、pin swap等。说白了,必须动网表。

1.2 477条net这个量级能说明什么

先给个直观感觉。一个百万门级SoC顶层,几千条net实在太正常。477条net的DRV违规,如果均匀散布在整个die上,其实是个很小的数字,工具大概率几轮就修完了。真正要警惕的是两个特征:

  • 违规数量在多次优化后完全不变,或者只增不减。
  • 这些net在hierarchy路径上有明显的集中趋势,比如都集中在某几个子模块的边界上。

出现这种局面,第一反应应该是:工具真的想修,但它没有权限去碰这些net。换成人话就是,你的网表上某些地方被贴了"禁止入内"的标签,工具优化引擎跑到那儿就绕道走,只负责记录违规,不负责处理。

477条这个数字在这种场景下非常典型——它不是几百条net恰好都有物理问题,而是几百条net恰好都受了同一个属性开关的管控。

2. dont_touch hierarchy是如何把网表"焊死"的

2.1 dont_touch属性从哪里来,又是怎么传下去的

dont_touch是综合和后端实现工具里一个很常见的属性,目的是告诉工具"这个对象你不要乱动"。它经常出现在这几个地方:

  • 综合脚本里,为了锁定某个单元或者某个模块的网表结构,使用set_dont_touch。
  • 集成第三方IP时,为了保证IP网表和交付版本一致,对整个IP instance加dont_touch。
  • 时钟约束里,对clock net设置dont_touch或dont_touch_network,防止CTS和优化工具修改时钟路径。
  • memory、PLL、IO等特殊单元,为了保持库提供商的reference netlist不被改动,也会加dont_touch。

最容易被忽略的是属性传递性。虽然dont_touch可以分别设置在instance、net、design、reference等不同对象上,但一旦对一个hierarchy instance设置了dont_touch,很多工具会默认它内部的cell和net也全部继承保护。尤其在一些集成流程里,脚本喜欢写set_dont_touch [get_cells xxx]一口气把整个IP锁住,那这个IP内部就是铁板一块。

我曾经排查的时候做过对象级别的对比,这里直接列出来:

属性设置对象工具会被限制的操作典型命令
instance/hierarchy禁止改变内部cell尺寸、插buffer、逻辑重组set_dont_touch [get_cells u_ip1]
net禁止改变该net的拓扑结构、插入bufferset_dont_touch [get_nets xxx]
design/reference禁止对整个设计做跨边界优化set_dont_touch [current_design]

2.2 为什么工具对dont_touch的net只报不修

要理解"修不了"的根因,得先看工具修DRV到底要做什么。就拿最简单的一条max_capacitance违规来说,负载太高,工具通常两个选择:

  • 把驱动cell从低驱动强度换成高驱动强度,也就是upsize。
  • 在net中间插入一个或几个buffer,把大的扇出拆成几段,每段负载降下来。

这两种操作都必须改网表。upsize要求工具能替换cell type,插buffer要求在net上增加新的cell和连线。而dont_touch属性的作用,恰恰就是把这类网表改动全部禁止掉。

工具在优化阶段看到dont_touch对象,会把它放进一个"受保护列表",内部引擎不会对这些对象做任何sizing或insertion操作。它不会停下来说"这里修改不了",而是非常安静地把这条net跳过。最后你看到的报告就是:DRV数量还在,工具显示eco完成,一切正常,但违例原封不动。

如果你手动修,比如打开ECO工具在hierarchy边界硬插一个buffer,工具会直接报类似"instance is inside a dont_touch hierarchy"的错,根本落不下去。

这个场景很像你把一个抽屉彻底封死了,机械臂要整理抽屉里的东西,它只能看着,然后告诉你"里面不整齐",但你问它为什么不整理,它回答不了,因为它连把手都碰不到。

3. 定位根因:不查属性,先查congestion会走弯路

3.1 第一步:从报告里看共性

遇到大面积修不动的DRV,第一件事不是打开GUI看布局,而是先分析报告里的net分布。我当时写的脚本很简单,把report出来的违规net做三件事:

  • 按hierarchy前缀分组,统计每一组数量。
  • 区分违规类型是max_transition还是max_capacitance。
  • 标记这些net是否是某个IP/hierarchy的输出端口或内部节点。

477条net做完分组之后,规律立刻出来了:其中410条集中在同一个hierarchy下面,另外几十条分散在这个hierarchy的边界net上。这个信号非常明确——问题大概率出在这个hierarchy本身,而不是整颗芯片。

同时再看一眼这些net的驱动cell和负载cell。如果驱动cell是IP内部cell,负载cell在顶层,那就基本可以断定,工具的优化引擎被block挡在hierarchy外面,只能眼睁睁看着IP输出口的驱动能力不够、顶层负载又拉高。

3.2 第二步:用工具命令实锤dont_touch

定位到具体层次之后,下一步就是用工具命令确认dont_touch属性。不同工具命令差异很大,我这里讲思路,你对应自己环境里找命令。

在ICC2里,可以先用report_dont_touch看整个设计有哪些dont_touch对象,再用类似这样的过滤找到受影响的net:

get_nets -hier -filter "full_name =~ *u_ip1* && dont_touch == true"

在Innovus里,可以用dbGet体系去查net或者inst的dont_touch状态:

dbGet top.nets.isDontTouch dbGet top.insts.isDontTouch

更直接的做法是在GUI里把477条违规net高亮,再叠加dont_touch对象高亮,两条高亮如果高度重合,实锤基本就落地了。命令名字不重要,关键是脑子里要有这个判断路径:先查属性,再查物理。

3.3 第三步:把其他常见原因排除掉

dont_touch是最常见的"修不掉"原因,但它不是唯一原因。为了不被自己的判断带偏,我当时把下面几个因素也一起排掉了:

  • 约束设得是否不可实现。比如set_max_transition 0.02ns,但标准单元库里即使最小尺寸的cell也做不到这么快的slew。此时工具无论怎么修,transition都降不到约束值以下。这种情况可以通过放宽约束后重跑一版来验证。
  • buffer/inverter库是否被dont_use禁用。如果link library里buffer本来就不全,或者被set_dont_use禁掉,工具在修DRV时没有可用的插入cell,结果也是修不动。
  • 面积利用率过高、局部congestion严重。工具想插buffer但找不到位置,DRV也无法修复。这种情况和dont_touch的区别是:它会尝试修但修不完,一版跑完违例数量会变化,而不是完全不变。
  • 加密IP或者只读网表。有些IP交付时已经encrypt,内部结构不可见也不能改,这在物理上就等效于一个超大dont_touch。

我那次排查到最后,还把违规net临时解除dont_touch跑了局部eco,DRV数量马上往下掉。这一步能直接证明根因就是属性保护,而不是物理实现能力不够。

4. 修复思路与操作:从全局锁死到局部放行

4.1 方案一:定位到hierarchy,精准解除dont_touch

明确根因之后,最直接的修复方案就是在PR流程里精准解除对应hierarchy的dont_touch。操作上不是简单地把所有dont_touch删掉,而是要做范围控制。

先向上游确认这个IP能不能动。如果IP是内部团队交付的,一般会有一个"physical ECO授权"的概念——逻辑功能不能改,但允许后端工具做cell sizing和插buffer来满足物理实现。拿到授权之后,在PR脚本里把目标instance的dont_touch移除:

# ICC2示例思路 set_dont_touch [get_cells u_ip1] false
# Innovus示例思路 set_dont_touch u_ip1 false

然后重新跑一轮optDesign -drc或者route_opt。工具会立刻开始对它之前不敢碰的区域做优化,该插buffer插buffer,该upsize的upsize。我那次解除之后跑了不到半小时,477条net清零了380多条,剩下的靠再一轮迭代就收干净了。

这里有个细节容易踩坑:有些脚本是综合阶段生成的网表里带着dont_touch,PR读入网表的时候属性已经长在网表上了。你需要在place之前就处理掉,或者至少在DRV修复之前处理。如果已经跑完了CTS,再解除属性,工具对内部cell做size调整可能会引入hold问题,后续还得补一轮hold fixing。

4.2 方案二:只放行net,保留内部cell保护

有些场景不允许解掉整个hierarchy的dont_touch,比如IP内部结构需要严格保持,但允许在边界上插buffer。这种情况下,可以把保护粒度细化到net层面。

思路是:instance仍然保持dont_touch,但把该hierarchy下面所有net的dont_touch属性移除。工具在看到net没有保护后,依然不能改内部cell尺寸,但可以在net上插入buffer,等于在物理层增加驱动级。这样逻辑网表层面IP内部大体没动,相关路径延迟增加不会太夸张。

当然,这种"中间态"方案修复能力有限。如果违例是IP内部某个大扇出net本身驱动不足,仅靠边界插buffer是解决不了的。如果477条net大部分是IP输出口到顶层负载的边界net,那这个方案就非常好用。

4.3 方案三:上游配合,从网表和约束源头解决

如果时间允许,更好的方式是让上游在交付前就把问题解决掉。我当时通过排查报告发现,这批IP在单独跑flow的时候并没有DRV问题,是在顶层集成以后才出现的。原因是顶层SDC约束更紧,而且顶层布线负载更大,IP输出口的驱动能力按standalone场景设计,到了顶层就不够用了。

正确做法是:IP团队交付前处理掉边界net的DRV,或者至少在交付时明确告诉后端团队哪些hierarchy加了dont_touch、为什么加、是否可以放开。如果IP确实不能动内部逻辑,那就要在顶层约束里合理设置边界net的max_transition/max_capacitance,而不是直接套用一个全芯片统一值。

另一个上游方案是在综合导出网表时,就把dont_touch粒度拆细。比如只锁IP内部逻辑单元,但边界上的输出级寄存器/buffer不锁,给后端留出优化空间。这些需要前后端流程配合,但对收敛效率提升非常明显。

4.4 修复后的回归验证不能省

DRV不再报违例只是第一步,改网表之后的验证才是重点。不管用了哪种修复方案,至少要做三件事:

  • 形式验证:确认ECO前后的网表逻辑等价。工具插buffer、交换pin通常不会改变逻辑,但万一工具在优化时把一个cell的功能替代错了,形式验证能兜住。
  • STA回归:DRV修复很可能影响路径延迟,特别是upsize会增大cell面积和功耗,但也会改变transition,从而导致setup/hold变化。跑完修复之后重新看所有path的slack。
  • 物理检查:插了buffer之后,局部congestion可能变化,尤其要检查插buffer区域有没有routing short或blockage违例。

我当时还额外做了一步:把修复前后的netlist diff打出来,给前端团队review。确保所有改动都可以解释,而不是黑盒里出来的大网表。这一步在流片前的ECO里尤其有用。

5. 常见问题与避坑经验

5.1 现象与排查方向速查表

现象可能原因排查思路
大量DRV集中在某个hierarchy,多轮修不掉该hierarchy被加dont_touch优先查dont_touch属性,而非congestion
工具log里有"cannot fix / skipped due to dont_touch"字样net或cell受保护在log里搜dont_touch关键词追根溯源
手动ECO插buffer报hierarchy protectedinstance级dont_touch确认保护范围,或改用方案二只放行net
时钟网络上DRV高,工具无法修clock net被set_dont_touch_network误伤区分dont_touch和dont_touch_network,CTS前处理
放宽max_transition限制后DRV立即减少约束不可实现对照库的最小transition能力,确定约束合理性
工具尝试修但每次违例数量都有变化局部congestion或buffer资源不足看congestion map,确认面积利用率

5.2 我踩过坑后总结的三条规律

第一条,先查属性,再查物理。这个优先级要刻在脑子里。我见过太多人拿着DRV报告直接盯congestion map和floorplan,折腾了一两天才发现罪魁祸首是网表里一个不起眼的dont_touch。查属性最快的方法不是GUI点来点去,而是在日志里直接搜dont_touch和cannot fix,工具其实早就告诉你了。

第二条,dont_touch不是只有综合脚本才有。很多物理实现工程师不知道,导入第三方IP的网表时,IP自带属性也会带进来。你要把report_dont_touch当成前期flow的例行检查项,在place之前了解清楚整个设计哪些区域是不能动的。这样一来,后期DRV乱飞的时候,你心里早有一张"受保护区"地图。

第三条,修DRV的ECO改动要留痕。无论你解除了哪个hierarchy的dont_touch,插了哪些buffer,都建议用一个ECO log文件记录下来。流片前跑ECO freeze的时候,这些记录能帮你快速判断当前netlist相对golden netlist改了什么,也方便做formal verification的setup。

我自己在项目里把这一套流程完整走下来之后,最大的感受是:后端遇到匪夷所思的DRV数量时,最先排除的往往不是物理问题,而是那些写在约束文件前几行、看起来人畜无害的dont_touch。它能把工具半个身体锁住,而报告只会告诉你结果,不会告诉你原因。如果你也遇到类似的场景,别急着怀疑floorplan,先花半个小时把属性和约束翻清楚,很多"修不掉"的违例,其实只是没被允许去修而已。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询