☰
数字后端CTS分段长时钟树:5种特殊sink type实战与避坑指南
2026/10/7 17:42:11 网站建设 项目流程

在数字后端实现里,时钟树综合(CTS)永远是那个最容易被低估、却又最能决定芯片成败的环节。很多人跑完place、做完时序优化,觉得剩下的就是把CTS脚本一跑、让工具自动balance一下,结果流片回来发现某些模块的时钟偏斜大得离谱,或者hold修到崩溃。我做了这么多年后端,踩过最多的坑基本都集中在一种场景上:分段长时钟树。这种结构通常出现在多电压域、跨物理分区、或者时钟需要经过长距离绕线才能到达sink点的设计里。Innovus提供了多种clock tree sink type,默认的flop/cell类型大家都熟,但真正能救命的,是那几种特殊sink type在分段长树里的组合用法。这篇就围绕这5种特殊sink type,把我在实际项目里怎么用、为什么这么用、以及踩过的坑,完整拆一遍。

1. 先搞清楚分段长时钟树到底难在哪

1.1 长时钟树的物理现实:绕线延迟不再是配角

短时钟树里,leaf到root的距离可能就几百微米,绕线延迟相对于buffer延迟可以忽略。但分段长时钟树不一样,sink点可能分布在芯片的两端,中间要穿过好几个物理分区,绕线长度动辄几毫米。这时候绕线延迟(net delay)会占到总insertion delay的很大比例,甚至超过buffer本身的延迟。

我遇到过最极端的一个case:一个跨两个die-to-die接口的时钟,从PLL出来到最远sink点,绕线延迟占了整个insertion delay的65%。这种情况下,如果你还用默认的CTS策略,工具会拼命在中间插buffer来balance,结果就是buffer数量爆炸、功耗飙升,skew还不一定收敛。

所以分段长树的核心矛盾是:你要在绕线延迟主导的前提下,让工具理解哪些sink是可以"分组对待"的,而不是一视同仁地balance。

1.2 为什么默认sink type在长树里会失效

Innovus默认把leaf pin识别为flop的clock pin、latch的gate pin、或者macro的clock input。工具会把这些都当成需要严格balance的sink。但在分段长树里,有些sink其实不需要和主树严格对齐,比如:

  • 某些低速模块的时钟,允许有较大的skew
  • 某些测试逻辑的时钟,只在scan mode下用
  • 某些跨域同步的时钟,本身就要做clock gating处理

如果你不告诉工具这些sink的特殊性,它就会浪费大量资源去balance它们,反而把真正关键的路径搞乱。这就是特殊sink type存在的意义——给工具"分类指令",让它知道哪些sink该严格管、哪些可以放松、哪些干脆单独处理。

1.3 五种特殊sink type的定位速览

在展开细节之前,先给一个整体定位,方便你建立全局认知。Innovus里常用的特殊sink type包括:stop pin、exclude pin、float pin、through pin、以及non-stop pin(不同版本命名略有差异,但语义一致)。它们各自的作用方向不同:

Sink Type核心作用典型场景
Stop Pin时钟树在此终止,不再往下balance分段树的边界点
Exclude Pin完全排除在CTS之外测试逻辑、旁路时钟
Float Pin参与balance但不强制对齐低速模块时钟
Through Pin时钟穿过但不作为sink跨分区馈通
Non-stop Pin继续往下但不在此点收敛中间馈点

这张表建议你先记住,后面每一节我都会结合具体操作和踩坑经验展开。

2. Stop Pin:分段长树里最关键的"断点"设计

2.1 Stop Pin的本质:让时钟树分段独立收敛

Stop pin是我在分段长树里用得最多的特殊sink type。它的语义很直接:时钟树传播到这个pin就停止,不再继续往它的fanout方向balance。换句话说,这个pin成了当前这段时钟树的leaf,工具只负责把时钟送到这里,后面的子树由你单独处理。

为什么这个机制在长树里这么重要?因为长时钟树如果一次性从头balance到尾,工具会试图用一条统一的树去覆盖所有sink,导致中间节点被过度约束。而实际上,长树往往可以拆成几段:PLL到分区入口是一段,分区入口到模块内部是另一段。每段的延迟特征、绕线环境都不一样,分开收敛反而更容易控制skew。

2.2 设置Stop Pin的具体操作与参数

在Innovus里设置stop pin通常有两种方式。一种是在CTS spec文件里用set_clock_tree_stop_pins命令,另一种是在MMMC或者floorplan阶段通过set_db属性标记。我一般用spec文件的方式,因为可读性好、方便版本管理。

# 在CTS spec中标记stop pin set_clock_tree_stop_pins [get_pins u_partition_A/clk_in] set_clock_tree_stop_pins [get_pins u_partition_B/clk_in] # 也可以批量标记 set stop_pins [get_pins -of_objects [get_cells {u_part_A u_part_B}] -filter "direction==in && name==clk*"] set_clock_tree_stop_pins $stop_pins

设置完之后,工具在CTS时会把这两个pin当作leaf处理,不再往partition内部插buffer。partition内部的时钟树你需要单独跑一次CTS,或者用手工balance的方式处理。

注意:stop pin设置后,这个pin的downstream逻辑不会被自动CTS,必须确保你有后续的CTS步骤覆盖它,否则会出现unbalanced的时钟。

2.3 Stop Pin在跨电压域场景下的实战心得

跨电压域是stop pin最典型的应用场景。不同电压域的时钟树如果混在一起balance,工具会忽略level shifter带来的额外延迟,导致skew计算失真。我通常会在level shifter的输入侧设置stop pin,让每个电压域的时钟树独立收敛。

这里有个细节很多人会忽略:stop pin的位置选择会影响后续ECO的难度。如果你把stop pin设在太靠前的位置,后续要调整分区内部时钟树时,就得重新跑整个分区的CTS;如果设在太靠后,又起不到隔离作用。我的经验是设在分区入口的第一个buffer之后,这样既隔离了域间影响,又保留了分区内部一定的调整空间。

另外,stop pin和clock gating cell的配合也很关键。如果stop pin下游有ICG,要确保ICG的enable路径也被正确处理,否则会出现gating失效或者时钟被意外切断的问题。

3. Exclude Pin:把不该管的sink彻底踢出去

3.1 什么时候必须用Exclude Pin

Exclude pin的语义是:这个pin完全不参与时钟树综合,工具既不给它插buffer,也不把它算进skew计算。听起来很粗暴,但在某些场景下这是唯一正确的做法。

最典型的场景是测试逻辑。比如scan chain的clock、DFT专用的时钟mux,这些逻辑在function mode下根本不工作,但如果你不exclude,工具会认真地去balance它们,浪费大量buffer资源。更糟糕的是,有些测试时钟的fanout很大,工具为了balance它会插一堆buffer,结果影响了正常功能时钟的树结构。

还有一种场景是旁路时钟(bypass clock)。有些设计里存在多条时钟路径,实际工作时只选一条,其他都是备选。这些备选路径如果不exclude,工具会试图同时balance所有路径,导致资源浪费和skew恶化。

3.2 Exclude Pin的配置方法与验证

配置exclude pin的命令和stop pin类似,但语义完全不同:

# 排除测试逻辑时钟 set_clock_tree_exclude_pins [get_pins u_dft/scan_clk] set_clock_tree_exclude_pins [get_pins u_dft/test_mux/Z] # 排除旁路时钟路径 set exclude_list [get_pins -of_objects [get_cells u_bypass*] -filter "direction==in"] set_clock_tree_exclude_pins $exclude_list

设置完之后,一定要验证。我通常用report_clock_tree -exclude_pins来确认排除列表是否正确,然后用check_clock_tree检查有没有遗漏的sink。

提示:exclude pin设置后,这些pin的时钟延迟不会被工具优化,你需要手工确保它们的时序满足要求,或者在后续ECO阶段单独处理。

3.3 Exclude Pin的常见误用与避坑

我见过最常见的误用,是把exclude pin当成了"万能解药",一遇到难balance的sink就exclude掉。这是很危险的。因为exclude之后,工具完全不管这个sink,如果它恰好是功能路径上的关键sink,时序会直接崩掉。

我的原则是:只有确认在function mode下不工作、或者有独立处理方案的sink,才用exclude。每次exclude之前,我都会问自己三个问题:这个sink在function mode下有时序要求吗?它的时钟延迟有人管吗?如果工具不管它,我有没有后续手段保证它不出问题?

另外一个坑是exclude pin的传播性。有些版本的Innovus里,exclude一个pin会连带影响它的fanout,导致下游sink也被排除。这时候要用-no_propagate选项控制传播行为,具体要看版本手册确认。

4. Float Pin:给低速模块时钟"松绑"的正确姿势

4.1 Float Pin与Stop Pin的本质区别

很多人会把float pin和stop pin搞混,其实它们的语义差别很大。Stop pin是"到此为止,不再往下",而float pin是"继续往下,但不强制和主树对齐"。换句话说,float pin仍然参与时钟树构建,但工具不会为了它去牺牲主树的skew。

这个区别在分段长树里非常关键。比如一个长树上有几个低速模块的sink,它们的时钟频率只有主时钟的1/4,对skew的要求很宽松。如果你用stop pin,就得单独给它们建树;如果用float pin,工具会在主树的基础上"顺带"把时钟送过去,只是不保证严格对齐。

4.2 Float Pin的适用判断标准

什么时候该用float pin?我的判断标准是看sink的时序裕量和时钟频率:

  • 时钟频率低于主时钟的1/2,且setup裕量大于insertion delay的20%
  • sink数量少,且分布不集中
  • 这些sink的skew即使偏大,也不会导致功能错误

满足这些条件,就可以考虑float pin。具体配置:

# 标记低速模块时钟为float set_clock_tree_float_pins [get_pins u_low_speed_mod/clk] # 可以配合skew group使用 set_clock_tree_skew_group -name low_speed_grp -pins [get_pins u_low_speed_mod/clk] -target_skew 500ps

这里我通常会配合skew group一起用,给float pin一个宽松的target skew,让工具知道"这些sink可以放松,但别完全不管"。

4.3 Float Pin在长树里的实测效果与调优

我在一个多核处理器项目里大量使用了float pin。当时的情况是:主时钟要送到8个core,每个core内部还有一堆低速外设时钟。如果全部严格balance,buffer数量会多出30%以上。

用了float pin之后,工具只对主时钟路径做严格balance,低速外设的时钟通过float pin"搭便车"送过去。实测下来,主时钟skew控制在50ps以内,低速外设的skew在300ps左右,完全满足时序要求,buffer数量减少了约25%。

调优的关键是float pin的target skew设置。设得太紧,工具还是会拼命balance;设得太松,又可能出现时序问题。我的经验值是:float pin的target skew设为主树target skew的5到10倍,具体要看低速模块的时序裕量。

5. Through Pin与Non-stop Pin:长树馈通场景的精细控制

5.1 Through Pin:时钟穿过但不收敛的中间点

Through pin的语义是:时钟信号经过这个pin,但工具不把它当作sink,也不在这里收敛。它更像是一个"透明"的节点,时钟穿过去继续往后面的sink传播。

这个类型在跨分区馈通场景下特别有用。比如时钟从分区A传到分区B,中间经过分区A的一个边界buffer。如果你把这个buffer的输出pin设为through pin,工具就知道"这里只是路过,真正的sink在分区B里面",从而不会在分区A里浪费资源去balance这个点。

配置方式:

# 设置through pin set_clock_tree_through_pins [get_pins u_part_A/boundary_buf/Z] # 配合stop pin使用,形成完整的馈通路径 set_clock_tree_stop_pins [get_pins u_part_A/clk_out]

Through pin和stop pin经常配合使用:stop pin标记分区的出口,through pin标记路径上的中间节点。这样工具就能清晰地理解时钟的传播路径,不会在中间节点上做无谓的balance。

5.2 Non-stop Pin:继续传播但不在此收敛

Non-stop pin和through pin很像,但有一个细微差别:non-stop pin会参与时钟树的延迟计算,只是不作为最终的balance目标。它通常用在那些"需要被看到,但不需要被严格对齐"的中间节点上。

在实际项目里,我一般用non-stop pin来处理那些有monitor或者debug逻辑的中间节点。这些节点需要时钟信号,但它们的时序要求很宽松,不值得为它们牺牲主树的skew。

# 设置non-stop pin set_clock_tree_non_stop_pins [get_pins u_debug/monitor_clk]

5.3 五种sink type的组合使用策略

单独用某一种sink type往往解决不了复杂问题,真正的威力在于组合。我在一个跨三个物理分区的长时钟树项目里,是这样组合使用的:

  • 分区入口设stop pin,隔离各分区的时钟树
  • 分区内部的低速模块设float pin,放松skew要求
  • 跨分区的馈通路径设through pin,避免中间节点被误balance
  • 测试逻辑设exclude pin,彻底排除
  • debug逻辑设non-stop pin,保证信号可达但不影响主树

这套组合下来,整个长树的buffer数量比默认策略少了约35%,skew反而更好了。核心逻辑就是:让工具把精力集中在真正重要的sink上,其他sink用合适的类型"分流"处理。

组合场景使用的sink type效果
跨电压域stop + through域间隔离,馈通透明
多核+低速外设float + stop主树严格,外设放松
含DFT逻辑exclude + non-stop功能路径干净
跨分区长树stop + through + float分段收敛,资源最优

6. 踩坑实录:那些让我熬夜的sink type配置问题

6.1 Stop Pin设错位置导致的分区时钟失配

有一次我在一个双分区设计里,把stop pin设在了分区入口的时钟mux输出上。结果CTS跑完,两个分区的时钟延迟差了将近200ps,导致跨分区接口的setup直接violation。

排查过程是这样的:先用report_clock_tree -summary看各分区的insertion delay,发现分区B的延迟明显偏大。然后report_clock_tree -pin追到stop pin的位置,才意识到问题——mux本身有延迟,而我把stop pin设在mux之后,导致分区B的时钟树从mux输出开始算,自然比分区A多了一段mux延迟。

修复方法很简单:把stop pin移到mux的输入侧,让mux的延迟被包含在上一段时钟树里。改完之后,两个分区的延迟差缩小到30ps以内。

这个坑的教训是:stop pin的位置要选在"延迟特征一致"的边界上,不要把有额外延迟的单元夹在中间。

6.2 Exclude Pin传播导致的意外断链

另一个坑是exclude pin的传播行为。我在一个项目里exclude了一个测试mux的输出pin,本以为只影响这个pin本身,结果工具把它的整个fanout都排除了,包括一个功能路径上的clock divider。CTS跑完后,那个divider的时钟完全没被balance,时序崩得一塌糊涂。

排查的时候用check_clock_tree -exclude才发现,exclude列表里多了一堆我没设置的pin。后来查手册才知道,某些版本的Innovus默认会传播exclude属性。解决办法是加-no_propagate选项,或者改用stop pin来达到类似效果。

6.3 Float Pin的target skew设置过紧

float pin的target skew如果设得太紧,工具会把它当成普通sink来balance,float的意义就没了。我有一次把float pin的target skew设成了100ps,结果工具为了达到这个目标,在主树和float sink之间插了一堆buffer,buffer数量不降反升。

后来我把target skew放宽到500ps,工具才真正"放松"下来。所以float pin的关键不是设不设,而是target skew要设得足够宽松,让工具明确知道这些sink可以牺牲精度换资源。

6.4 Through Pin与Stop Pin顺序错误

through pin和stop pin如果设置顺序不对,工具可能无法正确识别路径。我遇到过一次:先设了stop pin,后设through pin,结果工具把through pin当成了stop pin的下游sink,反而在中间插了buffer。

正确的做法是先设through pin,再设stop pin,让工具先理解路径的透明性,再确定收敛点。这个顺序在Innovus的CTS spec里很重要,建议养成习惯。

7. 从CTS到ECO:sink type配置的后续维护

7.1 CTS完成后如何验证sink type是否生效

CTS跑完不代表万事大吉,sink type是否真正生效需要验证。我通常用这几个命令组合检查:

# 检查stop pin是否被正确识别 report_clock_tree -stop_pins # 检查exclude pin列表 report_clock_tree -exclude_pins # 检查float pin的skew group report_clock_tree -skew_groups # 整体检查时钟树结构 check_clock_tree -verbose

如果发现某个sink type没生效,先检查pin的层次名是否正确,再检查是否有其他约束覆盖了它。有时候MMMC里的约束会和CTS spec冲突,导致sink type被忽略。

7.2 ECO阶段sink type的调整策略

ECO阶段如果改了时钟结构,sink type可能需要同步调整。比如你新增了一个分区,就要给它的入口加stop pin;如果删除了某个低速模块,对应的float pin也要清理掉。

我的习惯是在ECO之前先report_clock_tree导出当前的sink type配置,改完之后对比一遍,确保没有遗漏。ECO buffer tree的插入也要注意不要跨越stop pin的边界,否则会破坏分段结构。

7.3 跨项目复用的sink type配置模板

最后分享一个我常用的配置模板思路。不同项目虽然细节不同,但sink type的配置逻辑是相通的。我会把配置分成三层:

  • 基础层:所有项目通用的exclude规则(比如DFT逻辑)
  • 结构层:根据分区和电压域设置的stop/through pin
  • 优化层:根据时序裕量设置的float/non-stop pin

这样复用的时候只需要改结构层和优化层,基础层直接拿来用。模板化的好处是减少遗漏,坏处是可能忽略项目特殊性,所以每次复用都要重新review一遍。

# 基础层示例 set_clock_tree_exclude_pins [get_pins -hier -filter "name=~*scan_clk*"] set_clock_tree_exclude_pins [get_pins -hier -filter "name=~*test_clk*"] # 结构层示例(需按项目修改) foreach part {u_part_A u_part_B u_part_C} { set_clock_tree_stop_pins [get_pins $part/clk_in] } # 优化层示例(需按时序裕量调整) set_clock_tree_float_pins [get_pins -hier -filter "name=~*low_speed*/clk"] set_clock_tree_skew_group -name float_grp -target_skew 500ps

这套模板我在好几个项目里用过,基本能覆盖80%的场景,剩下的20%靠具体分析补。关键是理解每种sink type的语义,而不是死记命令。

我在实际使用中最大的体会是:sink type不是越多越好,而是越精准越好。每设置一个特殊sink,都要清楚它为什么存在、不设会怎样、设了之后谁来保证它的时序。想清楚这三个问题,分段长时钟树就不再是噩梦,反而成了你可以精细调控的舞台。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询