先问你一个问题:跑完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)、门控时钟单元(ICG,integrated 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。
如果手头有MMMC(multi-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。这样主动权握在自己手里,而不是等工具的自动策略来替你做决定。你要知道,工具再智能,也不清楚你的时钟架构里哪些逻辑是异步的、哪些路径是测试专用的——这些信息只有设计者自己最清楚。