
先问你一个问题跑完Innovus的CTS之后你是不是在时钟树报告里见过类似_clock_gen_div32、_clock_gen_ck_xxx这种名字的skew group它们的member列表里躺着一堆分频器、ICG门控单元、时钟mux看起来好像很合理但很多人没意识到这个自动分组恰恰是很多莫名其妙时序违例的源头。我见过不少项目CTS之前setup还行CTS之后反而冒出来一堆hold violation查到最后发现元凶就是这个自动生成的clock_gen skew group——工具为了让某个分频时钟的前后级对齐强行把高频源时钟路径拉长结果插了一串delay buffer功耗面积上去不说时序还变差了。这篇文章我就把这个隐藏陷阱从头到尾拆一遍它是什么、为什么会坑你、怎么定位、怎么处理。1. 先搞懂_clock_gen skew group是什么、怎么来的1.1 从skew group的基本概念说起要理解这个问题先得清楚skew group在CTS里扮演什么角色。简单说skew group就是一组需要被时钟树综合引擎捆绑拉齐的sink点。工具在做时钟树时会以skew group为单位去平衡组内各点的clock latency让它们到达时间尽量一致。你可以把它理解成一群人要合影摄影师说这排人给我对齐skew group就是摄影师划定的那一排。Innovus在做时钟树综合时不会一个点一个点地单独平衡——那样效率太低而且有些点本来就不需要对齐。所以工具会依据时钟拓扑结构自动划分若干group。常见的group类型包括普通sink group就是寄存器时钟引脚组成的组、through pin group、以及咱们今天的主角clock_gen skew group。1.2 为什么工具要专门给clock gen逻辑建一组时钟生成逻辑clock gen logic指的是时钟路径上的再生单元分频器divider、门控时钟单元ICGintegrated clock gating cell、时钟mux、level shifter等等。这些单元的输入是上一级时钟输出是下一级时钟它们的工作方式决定了整个时钟网络的形态。工具为它们单独建skew group逻辑初衷是让进入这个单元之前的clock tree latency和从这个单元输出之后继续往下走的clock tree latency保持某种对齐关系。这样做的目的是保证分频/门控/选择动作发生时前后级时钟沿的关系是确定的避免出现毛刺、竞争或者时序判断错误。举个例子一个2分频器输入2GHz输出1GHz。工具希望时钟树到达分频器输入端的延迟和分频器输出端往下走到寄存器的延迟两者差距可控。于是它就把分频器的输入clock pin和分频器驱动的第一级寄存器的clock pin放进同一个skew group让CTS引擎去平衡。这个思路本身没错问题出在自动这两个字上。1.3 触发条件与默认行为哪些cell会被归进去在CCopt流程Innovus主推的并发时钟数据优化流程下工具默认会对时钟网络中的synchronous cell做识别。只要满足这个cell在时钟路径上且自身带clock pin参与时钟再生就有可能会被划入自动生成的clock_gen skew group。常见会被归进去的对象包括分频器/计数器类cell比如一个divider后面接了一堆状态寄存器ICG门控单元特别是门控时钟后再驱动大量寄存器的场景时钟mux功能时钟和测试时钟的切换点一些level shifter和isolation cell如果它们位于clock path上。这些cell聚在一起之后工具给这个group设定的目标通常是零偏差或者接近零偏差。成员越多、扇出差异越大、频率差异越大这个group对CTS引擎的约束力就越强代价也越大。2. 隐藏陷阱自动分组在哪些场景坑你2.1 低频分频时钟被高频源时钟拖下水这是最典型、也最疼的一个坑。想象这样一个场景PLL输出1GHz的主时钟经过一个32分频器得到约31.25MHz的低频时钟只驱动时钟域里的几十个寄存器。按说这个低频时钟树很好做sink少、负载小一两级buffer就能搞定。但是问题来了工具把分频器的输入时钟pin还在1GHz网络上和分频器输出驱动的寄存器clock pin放进了同一个_clock_gen skew group。为了让1GHz网络上的latency和31.25MHz网络上的latency对齐工具会怎么做它发现31.25MHz那边天然延迟很小1GHz那边哪怕不加任何buffer也太短了所以它才会去补不对往往情况是反过来的——为了把低频侧的延迟拉上来工具可能要在低频时钟网络上插delay buffer如果目标值是让两边相等而那1GHz网络本身已经很长低频侧就得加更多delay。但更常见的情况是另一种低频侧sink本来就少、latency天然偏短工具为了平衡被迫在1GHz源时钟路径上插入大量delay buffer把整个主时钟树拉长。这样一来1GHz时钟域的setup裕量直接受损因为叶子时钟到达时间整体变晚了。这种情况我真实遇到过一个简单的32分频时钟工具为了balance居然在主时钟网络上多插了将近1.5ns的delay直接导致该时钟域下所有路径的setup全面恶化。2.2 高扇出mux被强制拉齐时钟mux是另一个重灾区。芯片里几乎都有功能时钟和测试时钟的切换mux还有DFS动态调频场景下不同PLL输出之间的切换。工具看到这个mux的输入是两路时钟、输出接负载就自动把它归入clock_gen skew group要求两条输入路径的latency对齐。听起来也不算错但问题是功能模式下可能只走其中一路时钟另一路完全不用测试模式下才走另一路。这两条路径根本不应该被同时拉齐因为它们的balance需求分属不同mode强行拉齐的结果是让两条路径都变得又长又绕还没换来任何实际收益。尤其是当mux靠近时钟末端、某一路输入是本地直连、另一路是从很远的地方绕过来的情况下自动分组会强迫工具做大量工作去弥补拓扑差异。这个差异是物理距离决定的不是插buffer就能完美解决的。2.3 跨时钟域被包办婚姻还有一种情况一个clock gen cell同时为两个异步时钟域提供时钟。比如一个分频器同时输出两路时钟一路给A域一路给B域A域和B域之间没有任何同步关系。工具可不管这些它只要看到这个cell的输出同时驱动两边就把两边的sink都拉进同一个clock_gen skew group。于是两个本来各自独立的时钟树被强制做了balance。异步路径本来不需要满足时序关系自动分组这一波操作等于包办婚姻让CTS引擎在两组之间做无意义的平衡工作。最后的结果是时钟树变大、congestion变差、hold问题变多而这一切本可以用一个clock group设置就避免。2.4 版本升级后默认行为漂移这个坑更隐蔽。Innovus不同版本对clock_gen skew group的默认策略有过调整。老版本SoC Encounter时期CTS flow用auto_skew_group这个开关控制到了CCopt流程后相关属性、默认值、group命名规则都有变化。同一个设计从旧版本升级到新版本你会发现CTS结果对不上skew group名单也不一样。这不是bug是工具在智能化演进过程中调整了行为。但对项目来说这种漂移非常致命——你上一个项目好不容易收敛好的约束和配置换了个版本后全部要重新验证。所以如果你在版本升级后发现时钟树结果出现不合理的变化别急着怀疑自己的约束先翻一翻自动生成的skew group列表。3. 诊断与定位怎么快速发现不合理的自动分组3.1 先把自动生成的group拉出来看处理任何问题第一步永远是看清现状。在Innovus里查看skew group的方式很简单你可以用以下命令report_ccopt_skew_group -verbose get_skew_groups -type clock_gen第一条命令会输出比较详细的分组报告包括每个group的成员、类型、目标要求等信息。第二条命令专门用来筛选clock_gen类型的group。跑完之后你会看到类似这样的输出group名字、group类型、成员数量和具体成员列表。我的习惯是CTS每一轮结束先把这份报告导出来逐个group过一遍。重点看两件事——这个group的成员是不是都来自同一个时钟域这些成员之间是不是真的有对齐需求如果答案是否定的这个group就是可疑对象。3.2 用latency数据验证是不是被过度balance了光看分组还不够你得确认这个自动分组到底有没有造成实际伤害。方法是看时钟路径上的延迟分布。你可以用report_clock_timing -type latency去查每个sink点的clock latency也可以直接看clock tree报告里的insertion delay。重点关注clock gen cell输入端的latency。比如分频器的输入clock pin上它的latency如果明显大于该时钟域普通寄存器clock pin的latency说明工具在这里做了额外的delay插入。这时候再对比一下手动去掉自动分组之后的理论延迟就能确认问题有多大。有些版本还支持按skew group单独报时序你可以把某个可疑group单独拉出来看它的内部skew和外部分布这样更容易定位是哪个group在拖着整体时钟质量后腿。3.3 真实案例复盘一个32分频模块的hold violation之谜讲一个我自己真实排查过的案例。某个低功耗MCU子系统要求32kHz实时时钟由32MHz系统时钟分频得到。CTS跑完报告里出现一串hold violation全部集中在32kHz时钟域的寄存器上而且violation的绝对值不小有接近200ps。刚开始我以为是约束没写全检查了时钟定义、不确定度、异步路径设置都没问题。后来把report_ccopt_skew_group -verbose一拉发现了一个_clock_gen_div32的skew group成员包括32MHz到divider输入端的路径、divider输出端到32kHz第一级寄存器的路径、甚至还包括了divider内部逻辑的pin。工具为了让32kHz时钟域和32MHz时钟树对齐在32MHz网络上给divider输入路径加了一堆delay buffer把那段路径延迟拉上去。结果32MHz域本身的时序余量被压缩同时32kHz域内部也因为工具尝试做极端balance而产生了大量虚假hold问题。处理方案后面会细说但当时的结论很清楚这个自动生成的group是多余的divider前后级根本不需要如此强制的对齐只要保证分频器输入时钟质量满足要求、输出树平滑展开就够了。3.4 实操怎么快速选中和分析特定clock gen单元在分析过程中你经常需要快速定位某个特定cell。举个搜索热词里的例子如果设计里有一个标准单元名是biasnw你想找到它、看它的pg term连接情况可以用下面这些方法。在Innovus的Tcl环境下最简单的方式是用dbGet命令dbGet [dbGet top.insts.name biasnw*] -p .pgHeaders.name这条命令可以列出所有名字以biasnw开头的instance的power/ground header信息。如果你想拿到具体的pg term比如VDD、VSS可以这样dbGet [dbGet top.insts.name biasnw*].pgTerms.name如果你用的是ccopt/Genus那条流程的混合约束文件也可以用get_cells加通配符来操作get_cells -hier -filter name ~ biasnw*拿到cell之后再用get_pins -of_objects [get_cells ...]去查具体的pin或者直接在GUI里用gui_select -inst [dbGet top.insts.name biasnw*]高亮选中。这个小技巧在分析clock gen cell的pg连接时特别有用——很多时候时钟生成单元的pg电压域不对会导致分频器输出沿有偏差进而让自动分组的平衡结果变得不可信。4. 处理与修正如何避免或调整自动分组4.1 全局关闭自动分组如果经过排查确认设计里大部分自动生成的clock_gen skew group都不合理最直接的办法是全局关掉这个功能。在Innovus CCopt流程下相关开关大致是set_ccopt_property auto_skew_group false部分版本或者老流程里对应的写法是set_clock_tree_options -auto_skew_group false需要注意不同大版本之间具体的property名称有差异建议先跑一下set_ccopt_property -help或者查一下当前版本的User Guide里关于skew group auto generation的说明确认拼写。全局关闭的代价是如果确实有某些clock gen逻辑需要做前后级对齐工具就不会再自动处理了。所以这个方法适合绝大多数自动分组都是干扰项的设计关闭之后你再手工人为建立必要的group。4.2 针对单个cell或group单独摘除有些时候全局关闭太粗暴因为设计里确实存在一两个需要对齐的clock gen单元。这种场景下更好的办法是把指定cell从自动分组中排除或者直接忽略某个自动生成的group。Innovus提供了类似set_skew_group -ignore或者对cell设置skip属性这样的手段。具体到CCopt流程有一些常见的策略性设置比如set_ccopt_property -pin clock_pin balance_pin false set_ccopt_property -cell cell_name cts_skew_group_ignore true这些命令的作用是告诉工具某个pin、某个cell不参与skew group的自动构建。设置完成之后重新跑CTS工具会重新生成时钟树不再把你指定的对象纳入自动分组。这种外科手术式的处理方式在项目后期收敛阶段特别好用——不会像全局关闭那样引入大量不确定性又能精准消除有害的自动分组。4.3 手动建立真正有意义的skew group去掉不合理的自动分组之后你不能直接撒手不管。有些位置确实需要balance你要自己建group来控制。比如分频器输入和第一级输出之间如果确实需要对齐可以手动指定create_skew_group -name div32_req_align -sinks {divider_in_pin div_out_first_reg_clock_pin}需要注意的是手动建group的时候成员范围一定要克制。我见过有人手动建的group比工具自动建的还大那等于换了一种方式制造同样的问题。原则上只有那些数据路径上存在跨时钟域同步要求、或者逻辑功能上依赖时钟沿对齐的位置才值得放进同一个手动skew group。如果手头有MMMCmulti-mode multi-corner约束你还可以针对不同mode设置不同的skew group策略。功能模式下不需要对齐的在测试模式下可能需要对齐分开处理才能两全。4.4 调整CTS之后的平衡策略除了在CTS之前干预分组你还可以在CTS完成之后做增量修正。比如发现某个group导致整体latency过大的时候可以尝试对group做拆分或者调整group的target skew、insertion delay等目标值。Innovus CCopt里对单个skew group有一些细化控制能力比如设定组内最大skew、组间latency差等。具体命令因版本而异你可以查一下set_skew_group或者set_ccopt_property看有没有针对group的属性设置。不过我的建议是尽量在CTS早期把分组策略定好不要依赖后期修补。后端工程的常识是越早解决的问题成本越低CTS的skew group策略直接影响时钟树形态后期硬调往往是按下葫芦浮起瓢。5. 常见问题排查与决策清单5.1 一张对照表帮你快速定位我把实际项目中process过的高频问题整理成一张速查表遇到类似现象直接按图索骥现象可能原因处理方向CTS后低频时钟域出现大量hold violation低频时钟被自动分组拉扯内部balance过度检查_clock_gen group摘除多余成员或忽略自动组高频主时钟域的setup margin大幅下降源时钟路径被插入过量delay以平衡分频器前后级去掉对divider输入clock pin的自动分组约束测试模式下时钟树异常shift路径违例功能时钟与测试时钟mux被放到同一skew group强制对齐区分mode设计skew group策略必要时忽略自动组时钟树congestion变差buf/inv数量暴增自动分组跨异步域做无意义平衡用clock group/async设置明确domain关系摘除跨域成员版本升级后CTS结果突变新版本自动skew group策略不同对比新旧版本skew group报告显式声明关键分组5.2 命令速查手册以下是我在多个项目里反复使用的命令集合建议直接收藏# 查看所有skew group report_ccopt_skew_group -verbose # 只看clock_gen类型的自动分组 get_skew_groups -type clock_gen # 全局关闭自动skew group set_ccopt_property auto_skew_group false # 忽略特定cell参与skew group set_ccopt_property -cell cell_name cts_skew_group_ignore true # 手动建立skew group create_skew_group -name group_name -sinks {pin1 pin2} # 快速选中特定名字的cell并查看pg term dbGet [dbGet top.insts.name biasnw*].pgTerms.name这些命令在不同版本里细节可能有差异请在真实环境中先用-help确认。但排查思路完全通用。5.3 我的判断原则和实操心得踩过这么多次坑之后我给自己定了一个判断标准每次看自动生成的clock_gen skew group就问三个问题第一这个group的成员是不是都来自同一个functional clock domain如果不是大概率是工具误判需要摘除。第二这些成员之间的时序关系是否真的有真实路径依赖如果只是拓扑上共享一个gen cell、但数据路径完全异步那就没必要平衡。第三这个group的balance目标值是不是过分激进比如对异步跨域也要求零skew这就是在制造问题。只要这三个问题里有一个是不我就会手动干预。实操中处理完不合理的自动分组之后一定要重新看一遍时钟树QoR报告skew有没有变化、latency是否合理、整体cell面积有没有降下来。我的经验是这个操作通常在latency和area上都会有明显收益setup/hold违例也会减少。最后分享一个小技巧在跑CTS之前先主动按时钟域把skew group的设计意图写进约束文件。把明显不该balance的点提前排除把真正需要对齐的点显式定义成group。这样主动权握在自己手里而不是等工具的自动策略来替你做决定。你要知道工具再智能也不清楚你的时钟架构里哪些逻辑是异步的、哪些路径是测试专用的——这些信息只有设计者自己最清楚。