ARTICLE DETAIL

资讯详情

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

CTS时钟树CDB Cell过多?Innovus后端优化的关键策略与实践

CTS时钟树CDB Cell过多?Innovus后端优化的关键策略与实践 项目做到CTS这步时钟树报告一拉我脑子里直接跳出两个字完了。Innovus的时钟树报告显示CDB Cell数量比上一版失控版本还多出四成更离谱的是有一堆buffer只驱动了一两个触发器leaf纯粹在给自己增加绕线负担。那一刻我就知道问题不在工具而在CTS spec里那些被我顺手填进去的“保守值”。在数字后端流程里Clock Tree SynthesisCTS是难度最容易被低估的一环。很多同学以为CTS就是把时钟从root推到大几千个sink让skew尽量小就行结果Innovus“忠实”地执行了你的意图插出满满一屏CDB Cell——面积上去、功耗上去、congestion恶化、OCV悲观反而把时序拖垮。这篇博文不聊教科书定义只讲我在真实项目里怎么定位“为什么工具会疯插CDB Cell”、怎么通过spec和ECO操作把时钟树重新“减回标准体重”。1. 一颗“胖”时钟树带来的连锁反应过度CDB Cell到底坑在哪1.1 CDB Cell的身份以及时钟树里它该占多大体量CDB Cell是时钟树综合过程中专门插入的时钟网络驱动单元本质上是经过特殊设计和选型的buffer/inverter。它跟普通data path上的buffer有个重要区别CDB Cell的型号选择、上升下降时间对称性、噪声鲁棒性和驱动能力都是为了时钟信号大规模分发而准备的。在标准单元库里这类cell通常会被标注为clock buffer/clock inverter供CTS工具调用。正常的CTSCDB Cell数量应该和sink数量保持一个健康比例。以我这些年经手的项目经验纯时钟buffer/inverter的总数量通常控制在sink数量的10%以内是比较合理的区间。举个例子一个时钟域有2万个sink时钟树里插了15002000个CDB Cell这算正常如果插到5000个以上那就要警惕了。另外还要看树的级数一颗从clock root到最远leaf之间的buffer级数8级左右很常见超过12级且平均每级延迟明显偏大基本就是过度插入的信号。1.2 过度插入的表征信号先从报告和图上发现“三高”工具不会直接告诉你“我插多了”但它的行为会留下痕迹。我在项目里总结了三个最直观的表征简称“三高”。高比例CDB Cell数量 / sink数量超过15%甚至20%以上说明大量buffer只是在打酱油。高冗余报告里出现大量fanout极低的buffer比如一个buffer就驱动1个或多或2个leaf pin。这种低效驱动是过度插入最典型的表现——正常工具不会为单leaf专门插一级buffer除非它是在做延迟匹配或修复transition。高级数report_clock_tree -detail拉出来某些路径上的级数远超同domain均值且latency明显偏大。在Innovus GUI的Clock Tree Browser里选中某棵树看结构图你会看到一串串buffer像糖葫芦一样串在很短的net上。这种结构一旦出现在congestion hot spot区域后面绕线阶段会非常痛苦。1.3 面积只是一层皮真正的代价在时序和绕线有人觉得CTS多插几百个buffer面积也没超有什么好慌的我刚开始也这么想直到被一个案子教育了。时钟树buffer过多首先直接影响的是时钟延迟。buffer多了insertion delay自然被推高而现代工艺下OCV和derating对长路径非常不友好。你辛苦修完setup结果CTS阶段因为时钟树过长到post-CTS后setup slack直接掉一截这属于“CTS种下苦果signoff阶段还债”。其次大量CDB Cell占用了placement site把原本留给data path cell的空间挤掉congestion恶化。尤其在clock gating cell和macro附近工具为了绕开拥塞区域会插入更多detour buffer形成恶性循环。更隐蔽的一点clock buffer数量多意味着时钟网络的晶体管开关次数多动态功耗上涨。芯片规模大了以后时钟网络功耗占芯片总功耗的比例可以达到30%50%这一块的浪费是非常可惜的。2. 动手之前先找根因五个会诱导工具“只管多插”的CTS设置2.1 第一个旋钮拧过头target skew设成0不代表更优秀这是我在新项目里最容易看到的问题。很多工程师觉得skew越小越好于是直接SetTargetSkew 0.02甚至0。工具当然会“认真执行”为了把远端sink和近端sink对齐它会在路径较短的一端插一串delay buffer让信号绕路、让buffer堆叠硬生生把晚到达的路径给“等”出来。这些delay buffer并不改善任何signal integrity纯粹是为了匹配延迟。更讽刺的是在OCV环境下skew设到极值往往导致时序更差因为长路径的derating因素放大了。我自己的习惯是把target skew从极端值放宽到可接受的范围内比如60100ps甚至更高具体看时钟周期和库的单元延迟。请记住CTS的目标不是skew0而是skew足够小、并且不会因为过度补偿带来额外的OCV和绕线代价。2.2 被误解的max_fanout设置越小反而引导工具插更多buffer有一个常见的“反直觉”现象把max_fanout设得非常小比如8工具反而会插出更多级的CDB Cell。原因在于时钟树net如果fanout超过限制工具会把大扇出拆成多个子树每个子树都由一个新的buffer驱动。本来一个buffer能驱动16个sink你非要让它最多驱动8个那同样的sink数量就需要两倍的驱动buffer这些驱动buffer之间为了满足skew往往还需要额外的平衡buffer层。整体下来buffer数量直接翻倍甚至更多。我并不是说max_fanout可以随便设很大过大的fanout会导致驱动不足、transition超限。关键是要在两者之间找到项目实际可行的平衡点。对大多数主流工艺库1420这个范围是比较常见的起点。如果你的库单元驱动能力很强适当再放大也问题不大如果库单元本身偏弱就再收一点。总之不要为了“看起来稳健”就把fanout压到个位数。2.3 target delay的隐形上限目标延迟设太小工具就疯狂“推”CTS spec里另一个容易埋雷的是延迟约束。有些流程会在spec里写SetTargetDelay -early值很小甚至设置成0试图让时钟尽快到达所有sink。工具为了实现极小的延迟目标会不断加大每级buffer的驱动强度、缩短net长度结果就是在原本两三颗buffer就能完成任务的地方硬生生堆出四五颗只为把transition再压快一点、延迟再缩一点。这里需要理解一个物理事实在特定工艺和特定库下时钟树存在一个“自然延迟”。你说“我这里需要快一点”工具能做的只是多加buffer、加宽wire、拉开间距。这些手段都存在收益递减的拐点。一旦过了拐点再堆buffer换来的延迟改善微乎其微面积功耗却直线上升。正确的做法是先跑一版不做强约束的CTS看工具自然的latency分布再决定要不要收敛而不是一开始就把target delay打到最小值。2.4 skew group跨区域平衡让远水和近渴强行对齐的代价当多个时钟域或同一域内物理距离悬殊的sink被放进同一个skew group时Innovus就得想办法让它们对齐到达时间。最粗暴的办法就是给较近的那组插延迟buffer给较远的组加大驱动。物理距离差异越大需要的补偿buffer就越多。我有一次处理过一个多时钟域模块A域sink集中在左上角B域sink散落在右下角结果我把它们设到了一个group里CTS完成后发现两棵子树之间密密麻麻全是平衡buffer。后来把跨域约束拆掉各自独立build tree再通过CTS后的useful skew微调来满足时序要求CDB Cell数量立刻降了25%。所以skew group的定义里物理位置、时序要求、macro sink的模型差异都要考虑进去不能一刀切。2.5 buffer/inverter类型配置全buffer树比inverter树更容易“胖”不少项目为了省事CTS cell list里只配了clock buffer没有配clock inverter。这相当于让工具只能用一种武器打仗。反相器结构在CMOS工艺下延迟更小、面积更小而且可以通过奇偶级配对来实现同相传输插入的级数天然比buffer树少。如果库里明明有高质量的clock inverter却不用工具只能靠堆buffer来实现同样的驱动效果CDB Cell数量自然会上去。另外cell list里不同驱动强度的型号也要选全。如果库里有驱动能力偏弱的小尺寸clock buffer工具可能会在长距离传输时连续掉多级小buffer而不是换成一级大驱动buffer。如果你在CTS spec里只给了中等驱动强度的几种工具的可选项就受限也会导致级数偏多。3. 先学会“体检”用报告和spec量化你的时钟树是否超重3.1 从report_clock_tree里提取关键指标判断时钟树是否过度插入不能只靠感觉。我一直习惯在CTS之后第一时间跑三份报告report_clock_tree -structure看树的整体层级和buffer分布。 report_clock_timing -type summary看skew、latency是否在预期范围。 report_ccopt_clock_trees这是在CCopt流程里看每棵树的拓扑情况。具体看的时候我会重点关注几个字段。一个是Number of Clock Tree Cells注意它和sink数量的比值另一个是Tree Levels也就是最大级数。如果你发现同domain内不同branch的level差异很大比如平均值5级某一条支路却有9级那这条支路上大概率就是被插了多余的平衡buffer。3.2 用skew/latency分布判断“值不值得”一颗健康的时钟树latency应该相对均匀地分布在root到各sink之间skew处在target范围内。我们可以做一个简单对比如果把target skew从20ps放宽到80psCDB Cell数量能明显下降而最终的skew依然在80ps内那说明之前那套约束确实是无谓地逼工具加班。实践中我建议CTS后把关键domain的skew和实际latency打点成直方图或者直接在Innovus GUI里看地理分布。如果发现大量sink集中在极窄的延迟窗口里但代价是整棵树级别暴增这就是过度平衡的典型表现。有些情况下放宽skew目标后setup/hold反而更容易收敛原因就是OCV悲观变小了这是我们在评估“值不值得”时的核心量化依据。3.3 哪些报表现象背后就是插太多的信号经历了几个项目之后我总结出几个特别“扎眼”的报表征象一旦出现就要怀疑CDB Cell过度插入。average fanout长期低于1.5说明大量buffer在驱动少量leaf属于明显的延迟匹配单元。同一片区域出现多个功能完全相同、驱动强度完全相同的buffer且它们的输入都连到同一上游net这多半是“clone buffer”过度使用的结果。clock net在report_ccopt_clock_trees里显示为“detour”物理距离比曼哈顿距离长30%以上说明工具在绕路做平衡。某层级cell数量异常高而下一层级network几乎没有sink增长典型的延迟补偿层。这些信号不需要等signoffCTS阶段看到就能提前介入省掉后面一大堆返工成本。4. 从约束到落地的修正清单让Innovus按需插buffer4.1 spec文件的正确姿势skew/delay/fanout到底怎么给如果你用的还是传统CTS spec流程我会给出下面这套可以当作起点的操作方式。在spec文件里先删掉那些“看起来很美好”的极端值。比如SetTargetSkew从0.02改到0.060.08单位nsSetTargetDelay不要同时压early和late而是给一个范围early设为0.1左右、late设为0.50.8具体数值取决于你的时钟周期和库延迟。不要小看这个改变它对CDB Cell数量的影响往往是立竿见影的。max_fanout方面我在上面提到过1420是个常见起点。如果你发现工具因为transition问题继续插buffer优先检查的就是NDR的线宽线距和层设置而不是继续调小fanout。另外一个好习惯是在spec里显式指定buffer和inverter的cell list让工具知道它有哪些“武器”可用而不是从库的所有cell里瞎挑。4.2 善用inverter配对和cell list优化在SetClockTreeOptions里我会同时指定buffer和inverter并且尽量选择不同驱动强度的型号。用inverter树时有一个细节如果设计里有大量clock gating cell要注意时钟门控单元输入端需要的通常是同相信号。这时可以用inverter pair来处理两级inverter形成一个buffer功能但因为它每一级的延迟更小总面积往往比直接插一个大buffer更优。实际操作时我会在spec里设置类似这样的配置SetClockTreeOptions -buffer {CLKBUFX16 CLKBUFX24 CLKBUFX32} -inverter {CLKINVX16 CLKINVX24 CLKINVX32}这里的核心思想是给工具足够多的选择同时限定在合理的候选范围。如果你只给一种大驱动buffer工具遇到小负载也必须用大炮打蚊子如果你给的全是小驱动buffer长距离传输又会堆级数。两者都会让CDB Cell白白变多。4.3 用CCopt与ECO buffer tree做局部外科手术光改spec重新跑CTS是最理想的情况但很多时候项目已经跑到了CTS之后重新做全部CTS的代价太大。这时候就要靠CCopt流程里的ECO能力也就是很多人搜索时用的“innovus eco buffer tree”这个方向。在Innovus里eco_ccopt_clock_trees可以在不重新做全局CTS的前提下对指定clock net做局部的buffer插入或移除。我会先运行一个带报告模式的命令让工具评估哪些buffer是冗余的。比如它可能会告诉你这条net上的两个buffer可以合并成一个高驱动buffer不会违反transition/cap却能省掉一级。如果发现某些buffer只驱动了一个leaf而且没有延迟匹配作用我会直接把buffer删掉让leaf直接连到上游net。但这里必须留个心眼删buffer后下游net的cap和transition可能发生变化必须重新跑一遍时钟树时序分析确认skew没有恶化到不可接受的范围。4.4 迭代验证闭环不要一次改到“面目全非”工具优化的一个现实是你调一个参数可能解决了一个问题却在另一个角落埋了新雷。所以我强烈建议迭代验证不要一口气把所有约束都改掉。我的习惯是一次只改一到两个变量然后对比三个指标CDB Cell总数、最大skew、setup/hold关键path的slack变化。如果指标变好保留改动再试下一组如果变差回滚再换一个方向。这里有一点经验值得分享CTS约束调整后不只要看CTS report还要跑一小段post-CTS的时序优化比如ccopt_design -cts -post_legalize或者简单place_opt。因为时钟树变短之后data path上的cell可能有一些重新优化的空间这时候你能看到最真实的时序收益。5. 真实案例复盘一次16nm项目的CTS减肥记录5.1 问题出现时的数据和现象那个项目是一个16nm的SoC子系统包含CPU cluster和周边总线总共6个时钟域。最初版本CTS完成后主时钟域的报告让我相当头疼sink数量大概2.4万CDB Cell总数却到了6500多个比例接近27%。树的级数平均8级个别branch到了13级。最扎眼的是报告里出现大量fanout为1或2的buffer平均fanout只有1.2。从GUI上看芯片中部的congestion map已经亮起了黄色。几个macro附近的绕线通道被整排的clock buffer占掉之后placement optimization阶段数据路径单元被挤到更远的row上绕线长度增加setup slack跟着掉了30多ps。我当时判断问题核心就是CTS阶段的过度插入而不是后面优化能救回来的。5.2 一步步排查最终锁定的三个元凶我先拉出CTS spec逐项审查重点排查顺序是skew group、target delay、max fanout、cell list。第一个问题很快就暴露了spec里SetTargetSkew 0.0220ps的目标skew对16nm工艺下CPU cluster这种大跨度场景来说太激进。为了对齐远端大扇出sink和近端sink工具在近端插了大量delay buffer。第二个问题是SetTargetDelay的early设成了0.05nslate设成0.1ns。这等于逼工具把时钟信号“吹”到所有sink而且要求非常短。工具只能通过堆buffer、增大驱动来硬撑。第三个问题是SetClockTreeOptions里max_fanout被设置成8而实际上这个库的CLKBUFX24驱动能力很强驱动16个sink完全没问题。再加上cell list里只有buffer没有inverter工具就只能用低效的“多级小buffer”打法。5.3 修改后的数值与收敛结果对比针对三个元凶我做了如下修改SetTargetSkew从0.02改为0.08ns目标skew放宽到80ps。 SetTargetDelay改为early 0.15、late 0.5。 max_fanout从8调整到16。 SetClockTreeOptions里同时加入inverter cell并且选用了驱动能力覆盖中到大范围的型号。重新跑完CTS后的数据CDB Cell总数从6500多降到3800多降幅超过40%。最大skew保持在了75ps以内完全满足设计目标。更关键的是因为树的级数变少、insertion delay缩短post-CTS阶段的setup slack比之前那版好了20ps以上OCV悲观也因为路径变短而明显减弱。5.4 事后清理直接挤出的面积/功耗余量这版修改不仅让时钟树“瘦身”还给整个floorplan带来了喘息空间。congestion map大范围转绿绕线阶段明显顺畅。根据后来的估算光时钟树部分减少的cell面积大概占了模块总面积的1.5%对一个大芯片来说可能不算夸张但对这个子模块来说多出来的面积直接让后续的修复buffer有了落点。功耗收益更明显。时钟树的动态功耗与CDB Cell数量正相关减少了近半的clock buffer意味着时钟网络功耗下降了接近30%。在低功耗设计里这个数字可以写进报告作为优化成果了。6. 经验与教训这些我踩过的坑你最好别踩6.1 不要迷信“先跑一版无约束”的原始数据但一定要看自然延迟有些工程师会说“CTS之前先别设约束跑一版看看”。这个方法本身没问题问题在于很多人跑完自然版本后看到skew很大就慌了然后开始疯狂收紧约束反而走上了过度插入的老路。我的建议是自然版本的价值不是看skew能不能接受而是看每棵树的自然latency和级数。基于这个baseline去设定一个有挑战但不离谱的target delay和skew这才是正确用法。6.2 Clock Gating Cell周围的buffer需要单独关注时钟门控cellICG是CTS中CDB Cell过度插入的重灾区。因为ICG通常有enable端和clock端工具在分析时钟树时会把ICG的clock pin当作一个特殊的sink point为了修复ICG输出时钟的transition有时会在ICG后面再插一串buffer。如果ICG大量聚集,这类局部buffer会迅速膨胀。遇到这种情况我会给ICG的输出net单独设置一组更合理的transition和fanout限制而不是用全局设置去约束它。6.3 每次改完spec提交给版本管理写上“为什么改”这条听起来像流程规定但其实是我被现实教育出来的经验。有一回我改了target skew过了两天另一个同事说“CTS怎么又胖了”排查了半天才发现是某次merge时把我的改动覆盖了。从那之后每轮CTS spec都提交版本库commit message里写清楚改了什么、为什么改、期望达到什么效果。这样做不仅能防覆盖也能让其他工程师review你的优化逻辑减少团队里的无效争论。6.4 如果真的只想快速验证一个方案用ECO而不是全量重跑项目后期经常会遇到一种情况时序还差一口气怀疑是某条clock path上多了一级uffer在拖后腿。这时候千万别冲动全量重跑CTS影响面太大、风险太大。直接用eco_ccopt_clock_trees的report模式分析那条path看看能不能安全地移除或合并buffer。一次典型的局部清理只影响几颗cell的级联关系做完后跑一下局部时钟树的skew和transition检查如果满足则继续不满足就回滚。这种外科手术式的操作我在多个项目后期都用过效率非常高。最后再分享一个我自己的小习惯每次CTS完成我都会强制自己看一眼“CDB Cell数量 / sink数量”这个比值。如果超过15%我会默认不是时序有问题而是约束或配置有问题先停下来检查再继续。这个数字让我躲过了好几次“看起来能收敛、其实在给后端埋雷”的时钟树版本。CTS里的CDB Cell数量从来不是越多越好工具插的每一个buffer背后都是你的约束逻辑在起作用。搞清楚它为什么插你才能真正掌控时钟树的质量。
返回列表