ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

数字后端CTS分段长时钟树:5种特殊sink type实战与避坑指南

数字后端CTS分段长时钟树:5种特殊sink type实战与避坑指南 在数字后端实现里时钟树综合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 directionin nameclk*] 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 PinExclude 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 directionin] 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内部还有一堆低速外设时钟。如果全部严格balancebuffer数量会多出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来balancefloat的意义就没了。我有一次把float pin的target skew设成了100ps结果工具为了达到这个目标在主树和float sink之间插了一堆bufferbuffer数量不降反升。后来我把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到ECOsink 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都要清楚它为什么存在、不设会怎样、设了之后谁来保证它的时序。想清楚这三个问题分段长时钟树就不再是噩梦反而成了你可以精细调控的舞台。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进