
1. 分段长时钟树到底难在哪从sink type说起做数字后端这行的朋友尤其是经常跟Innovus打交道的应该都有过这样的经历跑完CTSreport一看某条clock path的latency大得离谱skew倒是压得不错但insertion delay高得让人心里发毛。再仔细一看这条clock tree从root到leaf穿过了大半个die中间还跨了好几个power domain。这种场景下clock tree的sink type选择就变得非常关键了。所谓分段长时钟树说白了就是clock tree的物理跨度很大从clock root到最终的sink点路径上可能经过多个hierarchy、多个voltage island、甚至多个clock domain的边界。这种树如果按常规方式一刀切地设成同一种sink type要么工具优化不动要么优化出来的结果在某个局部看起来还行全局一看全是问题。Innovus里CTS阶段支持的sink type有好几种常用的包括stop pin、exclude pin、float pin、through pin、leaf pin等等。每种type背后对应的是工具对这条path的约束策略和优化自由度。选对了工具知道哪里该停、哪里该穿、哪里该重点balance选错了要么工具在该停的地方继续往下buffer要么在该穿的地方给你硬生生截断最后latency和skew两头不讨好。我见过不少项目CTS阶段为了图省事把所有sink都设成stop pin结果工具在长路径上疯狂插bufferinsertion delay直接爆掉后面再怎么调useful skew都救不回来。也见过把该设成through pin的地方设成了leaf pin导致工具在中间节点就开始做balance最后leaf端的skew反而压不住。这篇文章主要面向有一定Innovus CTS经验的数字后端工程师尤其是那些正在处理大规模SoC、多power domain、长clock latency场景的同行。我会把5种特殊sink type在分段长时钟树里的应用技巧拆开来讲包括每种type的适用场景、设置方法、常见坑点以及怎么配合其他CTS约束一起用。内容基于我自己在多个项目里的实操经验有些是踩过坑之后总结出来的有些是跟同行交流时学到的希望能帮你在下次跑CTS时少走点弯路。2. 五种特殊sink type的核心机制与选型逻辑2.1 stop pin什么时候该让工具“到此为止”stop pin是CTS里最常用也最容易被滥用的sink type。它的核心语义是告诉工具这条path到这里就结束了不要再往下trace也不要在这个点之后插任何CTS buffer。工具会把stop pin当作一个clock tree的终点来对待围绕它做balance和optimization。在分段长时钟树里stop pin的典型应用场景是clock gating cell的enable端或者某个子模块的clock input port。比如你有一个大的CPU cluster它的clock是从顶层root分下来的但cluster内部有自己的clock tree。这时候你可以在cluster的clock input port上设stop pin让顶层CTS只负责把clock推到cluster入口cluster内部的tree由它自己的CTS run去处理。这样做的好处是顶层CTS的优化范围可控不会因为cluster内部复杂的clock结构而把顶层tree搞得过于臃肿。但这里有个坑stop pin设多了clock tree会被切得太碎。我见过一个项目为了控制latency在中间hierarchy上设了十几个stop pin结果顶层tree的skew怎么都压不下去因为每个stop pin都是一个独立的balance目标工具在多个目标之间来回妥协最后哪个都做不好。所以stop pin的使用原则是只在真正的clock domain边界或者物理分区边界上设不要为了局部优化而随意设。设置方法上Innovus里可以通过set_ccopt_property sink_type来指定也可以在create_ccopt_clock_tree_spec文件里直接写。推荐的做法是在spec文件里显式声明这样可追溯性好后面ECO的时候也容易改。# 在ccopt spec文件里设置stop pin set_ccopt_property sink_type stop [get_pins cpu_cluster/ck_in]注意stop pin一旦设定工具就不会再往该pin的fanout方向trace。如果这个pin后面还有你希望工具balance的leaf那就要慎重因为工具根本看不到它们。2.2 exclude pin把不需要balance的点摘出去exclude pin的语义比stop pin更彻底工具不仅不往这个pin后面trace而且完全不把这个pin纳入clock tree的balance目标。换句话说这个pin在CTS眼里就是透明的它既不是sink也不是through工具不会为它做任何latency匹配。这个type在分段长时钟树里的典型用途是测试逻辑的clock端或者某些always-on的monitor clock。比如DFT的scan clock在function mode下你根本不关心它的latency那就可以设成exclude pin让工具把精力集中在function clock上。再比如一些performance monitor或者debug logic的clock它们对skew不敏感设成exclude可以避免工具为了balance它们而牺牲主clock的质量。但exclude pin有个隐藏风险如果这个pin后面实际上还有function logic的clock你把它exclude了那部分logic的clock就完全没人管了。我遇到过一种情况某个模块的clock在RTL里被mux过function mode下走主clocktest mode下走scan clock。工具默认会把mux的输出当作一个sink来balance但如果你把mux的某个input设成exclude工具可能会误判整个mux的输出都不需要balance。所以设exclude之前一定要确认这个pin的fanout里没有你关心的function clock。# 设置exclude pin set_ccopt_property sink_type exclude [get_pins dft_scan_ck]2.3 float pin让工具“看着办”的灵活选项float pin是五种type里最灵活的一种。它的语义是工具可以把这个pin当作sink来balance也可以当作through来trace具体怎么处理由工具根据全局优化目标来决定。换句话说你把决定权交给了工具。在分段长时钟树里float pin适合用在那些你不太确定该stop还是该through的中间节点上。比如某个hierarchy的clock input你既希望工具能balance它到和其他sink差不多的latency又希望工具能继续往下trace到内部的leaf。这时候设成float工具会根据实际情况判断如果往下trace对全局skew有利它就往下走如果停在这里更有利于balance它就停。但float pin的问题是结果不可预测。同一个设计两次CTS run可能会得到不同的结果因为工具的优化算法有一定的随机性。所以如果你的clock tree对一致性要求很高比如要做ECO或者要跟其他corner做对比float pin可能会给你带来麻烦。我的建议是在项目初期可以用float pin来探索工具的优化倾向但到了signoff阶段最好把float pin替换成明确的stop或through让结果可控。# 设置float pin set_ccopt_property sink_type float [get_pins sub_module/ck_in]2.4 through pin长路径上的“穿针引线”through pin是分段长时钟树里最重要的type之一。它的语义是工具必须穿过这个pin继续往下trace但这个pin本身不作为一个balance目标。换句话说这个pin在clock tree里是一个“路过”的节点工具会在它上面插buffer来驱动后面的load但不会为了它单独做latency匹配。through pin的典型应用场景是长clock path上的中间buffer节点或者跨power domain的level shifter。比如你的clock从顶层root出发经过一个always-on的power domain再进入一个可关断的domain。在always-on domain里有一个level shifter或者isolation cell它的clock input就是一个天然的through pin。工具需要穿过它继续往下走但不需要为它做balance因为它本身不是最终的sink。through pin用得好可以显著减少长路径上的buffer数量。我做过一个对比同样一条跨三个power domain的clock path如果中间节点都设成stop pin工具会在每个domain边界都插一堆buffer来做balance总buffer数多了将近40%。改成through pin之后工具只在必要的地方插bufferlatency反而更小因为路径上的逻辑级数少了。但through pin也有坑如果through pin后面的load太大工具可能会在through pin前面插很多buffer来驱动这时候through pin就变成了一个事实上的sinklatency会集中在它前面。所以设through pin的时候要关注它后面的fanout情况如果fanout太大考虑在它后面再加一级buffer或者设一个stop pin来分担。# 设置through pin set_ccopt_property sink_type through [get_pins level_shift/ck_in]2.5 leaf pin最终sink的精确控制leaf pin是clock tree的最终终点也就是flop的clock pin或者latch的gate pin。在分段长时钟树里leaf pin的设置直接决定了工具在最后一级的balance策略。默认情况下工具会把所有flop的clock pin都当作leaf pin来处理。但在分段长时钟树里有时候你需要手动指定某些pin为leaf比如当某个flop的clock是从一个非标准的cell过来的时候或者当你想把某个pin从through改成leaf来强制工具在那里结束tree的时候。leaf pin的一个关键属性是leaf pin的balance group。工具会把leaf pin按照它们的clock domain、voltage island、以及你指定的group来分组然后在组内做balance。在分段长时钟树里如果你不显式地指定group工具可能会把不同domain的leaf混在一起balance结果就是跨domain的skew怎么都压不下去。所以我的习惯是在CTS之前先根据clock domain和power domain把leaf pin分好组然后在spec里显式指定每个group的balance目标。# 设置leaf pin并指定balance group set_ccopt_property sink_type leaf [get_pins cpu_core/reg_*/CK] set_ccopt_property balance_group cpu_core_grp [get_pins cpu_core/reg_*/CK]提示leaf pin的balance group不要设得太细否则工具会在每个小组内单独balance全局skew反而会变大。一般建议按clock domain来分最多再按power domain细分一层。3. 分段长时钟树里的组合应用与参数计算3.1 怎么根据路径长度和domain数量选type在实际项目里一条clock path上往往需要组合使用多种sink type。我的经验是先看路径长度再看domain数量最后看leaf的分布。如果路径长度超过2000微米或者穿过3个以上的power domain那中间节点大概率要用through pin来减少buffer级数。如果路径上某个节点后面有独立的clock tree那这个节点设stop pin。如果某个节点只是路过后面还有大量leaf需要balance那设through。如果某个节点的latency你完全不关心设exclude。具体怎么判断我一般会先跑一次CTS用report_ccopt_clock_trees看一下工具默认是怎么处理的然后根据report里的latency和buffer分布来调整。比如report显示某条path上插了20个bufferlatency 1.2ns那就要考虑把中间的一些节点从stop改成through让工具少插点buffer。3.2 latency目标怎么定一个实际的计算例子假设你的clock period是2nsroot的latency是0.3nsleaf端的setup time是0.1nsclock uncertainty是0.05ns。那么leaf端的clock latency最大不能超过2ns - 0.3ns - 0.1ns - 0.05ns 1.55ns但这只是理论上限。实际做的时候你要留足够的margin给OCV和后面的ECO。我一般会把目标latency定在理论上限的70%左右也就是1.1ns左右。然后根据这个目标反推每个中间节点的latency预算。比如路径上有3个中间节点那每个节点的latency预算大概是(1.1 - 0.3) / 3 ≈ 0.27ns。如果某个节点的实际latency超过了这个预算就要考虑调整它的sink type比如从stop改成through让工具把buffer分散到后面的节点去。3.3 跟useful skew的配合别让sink type打架useful skew是CTS里常用的技巧通过故意让某些leaf的clock早到或晚到来改善setup或hold。但在分段长时钟树里useful skew和sink type可能会打架。比如你把某个中间节点设成stop pin工具会围绕它做balance这时候如果你又对这个节点后面的leaf施加useful skew工具可能会因为stop pin的约束而无法实现你想要的skew。所以我的做法是先确定sink type再施加useful skew。如果某个leaf需要useful skew那它的上游节点最好设成through或者float给工具留出调整latency的空间。# 先设sink type set_ccopt_property sink_type through [get_pins mid_node/ck_in] # 再施加useful skew set_ccopt_property useful_skew -0.1 [get_pins leaf_reg/CK]4. 实操流程从spec编写到CTS signoff4.1 spec文件的编写要点Innovus的CTS spec文件是控制sink type的主要入口。我一般会把spec分成几个部分clock定义、sink type设置、balance group设置、以及exception设置。clock定义部分要写清楚每个clock的root、period、以及相关的generated clock。sink type设置部分按hierarchy来组织先设顶层的stop和through再设中间层的float最后设leaf的balance group。exception部分用来处理一些特殊情况比如某个pin需要单独设成exclude。# 示例spec结构 # 1. clock定义 create_ccopt_clock_tree -name func_clk -source [get_ports clk_in] set_ccopt_property target_skew 0.05 -clock_tree func_clk # 2. sink type设置 set_ccopt_property sink_type stop [get_pins cpu_cluster/ck_in] set_ccopt_property sink_type through [get_pins level_shift/ck_in] set_ccopt_property sink_type float [get_pins sub_module/ck_in] # 3. balance group set_ccopt_property balance_group cpu_grp [get_pins cpu_core/reg_*/CK] # 4. exception set_ccopt_property sink_type exclude [get_pins dft_scan_ck]4.2 CTS run的检查清单跑完CTS之后我一般会按以下顺序检查report_ccopt_clock_trees看每个tree的latency、skew、buffer数量。重点看latency有没有超过预算buffer数量有没有异常多。report_ccopt_skew_groups看每个balance group的skew。如果某个group的skew特别大检查它的sink type设置是不是有问题。check_ccopt_clock_tree_convergence看CTS有没有收敛。如果没收敛看log里的warning和error。report_ccopt_latency看每条path的latency分布。如果某条path的latency明显高于其他检查它的sink type是不是设成了stop但实际应该设through。4.3 ECO阶段的sink type调整CTS做完之后ECO阶段可能还需要调整sink type。比如某个模块的clock latency在post-CTS timing里成了critical path那就要考虑把它的sink type从stop改成through让工具在ECO时重新balance。ECO阶段调整sink type要注意不要一次性改太多。每次改一两个节点跑一次ECO看结果。因为sink type的改变会影响工具对整个tree的优化策略改多了容易导致ECO不收敛。# ECO阶段调整sink type set_ccopt_property sink_type through [get_pins critical_module/ck_in] ccopt_design -cts5. 常见问题与排查技巧实录5.1 sink type设了没生效先查这几项这是最常见的问题。你明明在spec里设了stop pin但report里显示工具还是往下trace了。排查顺序如下检查spec文件有没有被正确加载。Innovus里用ccopt_design -spec来加载spec如果路径写错了或者文件格式有问题spec会被忽略。检查pin的hierarchy path对不对。get_pins的path必须跟网表里的完全一致大小写敏感。检查有没有被后面的设置覆盖。比如你先设了stop后面又设了through那最终生效的是through。检查这个pin是不是被设成了exclude。exclude的优先级比stop高如果同时设了exclude生效。5.2 latency压不下去试试调整through pin的位置如果某条path的latency怎么都压不下去大概率是through pin的位置不对。我的经验是through pin应该设在路径的中间偏后位置而不是最前面。因为工具在through pin前面插的buffer会直接累加到latency上如果through pin太靠前前面的buffer级数就会很多。比如一条path有5个中间节点through pin设在第4个节点上那工具只需要在前4个节点之间插buffer级数可控。如果设在第1个节点上工具要在第1个节点前面插buffer来驱动后面所有的load级数就会爆炸。5.3 skew在跨domain边界变大检查balance group跨power domain的skew是分段长时钟树的经典难题。工具默认会把所有leaf放在一个balance group里但不同domain的leaf可能因为voltage不同而latency差异很大。这时候要把不同domain的leaf分到不同的balance group然后分别设target skew。但分group之后group之间的skew可能会变大。这时候可以用useful skew来补偿让latency大的group的target skew设小一点latency小的group设大一点这样group之间的skew就能压下来。5.4 常见问题速查表问题现象可能原因排查方法解决措施sink type设了没生效spec未加载/路径错误/被覆盖检查spec加载log确认pin path重新加载spec修正pathlatency过大through pin位置太靠前report_ccopt_latency看buffer分布把through pin往后移跨domain skew大balance group未分或分得太细report_ccopt_skew_groups按domain分group配合useful skewCTS不收敛sink type冲突check_ccopt_clock_tree_convergence减少float pin明确stop/throughECO后latency变差sink type改动太多对比ECO前后的report每次只改一两个节点提示CTS的sink type设置没有“万能模板”每个设计都要根据实际的clock结构、power domain划分、以及timing目标来调整。我的习惯是在项目初期多花点时间做sink type的探索把各种组合都试一遍找到最适合当前设计的方案后面ECO阶段就会轻松很多。6. 一些个人体会做CTS这些年我越来越觉得sink type的选择本质上是在工具优化自由度和结果可控性之间找平衡。stop pin和exclude pin给工具的自由度小结果可控但可能不是最优float pin给工具的自由度大结果可能更优但不可控through pin和leaf pin介于两者之间用好了能兼顾。分段长时钟树之所以难是因为它把这种平衡问题放大了。一条短clock path你随便设什么type工具都能给你balance得差不多。但一条长pathtype设错一个节点latency可能就差出几百个ps后面怎么调都调不回来。我自己的做法是在项目早期就用report_ccopt_clock_trees把每条长path的latency和buffer分布看清楚然后针对性地设sink type。不要等到CTS跑完了发现latency爆了再回头改那时候改的成本会高很多。另外spec文件一定要写得清晰、可追溯每个sink type的设置都要有注释说明为什么这么设这样后面ECO或者换人接手的时候不至于一脸懵。最后再分享一个小技巧如果你不确定某个节点该设什么type可以先设成float跑一次CTS看工具怎么处理。如果工具把它当成了sink那说明它后面的load不大设成stop可能更合适如果工具把它当成了through那说明它后面的load很大设成through让工具继续往下trace可能更好。这个方法我用了很多次基本上一次就能判断个八九不离十。