数字IC后端CTS实战:Skew Group划分与引擎优化指南
数字IC后端实现里时钟树综合CTS是那种做好了没人夸、做砸了全流程返工的环节。我做过几个从RTL到GDSII全流程的项目也在28nm、16nm、7nm几个节点上踩过CTS的坑最深的体会是Skew Group的划分方式直接决定了CTS引擎的优化空间和最终时序收敛的难度。很多人跑CTS就是打开工具、加载CTS脚本、点run结果hold修不完、clock latency大得离谱、OCV余量被吃掉一大截回头查半天发现是Skew Group没配对。这篇内容就是把我自己在实际项目中关于Skew Group配置和CTS引擎优化的经验整理出来从概念到脚本到debug尽量讲透。适合已经接触过数字后端流程、正在做CTS或者准备面试的工程师参考也适合想理解CTS底层逻辑的验证和前端同学。1. 先搞清楚Skew Group到底在管什么1.1 从时钟树的结构说起时钟树综合的本质是把一个时钟源点clock root通过缓冲器buffer和反相器inverter组成的树形网络扇出到成千上万个时序单元的时钟端口clock pin。理想情况下所有clock pin应该在同一时刻收到时钟沿但物理实现上不可能——线长不同、负载不同、buffer延迟不同到达时间必然有差异。这个差异就是skew。CTS引擎在优化时并不是把所有clock pin当成一个整体来平衡。它会把时钟域内的sink点划分成若干个组每个组内部做skew平衡组与组之间允许存在一定的偏移。这个组就是Skew Group。你可以把它理解成CTS引擎的优化单元——引擎在每个Skew Group内部尽量把skew压到目标值以内而跨组的偏移则由你通过配置来控制。为什么要有这个概念因为实际设计里不是所有寄存器都需要严格对齐。比如一个模块内部的寄存器之间需要严格同步但模块A和模块B之间可能通过异步FIFO或握手信号交互它们的时钟到达时间差个几百皮秒完全没问题。如果强行把所有sink拉到同一个skew目标CTS引擎会插入大量buffer去平衡那些本来不需要平衡的路径面积、功耗、latency全部恶化。Skew Group就是让你告诉引擎这几组寄存器要严格对齐那几组可以松一点。1.2 Skew Group和Clock Domain的区别这是新手最容易混淆的地方。Clock Domain是逻辑层面的概念由时钟定义决定——同一个时钟源驱动的所有寄存器属于同一个clock domain。Skew Group是物理实现层面的概念是你在CTS阶段人为划分的优化分组。一个clock domain可以包含多个Skew Group一个Skew Group也可以跨多个clock domain虽然不常见。举个例子一个CPU core的时钟域里整数执行单元和浮点执行单元可能被划成两个Skew Group因为它们之间的时序路径很少不需要严格对齐但每个单元内部的寄存器必须在同一个Skew Group里保证内部skew可控。我见过有人把整个clock domain当成一个Skew Group来跑CTS结果clock tree上挂了上万個sink引擎优化时收敛极慢而且为了平衡一些根本不相关的路径插了一堆buffer。后来拆成四个Skew Grouplatency降了15%buffer数量少了20%。1.3 什么时候需要拆Skew Group不是所有设计都需要精细划分Skew Group。以下几种情况建议拆跨时钟域交互少的模块比如一个SoC里CPU子系统、GPU子系统、外设子系统各自独立交互通过总线桥接这种天然适合拆成不同Skew Group。频率差异大的区域高频区域对skew敏感低频区域可以放宽拆开后高频区域能获得更多优化资源。物理位置分散的sink如果两个模块在floorplan上离得很远强行放一个Skew Group里CTS引擎会为了平衡它们之间的skew插入长线buffer反而恶化latency。特殊时序要求比如某些寄存器需要做clock gatinggating cell的时钟到达时间有特殊要求可以单独成组。反过来如果设计规模不大比如几万instance的小芯片或者所有模块交互都很紧密那可能一个Skew Group就够了拆多了反而增加配置复杂度。2. Skew Group的配置参数与脚本实战2.1 核心参数target skew和target latency配置Skew Group时最核心的两个参数是target skew和target latency。Target skew是你希望组内clock pin之间的最大到达时间差。这个值不是拍脑袋定的要根据时钟周期和工艺节点来算。经验公式是target skew ≤ 时钟周期的5%~10%。比如1GHz时钟周期1nstarget skew可以设在50~100ps。7nm以下节点由于OCV影响更大建议压到5%以内。Target latency是clock root到sink的平均延迟。这个值影响插入延迟insertion delay和时钟树功耗。latency设得越小CTS引擎会插更少的buffer但可能skew收敛难度增加。一般设成时钟周期的10%~20%比较合理。在Innovus里配置命令大概是这样的# 创建Skew Group create_ccopt_clock_tree -name cpu_core_clk -source [get_pins clk_gen/CLKOUT] \ -skew_group cpu_core_sg # 设置target skew和latency set_ccopt_property -skew_group cpu_core_sg target_skew 80ps set_ccopt_property -skew_group cpu_core_sg target_latency 300ps # 把sink加入Skew Group set_ccopt_property -skew_group cpu_core_sg -sinks [get_pins -hier */CLK]在ICC2里对应的是create_clock_tree -name cpu_core_clk -source [get_pins clk_gen/CLKOUT] set_clock_tree_options -clock_tree cpu_core_clk -target_skew 80ps set_clock_tree_options -clock_tree cpu_core_clk -target_latency 300ps注意不同工具对Skew Group的称呼和命令不一样Innovus叫ccopt skew groupICC2叫clock tree但概念是通的。2.2 用sink type区分leaf和non-leafSkew Group里的sink分两类leaf sink寄存器的clock pin和non-leaf sink比如clock gating cell、分频器、其他时钟树的root。这两类sink的优化策略不同。Leaf sink是CTS优化的主要目标引擎会尽量平衡它们之间的skew。Non-leaf sink则需要特殊处理——比如clock gating cell的时钟到达时间会影响gating后的时钟树如果gating cell的clock pin skew太大可能导致gating后的寄存器出现毛刺或时序问题。在配置时可以用sink type来区分# 只对leaf sink做skew平衡 set_ccopt_property -skew_group cpu_core_sg -sink_type leaf # non-leaf sink单独设置 set_ccopt_property -skew_group cpu_core_sg -sink_type non_leaf \ -target_skew 50ps我一般会把non-leaf sink的target skew设得比leaf更严因为它们的偏移会传递到下游时钟树放大误差。2.3 跨Skew Group的balance配置多个Skew Group之间如果需要保持一定的相位关系可以用balance配置。比如两个Skew Group共享同一个时钟源你希望它们的latency尽量接近避免跨组路径的时序余量被吃掉。# 设置两个Skew Group之间的balance set_ccopt_property -skew_group cpu_core_sg -balance_group cpu_balance set_ccopt_property -skew_group gpu_core_sg -balance_group cpu_balance这样CTS引擎会在优化时同时考虑两个组的latency尽量让它们对齐。但要注意balance group会增加引擎的优化复杂度如果两个组的sink数量差异很大收敛时间会明显增加。2.4 一个完整的CTS脚本框架下面是我在一个16nm项目里用的CTS脚本框架简化后分享出来# 1. 时钟定义检查 check_clock_tree -clock_tree all # 2. 创建Skew Group create_ccopt_clock_tree -name sys_clk -source [get_pins pll/CLKOUT] create_ccopt_clock_tree -name cpu_clk -source [get_pins clk_div/CLKOUT] # 3. 配置Skew Group参数 set_ccopt_property -skew_group sys_clk_sg target_skew 100ps set_ccopt_property -skew_group sys_clk_sg target_latency 400ps set_ccopt_property -skew_group cpu_clk_sg target_skew 60ps set_ccopt_property -skew_group cpu_clk_sg target_latency 250ps # 4. 指定sink set_ccopt_property -skew_group sys_clk_sg -sinks [get_pins -hier \ -filter full_name~*sys_domain* */CLK] set_ccopt_property -skew_group cpu_clk_sg -sinks [get_pins -hier \ -filter full_name~*cpu_domain* */CLK] # 5. 设置CTS引擎选项 set_ccopt_property -skew_group sys_clk_sg -useful_skew true set_ccopt_property -skew_group cpu_clk_sg -useful_skew true # 6. 运行CTS ccopt_design -cts # 7. 检查结果 report_ccopt_clock_trees -file cts_report.rpt report_ccopt_skew_groups -file skew_report.rpt这个脚本里有个关键点useful skew。开启后CTS引擎会利用时序余量来调整skew而不是一味追求零skew。比如某条路径setup余量很大引擎可以故意让这条路径的sink时钟晚到一点把余量让给hold紧张的路径。这个功能在时序紧张的设计里非常有用但前提是你的时序约束要准确否则引擎会优化出错误的方向。3. CTS引擎的优化策略与底层逻辑3.1 引擎是怎么做skew平衡的CTS引擎的优化过程大致分三步聚类、拓扑生成、缓冲器插入与 sizing。聚类阶段引擎会根据sink的物理位置和时钟需求把它们分成若干簇cluster。每个簇内的sink会被尽量放在一起减少后续布线长度。聚类算法通常基于几何距离和时序权重——时序紧张的sink会被优先聚在一起。拓扑生成阶段引擎会构建一棵树形结构从root到各个簇。树的结构直接影响latency和skew——平衡树balanced tree可以让各路径延迟接近但可能增加总线长H-tree在规则布局下效果好但实际设计里sink分布不规则H-tree往往不是最优。缓冲器插入与sizing阶段引擎会在树的节点上插入buffer并调整buffer的尺寸drive strength。这里有个权衡大尺寸buffer驱动能力强、延迟小但功耗和面积大小尺寸buffer省功耗但可能需要多级级联增加latency。我实测过一个对比同一个设计用大尺寸buffer少级数 vs 小尺寸buffer多级数前者latency低15%但功耗高20%。最终选了折中方案latency和功耗都在可接受范围。3.2 Useful Skew的利与弊Useful skew是CTS引擎最强大的功能之一但也是最容易出问题的。它的原理是利用时序路径的余量故意让某些sink的时钟早到或晚到从而改善整体时序。举个例子一条路径从寄存器A到寄存器Bsetup余量有200pshold余量只有20ps。如果CTS引擎让B的时钟晚到100ps那么A到B的setup余量变成100ps仍然够但hold余量变成120ps大幅改善。这就是useful skew的价值。但问题在于引擎判断余量依赖时序约束的准确性。如果SDC里有多周期路径multicycle path或虚假路径false path没设对引擎会误以为某些路径有余量做出错误的skew调整。我踩过一次坑一个跨时钟域路径没设false path引擎以为有余量把时钟调得乱七八糟后来修SDC重新跑CTS才解决。另外useful skew会增加时钟树的不确定性对OCV分析不利。所以我的建议是在时序紧张的设计里开启useful skew但必须确保SDC干净在时序宽松或安全性要求高的设计里可以关闭用保守的零skew策略。3.3 多源时钟树的处理实际设计里一个时钟树可能有多个源点——比如一个时钟经过MUX选择后驱动下游或者多个PLL输出汇聚到一个时钟网络。这种多源时钟树的CTS处理更复杂。引擎需要先确定每个源点的驱动范围然后在源点之间做平衡。如果两个源点的时钟频率不同引擎会把它们当成独立的Skew Group处理如果频率相同但相位不同则需要配置phase关系。我遇到过一个case两个PLL输出同频但相位差180度CTS时没配置phase关系引擎把它们当成独立时钟树优化结果下游寄存器采样出错。后来在SDC里加了set_clock_groups -asynchronous并在CTS配置里指定了phase关系问题解决。3.4 时钟树上的clock gating处理Clock gating cell在CTS里是个特殊存在。它既是上游时钟树的sink又是下游时钟树的source。CTS引擎需要保证gating cell的clock pin skew可控同时gating后的时钟树也要满足skew要求。处理方式通常有两种把gating cell当成non-leaf sink单独设skew目标或者把gating后的寄存器划到独立的Skew Group。我一般用前者因为gating后的时钟树通常规模不大单独成组反而增加配置复杂度。但要注意gating cell的enable信号时序也很关键。如果enable信号在时钟沿附近变化可能导致gating后的时钟出现毛刺。CTS阶段虽然不直接优化enable路径但可以通过调整gating cell的时钟到达时间来间接改善。我的经验是让gating cell的时钟比下游寄存器的时钟早到一点这样enable信号有更充裕的稳定时间。4. 实测中的典型问题与排查链路4.1 Skew收敛不了从sink分布查起CTS跑完发现某个Skew Group的skew远大于目标值比如目标80ps实际200ps。这时候别急着调参数先查sink分布。用report_ccopt_skew_groups -verbose看每个sink的到达时间找出偏离最大的那些。常见原因有几个sink物理位置过于分散如果最远和最近的sink距离超过500umCTS引擎很难在合理latency内平衡。解决办法是拆Skew Group或者调整floorplan让相关sink靠近。sink负载差异大有些sink的clock pin电容大比如大尺寸寄存器有些小引擎需要插不同尺寸的buffer来匹配如果差异太大skew收敛困难。可以在CTS前做clock pin的load balancing或者手动调整关键sink的驱动。时钟树上有高扇出net如果某个节点扇出超过50引擎的buffer插入策略可能不够优化。可以手动约束max fanout让引擎提前规划。我遇到过一次一个Skew Group里混了两种寄存器一种clock pin电容是另一种的3倍skew怎么都收敛不了。后来把这两种寄存器拆到不同Skew Group各自设不同的target skew问题解决。4.2 Latency过大buffer级数失控Latency过大通常意味着时钟树上的buffer级数太多。用report_ccopt_clock_trees -latency看每级buffer的延迟贡献找出瓶颈。常见原因target latency设得太小引擎为了满足latency目标拼命插buffer反而增加了级数。这时候要放宽target latency让引擎有更多优化空间。buffer尺寸选择不当如果引擎用了太多小尺寸buffer级数会增加。可以约束buffer的可用尺寸范围让引擎优先用大尺寸buffer。布线绕行如果时钟树布线绕了远路线延迟会增加。检查floorplan和placement确保时钟树路径畅通。我的经验是target latency不要设得比实际可达值小太多。可以先跑一次CTS看实际latency是多少然后设成实际值的90%左右给引擎一点优化压力但又不至于失控。4.3 Hold违例修不完CTS和后续流程的配合CTS跑完hold违例一大堆修了半天修不完。这时候要回头看看CTS阶段是不是留了太多余量给hold。Hold违例的根本原因是时钟到达时间差不够。如果CTS阶段把skew压得很小hold余量自然就少。解决办法有两个一是CTS阶段适当放宽skew目标给hold留余量二是开启useful skew让引擎主动调整时钟到达时间来改善hold。但要注意useful skew改善hold的前提是setup有余量。如果setup本身就很紧useful skew可能顾此失彼。我一般会在CTS前先跑一次时序分析看看setup和hold的余量分布再决定useful skew的策略。另外CTS后的hold修复通常用insertion delay或buffer插入但这些操作会改变时钟树可能影响skew。所以hold修复最好在CTS阶段就规划好而不是留到后面。4.4 跨时钟域路径的skew问题跨时钟域路径的skew问题往往被忽视。如果两个时钟域的时钟到达时间差太大跨域路径的setup和hold都会受影响。处理方式在CTS阶段配置balance group让两个时钟域的latency尽量接近。如果两个时钟域频率不同无法完全balance则需要在SDC里正确设置clock group关系并在时序分析时单独检查跨域路径。我见过一个设计两个时钟域频率比是2:1CTS时没做balance结果跨域路径的hold违例特别多。后来在CTS配置里加了balance group虽然不能完全消除skew但把跨域路径的余量改善了30%。5. 进阶CTS与PR流程的协同优化5.1 Placement阶段就要为CTS做准备CTS的质量很大程度上取决于placement阶段。如果placement时寄存器摆得乱七八糟CTS再怎么优化也救不回来。我的做法是在placement阶段就设置clock-aware的约束。比如用set_clock_tree_options -clock_aware_placement让工具在摆放寄存器时考虑时钟树的需求把同一Skew Group的寄存器尽量摆在一起。另外可以用create_clock_tree -sink_type提前标记关键sink让placement阶段优先处理。在Innovus里可以用ccopt_design -place在placement阶段就做初步的时钟树规划这样CTS阶段收敛更快。5.2 Routing阶段的时钟树保护CTS后的routing阶段时钟树网络需要特殊保护。因为时钟树对线延迟敏感如果routing时走了远路或串扰严重skew会恶化。常用手段设置non-default ruleNDR给时钟树网络设置更宽的线宽和更大的间距减少电阻和串扰。屏蔽层shielding在时钟线两侧加接地屏蔽线减少串扰。限制layer让时钟树走高层金属电阻小、延迟稳定。我一般会给时钟树设置双倍线宽和双倍间距的NDR虽然面积会增加但skew和latency的稳定性明显提升。5.3 Post-CTS的时序修复策略CTS后的时序修复要分优先级先修setup再修hold。因为setup违例影响功能hold违例影响可靠性但setup修复可能会改变时钟树进而影响hold。修复setup时优先用useful skew和buffer sizing尽量避免动时钟树结构。修复hold时可以用insertion delay或小尺寸buffer但要注意不要破坏skew。另外Post-CTS的时序修复要设置合理的margin。因为后续还有routing和signoff如果Post-CTS阶段把时序修得太紧routing后可能又出现违例。我一般会留10%~15%的余量给后续流程。5.4 一个完整的CTS-PR协同流程总结一下我在实际项目中用的流程Placement阶段开启clock-aware placement初步规划时钟树。Pre-CTS时序分析检查setup和hold余量确定useful skew策略。CTS配置划分Skew Group设置target skew和latency配置balance group。CTS运行先跑一次无useful skew的CTS看baseline再开useful skew优化。Post-CTS时序分析检查skew、latency、setup、hold定位问题。时序修复先修setup再修hold留余量给后续流程。Routing设置NDR和shielding保护时钟树。Post-Route时序分析检查最终时序确认skew和latency在目标范围内。这个流程不是固定的要根据设计规模和时序难度调整。比如小设计可以跳过Pre-CTS分析大设计可能需要多轮CTS迭代。6. 几个容易被忽视的细节6.1 Clock pin的load balancingCTS前做clock pin的load balancing可以显著改善skew收敛。具体做法是检查所有sink的clock pin电容如果差异超过2倍考虑插入dummy buffer或调整驱动来平衡。在Innovus里可以用report_ccopt_clock_trees -load查看每个sink的负载。如果发现某些sink负载特别大可以在CTS前手动插buffer。6.2 时钟树的power优化时钟树是芯片功耗的大头通常占动态功耗的20%~40%。CTS阶段可以通过以下方式优化功耗减少buffer数量在满足skew和latency的前提下尽量少插buffer。用低功耗buffer如果工艺库里有低功耗buffer优先选用。clock gating在CTS阶段合理插入clock gating cell减少不必要的时钟翻转。但要注意clock gating会引入额外的skew和latency需要在功耗和时序之间权衡。6.3 多corner下的CTS先进工艺节点下CTS需要在多个corner下验证。比如SS corner下latency大FF corner下latency小如果只在一个corner下优化另一个corner可能出问题。我的做法是在typical corner下做CTS优化然后在SS和FF corner下检查skew和latency。如果差异太大需要在CTS配置里设置corner-aware的约束让引擎在优化时考虑多个corner。6.4 CTS报告的解读CTS跑完会生成一堆报告重点看这几个skew report看每个Skew Group的实际skew和目标值的差距。latency report看clock root到sink的平均延迟和最大延迟。buffer report看插了多少buffer各级buffer的尺寸分布。violation report看有没有skew或latency违例。我一般会把这些报告和Pre-CTS的时序报告对比看看CTS对时序的改善程度。如果改善不明显说明CTS配置有问题需要调整。7. 面试中常被问到的CTS问题7.1 Skew和Jitter的区别面试高频题。Skew是空间上的到达时间差——不同sink在同一时钟沿的到达时间差异。Jitter是时间上的不确定性——同一sink在不同时钟周期的到达时间波动。Skew可以通过CTS优化减小Jitter主要由PLL和时钟源决定CTS只能间接改善。7.2 为什么CTS后要做hold修复CTS后时钟树确定了寄存器之间的时钟到达时间差也确定了。如果这个差值太小hold违例就会出现。Hold修复的本质是增加时钟到达时间差或者增加数据路径延迟。CTS阶段可以通过useful skew主动调整Post-CTS阶段则用buffer插入或delay cell。7.3 Useful skew的风险Useful skew的风险主要有三个一是依赖SDC准确性SDC错了引擎会优化错方向二是增加时钟树不确定性对OCV分析不利三是可能顾此失彼改善了hold但恶化了setup。所以用useful skew要谨慎必须配合完整的时序分析。7.4 如何选择target skewTarget skew的选择要综合考虑时钟周期、工艺节点、设计规模。一般规则是时钟周期的5%~10%先进节点取小值成熟节点取大值。另外要考虑OCV余量如果OCV影响大target skew要设得更小。8. 写在最后CTS这个环节工具能帮你做很多事但工具不知道你的设计意图。Skew Group怎么划、target skew怎么设、useful skew开不开这些决策依赖你对设计的理解。我见过太多人把CTS当成一个跑一下就行的步骤结果后面修时序修到崩溃。我自己的习惯是CTS前花半天时间分析时钟结构和时序余量把Skew Group划分和参数配置想清楚CTS跑完后花半天时间看报告、定位问题。这比反复跑CTS、反复修时序效率高得多。另外CTS的经验很难从文档里学到更多是从项目里踩坑踩出来的。每次CTS出问题别急着调参数先搞清楚问题的根因——是sink分布问题、约束问题、还是引擎配置问题。搞清楚根因下次就不会再踩同样的坑。