1. 分段时钟网络为什么让人头疼
做数字后端这行的,估计没几个人没被时钟树平衡折磨过。尤其是碰到那种规模不大不小、但时钟结构特别拧巴的设计——比如一个模块内部有好几个时钟域,每个域又只驱动一小撮寄存器,工具默认的时钟树综合策略一跑,要么是插入延迟大得离谱,要么是几个分支之间怎么都平衡不了,最后时序报告一片红。我印象特别深的是之前做过一个带传感器接口的芯片,里面有个低速时钟域只驱动二十几个触发器,但工具硬是给它建了一棵完整的时钟树,buffer插了一长串,skew倒是压下去了,可功耗和面积全上去了,而且跟主时钟域之间的平衡关系反而更难收敛。
这种场景下,分段时钟网络的概念就派上用场了。所谓分段,就是把一个大的时钟树拆成几个相对独立的子网络,每个子网络单独做综合和平衡,最后再在顶层做整体协调。这样做的好处很明显:局部时钟树的规模小了,工具更容易找到最优解,插入延迟可控,功耗也更友好。但问题在于,Innovus默认会把所有时钟引脚一视同仁地纳入全局时钟树综合,它不知道你其实想把某几个分支单独拎出来处理。这时候就需要用到stop pin和ignore pin这两个看似不起眼、实则威力巨大的设置。
这篇文章主要面向已经有一定Innovus使用经验的数字后端工程师,尤其是那些正在被复杂时钟结构困扰、想找到更精细控制手段的人。我会从这两个概念的本质讲起,结合我实际项目中的操作步骤,把怎么设、为什么这么设、设完之后怎么验证,一步步拆开来说。如果你之前只是听说过这两个词但没真正用过,或者用过但效果不理想,那接下来的内容应该能帮你省下不少试错时间。
2. stop pin与ignore pin的本质区别
2.1 从时钟树综合的视角理解这两个概念
Innovus在做时钟树综合的时候,会把时钟源到所有时钟端点之间的路径全部纳入考虑。这里的时钟端点通常包括触发器的时钟引脚、时钟门控单元的时钟输入、以及一些宏单元的时钟输入。工具会根据这些端点的分布和约束,自动决定buffer的插入位置、数量以及连线方式。
stop pin的作用是告诉工具:时钟树综合到这里就停了,不要再往这个引脚的下游继续建树。换句话说,stop pin本身仍然会被纳入时钟树,工具会保证它被正确地驱动和平衡,但它下游的电路不会被当作时钟树的一部分来处理。这在你想要把某个分支单独拿出来做局部时钟树的时候特别有用。
ignore pin则更彻底一些:工具直接忽略这个引脚,既不把它当时钟端点,也不往它下游建树。这个引脚在时钟树综合阶段会被当作普通信号处理,或者完全不被纳入时钟网络的分析范围。ignore pin通常用在那些你确定不需要做时钟树平衡、或者下游电路有时钟树但你想在后续阶段单独处理的场景。
两者的核心区别可以用一句话概括:stop pin是“到此为止,但我在树上”,ignore pin是“我不在树上,别管我”。理解这一点,后面所有的操作逻辑就都顺了。
2.2 什么时候该用stop pin,什么时候该用ignore pin
这个问题我在实际项目中反复被问到,也踩过不少坑。我的经验是,判断标准主要看你对下游电路的控制意图。
如果你希望下游电路仍然被当作时钟域的一部分来对待,只是想把综合的粒度拆细一点,那就用stop pin。比如一个时钟经过分频器之后驱动一个独立的低速模块,你希望这个低速模块的时钟树单独做,但最终还是要跟主时钟做平衡,这时候在分频器的输出端设stop pin就非常合适。
如果你压根不想让工具碰下游的时钟树,或者下游电路有时钟但你不希望它影响当前时钟树的综合结果,那就用ignore pin。典型场景是那些异步时钟域、或者你打算在顶层单独处理的时钟分支。还有一种情况是,某些宏单元的时钟输入你不想让工具自动插buffer,因为宏内部已经有自己的时钟处理逻辑,这时候用ignore pin就能避免工具做无用功。
注意:ignore pin设置之后,工具不会对该引脚下游做任何时钟树综合,这意味着你需要自己确保下游电路的时钟质量。如果下游有时序要求,后续必须通过其他手段来满足。
2.3 两个设置在Innovus中的底层处理逻辑
从工具实现的角度看,stop pin和ignore pin都会影响时钟树综合阶段的“时钟端点识别”和“树构建范围”。Innovus在跑clock tree synthesis之前,会先根据约束文件、SDC以及这些特殊设置,生成一个内部的时钟树拓扑图。stop pin会被标记为“叶子节点但可继续驱动”,而ignore pin则直接被排除在这个拓扑图之外。
这个区别在log文件里其实能看出来。设了stop pin之后,你会在时钟树综合的报告里看到这个引脚被列为一个sink,但它的下游没有buffer插入记录。而ignore pin则完全不会出现在时钟树的sink列表里。我刚开始用的时候没注意看log,结果设错了类型,导致下游时钟根本没做树,时序直接崩了。后来养成了习惯,每次设完都要去log里确认一下工具到底怎么理解的。
3. 在Innovus中设置stop/ignore pin的完整流程
3.1 准备工作:确认时钟结构和约束
在动手设stop/ignore pin之前,有几件事必须先做清楚。首先是用report_clock_tree或者check_clock_tree把当前的时钟结构摸一遍,看看有哪些时钟域、各自的端点分布、有没有跨时钟域的路径。这一步很多人会跳过,直接去设pin,结果设完了发现跟预期完全不一样。
其次是要确认SDC里的时钟定义是否完整。stop pin和ignore pin的设置是依附于时钟约束的,如果某个时钟压根没在SDC里定义,你设了stop pin工具也不会理你。我一般会先用report_clocks确认所有时钟都正确创建了,再用report_clock_tree -structure看一下工具当前识别到的时钟树长什么样。
还有一个容易被忽略的点是时钟门控单元的处理。如果你的设计里有ICG,ICG的时钟输入和输出在时钟树里的角色是不一样的。通常ICG的输入侧是时钟树的一部分,输出侧则可能成为新的分支起点。如果你想在ICG输出侧设stop pin,要确保工具能正确识别ICG的使能逻辑,否则可能会出现时钟树断裂的情况。
3.2 具体命令与参数详解
Innovus里设置stop pin和ignore pin主要通过set_clock_tree_exceptions命令来完成。这个命令的参数比较多,我挑最常用的几个来说。
设置stop pin的基本语法是:
set_clock_tree_exceptions -stop_pins {pin_list}其中pin_list可以是具体的引脚名,也可以是通配符匹配的一组引脚。比如你想把某个模块下所有分频器的输出端都设为stop pin,可以这样写:
set_clock_tree_exceptions -stop_pins [get_pins u_div/*/Q]设置ignore pin的语法类似:
set_clock_tree_exceptions -ignore_pins {pin_list}这两个命令都可以配合-clock参数来限定只对某个时钟生效,避免误伤其他时钟域。比如:
set_clock_tree_exceptions -stop_pins [get_pins u_slow_div/Q] -clock CLK_SLOW还有一个很实用的参数是-exclude_pins,它跟ignore pin有点像但又不完全一样。-exclude_pins是把引脚从时钟树综合中排除,但工具仍然知道它是个时钟引脚,只是不主动去平衡它。这个在有些场景下比ignore pin更合适,因为ignore pin会让工具完全“忘记”这个引脚的存在,后续做时序分析时可能会出问题。
提示:设完这些exception之后,一定要用
report_clock_tree_exceptions确认一下工具实际生效的设置,避免因为通配符写错或者时钟名不匹配导致设置没生效。
3.3 设置后的验证与调试方法
设完stop/ignore pin只是第一步,更重要的是验证效果。我通常会用以下几个手段来检查:
第一,跑一次clock_tree_synthesis,然后看log里关于sink和buffer插入的记录。如果stop pin设置正确,你应该能看到工具在这个引脚处停止了buffer插入,下游没有新的时钟buffer。如果ignore pin设置正确,这个引脚应该完全不出现在时钟树的sink列表里。
第二,用report_clock_tree -summary看各个时钟域的插入延迟和skew。分段之后,每个子网络的skew应该更容易收敛,整体插入延迟也可能有变化。如果发现某个分支的延迟突然变得很大,可能是stop pin设得太靠前,导致下游驱动能力不足。
第三,做时序分析。report_timing看时钟路径上的延迟是否合理,特别是跨时钟域的路径。如果发现setup或hold出现异常,要回头检查是不是ignore pin导致某个时钟分支没做树。
我自己的习惯是,每次调整stop/ignore pin之后,都会保存一个版本,然后跑一次完整的时序分析,对比调整前后的WNS和TNS。这样能直观地看出设置的效果,也方便回滚。
4. 分段时钟网络的优化策略与实战案例
4.1 一个真实项目的时钟结构拆解
拿我之前做过的一个MCU项目来说,里面有三个时钟域:主时钟48MHz、低速时钟32kHz、以及一个调试用的10MHz时钟。主时钟驱动核心逻辑,低速时钟只驱动RTC和几个always-on的寄存器,调试时钟只在测试模式下用。
工具默认跑时钟树综合的时候,把三个时钟域全部纳入了一棵大树,结果低速时钟那条分支插了十几个buffer,延迟比主时钟还大,而且因为驱动能力不匹配,skew怎么都压不下去。调试时钟更离谱,明明只在测试模式下用,工具还是给它建了完整的树,白白浪费面积和功耗。
我的处理方式是:在低速时钟的分频器输出端设stop pin,让工具单独给这个分支做树;在调试时钟的根节点设ignore pin,让工具完全忽略它,后续在顶层手动处理。这样调整之后,主时钟树的规模缩小了将近三分之一,插入延迟从原来的1.2ns降到了0.8ns,低速时钟的skew也从150ps压到了40ps以内。
4.2 分段后的平衡策略与参数调整
分段之后,每个子网络需要单独设置综合参数。Innovus里可以通过set_clock_tree_options来针对特定时钟或特定引脚设置不同的目标skew、最大插入延迟、buffer类型等。
比如对低速时钟分支,我会把目标skew设得宽松一些,因为它的时序要求本来就不高,没必要为了压skew浪费太多资源:
set_clock_tree_options -clock CLK_SLOW -target_skew 100ps -max_insertion_delay 1.5ns对主时钟分支,则要严格一些:
set_clock_tree_options -clock CLK_MAIN -target_skew 50ps -max_insertion_delay 1.0ns还有一个关键点是跨时钟域的平衡。分段之后,不同子网络之间的延迟差异可能会变大,如果它们之间有数据交互,就需要在顶层做额外的平衡。我通常会在分段综合完成后,用report_clock_tree -cross_corner看一下各分支之间的延迟差,如果超过半个时钟周期,就要考虑在顶层加一些延迟补偿或者调整约束。
4.3 功耗与面积的权衡取舍
分段时钟网络的一个直接好处是功耗和面积的优化。因为每个子网络的规模小了,工具不需要为了平衡全局而插入过多的buffer。但这里也有一个权衡:如果分段太细,每个子网络都需要自己的根buffer和驱动单元,反而可能增加面积。
我的经验是,分段的数量控制在3到5个比较合适。太少了起不到优化效果,太多了管理成本高且可能适得其反。具体怎么分,要看时钟域的驱动规模和时序要求。一般来说,驱动寄存器数量少于50个的时钟域,可以考虑单独分段;超过200个的,最好还是跟主时钟一起做,否则子网络的驱动能力可能不够。
另外,stop pin的位置也很关键。设得太靠前,下游驱动能力不足;设得太靠后,分段效果不明显。我一般会在分频器或者时钟门控单元的输出端设stop pin,这样既能保证上游有足够的驱动,又能让下游独立做树。
5. 常见问题与排查技巧实录
5.1 stop pin设了没效果怎么办
这是最常见的问题。明明设了stop pin,但工具还是往 downstream 插了一堆buffer。原因通常有几个:
第一,引脚名写错了。Innovus的引脚名是区分大小写的,而且层次分隔符要用对。我建议用get_pins配合通配符来获取,而不是手写。比如get_pins -hier *u_div/Q这样。
第二,时钟约束不完整。如果stop pin所在的时钟域没有在SDC里定义,工具不会把它当作时钟路径来处理,stop pin自然也不生效。用report_clocks确认一下。
第三,exception被后面的设置覆盖了。Innovus里时钟树exception是有优先级的,如果后面又设了一个更宽泛的exception,可能会把前面的覆盖掉。用report_clock_tree_exceptions看最终生效的是哪个。
5.2 ignore pin导致时序违例的修复思路
ignore pin用不好确实容易出问题,因为它让工具完全忽略了一个时钟引脚。如果这个引脚下游有时序路径,工具不会为它做任何时钟树优化,时序违例几乎是必然的。
修复的思路有两种:一是改用stop pin,让工具至少把这个引脚纳入时钟树,保证基本的驱动和平衡;二是在顶层手动为这个分支做时钟树,比如用specify_clock_tree或者手动插入buffer。
我个人的建议是,除非你非常确定下游电路不需要时钟树优化,否则尽量用stop pin而不是ignore pin。ignore pin更适合那些异步时钟域或者测试逻辑,这些路径通常有时序例外或者不需要严格时序收敛。
5.3 分段后skew反而变大的原因分析
分段的目的本来是优化skew,但有时候分段之后skew反而变大了。这种情况通常是因为子网络之间的平衡没做好。比如主时钟分支和低速分支之间的延迟差太大,导致跨时钟域的路径时序变差。
解决办法是在分段综合完成后,做一次全局的时钟树平衡。Innovus提供了clock_tree_balance之类的命令,可以在不改变各子网络内部结构的前提下,调整它们之间的延迟关系。另外,也可以通过调整各子网络的max_insertion_delay来间接控制它们之间的延迟差。
还有一个可能的原因是buffer类型选择不当。不同子网络如果用了不同驱动能力的buffer,延迟特性会不一样。我一般会在分段综合时统一指定buffer类型,避免引入额外的偏差。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决思路 |
|---|---|---|---|
| stop pin不生效 | 引脚名错误或时钟未定义 | report_clock_tree_exceptions | 用get_pins重新获取引脚名,确认SDC |
| ignore pin导致时序崩 | 下游电路未做时钟树 | report_timing看时钟路径 | 改用stop pin或手动做树 |
| 分段后skew变大 | 子网络间平衡未做好 | report_clock_tree -cross_corner | 调整max_insertion_delay或做全局平衡 |
| 功耗反而增加 | 分段过细导致根buffer增多 | 对比分段前后的面积报告 | 减少分段数量,合并小分支 |
| 工具报时钟树断裂 | stop pin设在了错误位置 | 检查log中的时钟树拓扑 | 调整stop pin位置到分频器输出端 |
6. 一些实操心得与避坑建议
6.1 我的调试习惯与版本管理
做时钟树调试最怕的就是改来改去最后不知道哪个版本是对的。我的习惯是每次调整stop/ignore pin之前,先保存一个版本,然后在脚本里把这次调整的目的和预期效果写清楚。Innovus的脚本是可以加注释的,别嫌麻烦,过两天回头看的时候你会感谢自己。
另外,我建议把stop/ignore pin的设置单独放在一个tcl文件里,跟主流程脚本分开。这样调试的时候只需要改这一个文件,不用动整个流程。而且方便做A/B对比,把不同设置的效果记录下来。
6.2 跟其他时钟树约束的配合使用
stop/ignore pin不是孤立的,它需要跟其他时钟树约束配合才能发挥最大效果。比如set_clock_tree_options里的-target_skew、-max_insertion_delay、-buffer_list这些参数,都会影响分段后的综合结果。我一般会先设好全局的时钟树选项,再针对每个分段单独调整。
还有一个容易忽略的是set_clock_gating_check和set_clock_groups。如果分段之后时钟域之间的关系变了,这些约束也要相应更新,否则时序分析的结果会不准确。
6.3 对新手的一些建议
如果你是刚开始接触Innovus的时钟树综合,我的建议是先从简单的设计练起,把stop pin和ignore pin的基本用法摸熟。不要一上来就在复杂设计上试,出了问题很难定位。
另外,多看log。Innovus的log里其实信息很全,工具怎么理解你的设置、怎么建树、插了哪些buffer,都能从log里找到。我见过很多新手设完pin就不管了,结果跑完发现效果不对,又不知道从哪查起。养成看log的习惯,能省很多时间。
最后,别怕试错。时钟树综合本身就是一个需要反复调整的过程,没有一次就能设对的。我做了这么多年,每次碰到新的时钟结构还是要试好几轮。关键是每次调整都要有明确的目的和验证手段,这样才能积累经验。
6.4 后续可以扩展的方向
分段时钟网络这个思路其实还可以延伸到更多场景。比如多电源域的设计里,不同电源域的时钟树可以分别做,最后在顶层协调。再比如3D IC或者chiplet设计里,跨die的时钟树也可以分段处理。这些场景下stop/ignore pin的用法会更复杂,但核心逻辑是一样的:明确哪些部分需要独立处理,哪些部分需要统一平衡。
另外,Innovus的版本更新也会带来一些新的时钟树综合功能,比如machine learning based的时钟树优化。这些新功能跟stop/ignore pin结合使用,可能会有更好的效果。我最近在试的一个方向是用ML预测最优的stop pin位置,初步结果还不错,等成熟了再单独写一篇分享。