
时钟树综合这件事说它是数字后端实现里最玄学的一环也不为过。跑完place时序看着还行一跑CTSsetup和hold同时炸给你看或者更气人的是CTS跑完时序明明收敛了route之后又莫名其妙冒出一堆violation。我在几个不同工艺节点的项目里都栽过跟头从28nm到7nmCTS踩的坑换汤不换药但每次都能以新的姿势出现。这篇就把我在Innovus里做CTS时最常撞上的5类问题拆开讲清楚——不是那种打开手册第几页的讲法而是从现象出发一步步定位根因最后给出能直接抄的TCL脚本。不管你是刚接手CTS的新手还是已经跑过几个项目但总觉得CTS结果不够稳的老手下面这些排查思路和参数配置应该都能帮你省下几个通宵。1. 先搞清楚Innovus CTS到底在做什么很多人跑CTS就是机械地执行一条ccopt_design跑完看报告violation多就调参数调完再跑循环往复。这种盲调效率极低根本原因是对CTS阶段Innovus内部到底做了哪些事没有清晰认知。你得先知道它在干什么才能判断它哪里干得不对。1.1 CTS不是简单的插buffer时钟树综合的本质是把一个理想时钟ideal clock变成一棵真实的、带延迟和偏差的物理树。Innovus在CTS阶段做的事情远比插buffer复杂它至少要同时处理这几件事时钟根到叶节点的路径构建决定用几级buffer、每级buffer放在哪里、用什么驱动强度的cell。skew和latency的平衡让同一个时钟域内所有flop的时钟到达时间尽量一致同时控制从时钟源到flop的总延迟。clock gating cell的处理ICGIntegrated Clock Gatingcell的插入位置和时钟树结构会直接影响功耗和时序。跨时钟域和generated clock的处理不同时钟域之间的交互、分频时钟、门控时钟的树结构都要单独考虑。与placement的协同CTS不是独立于place的clock buffer的摆放会反过来影响data path的拥塞和时序。理解这一点很关键CTS的每一个决策都是多目标权衡。你把skew压到极小可能latency变大、buffer数量暴增、功耗和面积都上去你把buffer数量压下来skew又可能失控。所以后面讲的所有问题和解决方案本质上都是在这些目标之间找平衡点。1.2 CCOpt的核心机制Innovus的CTS引擎叫CCOptClock Concurrent Optimization它和传统CTS工具最大的区别在于CCOpt是时序驱动的它在建树的同时会考虑data path的时序。传统CTS先把时钟树建好再去修data pathCCOpt则是在建树过程中就尝试把useful skew利用起来让时钟树的结构对data path更友好。这就解释了为什么有时候你看到CTS后的时序报告和place后的差异很大——CCOpt在建树时已经帮你做了一部分时序优化它通过调整不同flop的时钟到达时间也就是useful skew来借时间给关键路径。这个机制用好了是利器用不好就是灾难后面第3节会详细讲useful skew失控的情况。1.3 一个典型的CTS流程长什么样在Innovus里一个完整的CTS流程通常包含以下步骤我把它整理成表格方便对照步骤主要命令作用时钟定义检查report_clocks确认SDC中时钟定义完整、周期正确CTS前准备set_ccopt_property配置CTS全局属性时钟树综合ccopt_design执行CTS建树优化结果检查report_clock_timing查看skew、latency时序修复optDesign -postCTS修setup/hold时钟树调试ccopt_design -cts单独重跑CTS部分注意ccopt_design和optDesign -postCTS是两件事。前者负责建树后者负责在已有树的基础上修时序。很多人把这两个混在一起调结果参数改了半天不知道是哪个阶段起的作用。2. 问题一CTS后skew压不下去report里一片红这是最经典的问题。跑完CTSreport_clock_timing -type skew一看skew大得离谱某些corner下甚至超过时钟周期的10%。这时候大多数人第一反应是加buffer、调target skew但往往越调越糟。2.1 先别急着调参数先看时钟定义对不对我踩过最冤的一次坑skew怎么调都下不来折腾了一整天最后发现是SDC里有个generated clock的定义写错了导致工具把两个本来独立的时钟当成同一个域在处理。所以遇到skew问题第一步永远是检查时钟定义# 检查所有时钟定义 report_clocks # 检查时钟之间的关联关系 report_clock_properties # 查看是否有意外的clock group report_clock_groups重点看几个东西时钟周期是否和spec一致、generated clock的source是否正确、有没有clock被意外地设成了同一group。如果两个本该异步的时钟被分到同一group工具会拼命去balance它们之间的skew结果就是两边都压不下去。2.2 skew压不下去的常见根因排除了时钟定义问题之后skew压不下去通常有这几个原因第一clock buffer的驱动能力不够或者摆放位置太远。如果clock root到sink的物理距离很长而中间又没有足够的buffer来驱动延迟就会很大且不均匀。这时候需要检查clock buffer的分布看是不是有某些sink离root特别远。第二floorplan导致的物理不可达。如果某个flop被放在了一个角落而时钟root在另一个角落中间还隔着大片macro那这个flop的clock latency必然和其他flop差很多。这种情况调CTS参数是没用的得回去改floorplan或者调整这个flop的位置。第三NDRNon-Default Rule设置不合理。时钟net通常需要走更宽的线、更大的间距来保证信号质量但如果NDR设得太激进绕线资源不够工具可能被迫走远路反而增大skew。2.3 实操一套skew调试的TCL脚本下面这套脚本是我常用的skew调试流程从检查到调整一气呵成# # CTS Skew调试脚本 # # Step 1: 检查时钟定义 puts Clock Definitions report_clocks # Step 2: 查看当前skew情况 puts Skew Report report_clock_timing -type skew -verbose # Step 3: 查看clock tree结构 puts Clock Tree Structure report_clock_tree -summary # Step 4: 检查clock buffer分布 puts Clock Buffer Distribution report_clock_tree -clock_buffers # Step 5: 调整CTS属性 # 设置target skew根据时钟周期调整一般取周期的5%-10% set_ccopt_property target_skew 0.15 # 设置最大transition避免信号质量太差 set_ccopt_property target_max_trans 0.2 # 设置clock buffer的preferred layer set_ccopt_property -net_type trunk preferred_routing_layer M4 # Step 6: 重新跑CTS ccopt_design -cts # Step 7: 再次检查 report_clock_timing -type skew提示target_skew不是越小越好。设得太小工具会疯狂插buffer去balance结果latency和面积都爆炸。一般建议先设一个合理值比如周期的5%跑完看结果再微调。2.4 一个容易被忽略的点skew和latency要一起看很多人只盯着skew忽略了latency。实际上这两个是耦合的。如果latency特别大比如超过一个时钟周期那skew再小也没意义因为时钟信号到达flop的时间太晚了setup根本没法收敛。所以看report的时候一定要同时看skew和latency# 同时查看skew和latency report_clock_timing -type latency report_clock_timing -type skew如果latency过大通常意味着clock tree的级数太多需要减少buffer级数或者优化buffer的驱动强度。3. 问题二useful skew失控setup修好了hold炸了useful skew是CCOpt的一大卖点但也是很多人的噩梦。它的原理是通过故意让某些flop的时钟早到或晚到来借时间给关键路径。比如某条setup路径差0.1ns工具可以让capture flop的时钟晚到0.1ns这样setup就满足了。但代价是这条路径的hold会变差0.1ns。3.1 useful skew为什么会失控CCOpt默认是开启useful skew的而且它会比较激进地使用。在place阶段时序还不错的designCTS后可能因为useful skew被大量使用导致hold violation暴增。更麻烦的是有些useful skew是在CTS阶段借的但到了route阶段由于寄生参数的变化这个借的时间可能不够或者过头导致时序反复。我遇到过一次典型情况CTS后setup全部收敛hold有少量violation用optDesign -postCTS修完hold结果route之后setup又炸了。排查后发现是CTS阶段用了大量useful skewroute后clock net的寄生参数变化导致实际skew和CTS预估的不一致之前借的时间全乱了。3.2 控制useful skew的几种手段控制useful skew有几种思路我按激进程度从低到高排列第一种完全关闭useful skew。这是最保守的做法适合对时序余量要求高、或者时钟结构复杂的designset_ccopt_property useful_skew false关掉之后CTS就只做纯粹的skew balance不会为了data path去故意偏斜时钟。这样CTS结果更可预测但可能setup收敛会差一些。第二种限制useful skew的范围。不完全关闭但限制工具能借多少时间# 限制useful skew的最大值 set_ccopt_property useful_skew_max 0.05这样工具还是可以用useful skew但不会借太多降低route后时序反复的风险。第三种分阶段控制。在CTS阶段先关闭useful skew等时序基本收敛后再在postCTS阶段适度开启# CTS阶段关闭 set_ccopt_property useful_skew false ccopt_design -cts # postCTS阶段适度开启 set_ccopt_property useful_skew true set_ccopt_property useful_skew_max 0.03 optDesign -postCTS3.3 怎么判断useful skew是不是用过头了有几个信号可以帮你判断CTS后hold violation数量突然暴增而setup改善有限。report_clock_timing -type skew显示skew很大但latency正常。某些flop的clock arrival time明显偏离其他flop。这时候可以用下面这个脚本查看具体的useful skew使用情况# 查看每个sink的clock arrival time report_clock_timing -type arrival -verbose arrival.rpt # 查看clock tree的详细结构包括每级buffer report_clock_tree -verbose clock_tree.rpt # 对比不同flop的arrival time差异 # 如果差异超过target_skew的2倍说明useful skew用过头了注意useful skew不是不能用而是要控制。我的经验是对于高频design周期小于1nsuseful skew_max不要超过周期的3%对于低频design可以放宽到5%。4. 问题三clock gating cell导致的时序和功耗问题Clock gating是低功耗设计的重要手段但ICG cell的插入位置和时钟树结构会带来一系列问题。最常见的是ICG cell后面的时钟树skew和前面不一致导致gating后的flop时序变差。4.1 ICG cell对时钟树的影响ICG cell本质上是一个受enable信号控制的时钟门。它在时钟树中的位置很关键如果ICG cell放在时钟树的上游靠近root那它后面会挂很多flopgating效果好但ICG cell本身的clock latency会影响后面所有flop。如果ICG cell放在下游靠近flopgating效果差但对时序影响小。Innovus在CTS时会自动决定ICG cell的位置但默认策略不一定适合你的design。有时候工具会把ICG cell放得太靠上游导致后面一大片flop的clock latency都变大。4.2 ICG cell相关的常见问题问题一ICG cell的enable信号时序不满足。ICG cell的enable信号必须在时钟有效沿之前稳定否则会产生毛刺。如果enable信号来自另一个时钟域或者路径太长就容易出问题。问题二ICG cell后面的skew和前面不一致。因为ICG cell本身有延迟它后面的时钟树相当于重新建了一棵树如果工具没有把ICG前后的skew统一考虑就会出现前后skew不一致的情况。问题三ICG cell的clock pin和enable pin的时序约束不完整。很多人只约束了ICG cell的clock忘了约束enable导致工具在优化时忽略了enable路径。4.3 ICG cell的调试脚本和配置# # ICG Cell相关配置和调试 # # Step 1: 查看design中的ICG cell puts ICG Cells get_cells -hier -filter is_icgtrue # Step 2: 检查ICG cell的时序约束 puts ICG Timing Constraints report_timing -to [get_pins -of [get_cells -hier -filter is_icgtrue] -filter directionin] # Step 3: 配置ICG cell的CTS属性 # 设置ICG cell的clock pin的max transition set_ccopt_property -pin [get_pins -of [get_cells -hier -filter is_icgtrue] -filter is_clocktrue] max_transition 0.15 # 设置ICG cell的enable pin的setup/hold约束 set_ccopt_property -pin [get_pins -of [get_cells -hier -filter is_icgtrue] -filter is_enabletrue] setup_margin 0.05 # Step 4: 控制ICG cell在时钟树中的位置 # 设置ICG cell的preferred level越小越靠近root set_ccopt_property -cell [get_cells -hier -filter is_icgtrue] preferred_level 2 # Step 5: 重新跑CTS ccopt_design -cts # Step 6: 检查ICG前后的skew report_clock_timing -type skew -from [get_pins -of [get_cells -hier -filter is_icgtrue] -filter is_clocktrue]提示ICG cell的enable信号约束非常重要。如果enable路径的时序不满足gating后的时钟可能会出现毛刺导致功能错误。建议在SDC中显式约束ICG cell的enable路径并在CTS后专门检查。4.4 一个实际案例我之前做过一个低功耗design用了大量ICG cell。CTS后功能仿真没问题但功耗比预期高了20%。排查后发现是ICG cell的位置太靠下游导致很多本该被gating的flop实际上没有被gating到。后来调整了ICG cell的preferred_level把它们往上游移功耗降下来了但时序又变差了。最后是通过分区域设置不同的preferred_level在功耗和时序之间找到了平衡。这个案例说明ICG cell的位置不是越上游越好也不是越下游越好要根据design的实际情况来调。对于时序紧张的模块ICG cell可以放下游一些对于功耗敏感的模块可以放上游一些。5. 问题四CTS后route阶段时序反复这个问题最让人头疼CTS跑完时序收敛了route之后又冒出一堆violation。很多人以为是route工具的问题其实根因往往在CTS阶段就埋下了。5.1 为什么route后时序会变CTS阶段用的寄生参数是估算的基于floorplan和placement的粗略信息而route之后是真实的寄生参数。两者之间的差异主要来自clock net的绕线长度变化CTS时clock net走的是预估路径route后实际走线可能更长或更短。coupling capacitance的变化route后clock net和相邻信号线的耦合电容会变化影响clock latency。via和layer的变化CTS时可能假设clock net走某些layerroute后由于拥塞可能被迫换层。这些变化累积起来可能导致clock latency变化几十甚至上百ps对于高频design来说这足以让时序从收敛变成不收敛。5.2 减少route后时序反复的CTS配置要减少route后时序反复核心思路是让CTS阶段的预估尽量接近route后的真实情况。具体做法包括第一使用更准确的寄生参数估算模型。Innovus提供了不同的寄生参数估算模式CTS阶段可以用更精确的模式# 使用更精确的寄生参数估算 set_ccopt_property extraction_mode detailed第二给clock net设置合理的NDR。NDR可以保证clock net的绕线质量减少route后的变化# 设置clock net的NDR add_ndr -name clock_ndr -width {M3 0.1 M4 0.1 M5 0.1} -spacing {M3 0.2 M4 0.2 M5 0.2} set_ccopt_property -net_type trunk ndr clock_ndr set_ccopt_property -net_type leaf ndr clock_ndr第三控制useful skew的使用。前面讲过useful skew在route后容易失效所以如果design对时序反复敏感建议限制useful skew。第四CTS后做一次虚拟routetrial route再基于trial route的结果修时序。这样可以让时序修复更接近真实情况# CTS后做trial route trialRoute # 基于trial route结果修时序 optDesign -postCTS -drv5.3 一套完整的CTS到route时序一致性检查脚本# # CTS到Route时序一致性检查 # # Step 1: CTS后保存时序快照 puts CTS Timing Snapshot report_timing -max_paths 100 cts_timing.rpt report_clock_timing -type skew cts_skew.rpt # Step 2: 做trial route trialRoute # Step 3: 基于trial route重新提取寄生参数 extractRC # Step 4: 重新检查时序 puts Post-TrialRoute Timing report_timing -max_paths 100 trialroute_timing.rpt # Step 5: 对比两次时序报告 # 如果差异超过10%说明CTS阶段的预估不够准确 # 需要调整CTS配置或NDR # Step 6: 基于trial route结果修时序 optDesign -postCTS -drv # Step 7: 再次检查 report_timing -max_paths 100 postopt_timing.rpt注意trial route会增加运行时间但对于高频design或者时序紧张的design这一步是值得的。它可以在route之前就发现CTS阶段的预估偏差避免route后大改。5.4 一个实用的经验法则我总结了一个经验法则如果CTS后setup的WNSWorst Negative Slack余量小于时钟周期的5%那route后大概率会出问题。比如时钟周期是1nsCTS后WNS只有0.05ns的余量那route后很可能变成负的。这时候要么在CTS阶段多留余量要么提前做trial route确认。6. 问题五CTS跑得太慢或者跑不完最后一个问题不是时序问题而是效率问题。CTS跑得慢甚至跑不完在大型design或者复杂时钟结构下很常见。这不仅影响项目进度还可能导致你没法快速迭代。6.1 CTS慢的常见原因CTS慢通常有这几个原因时钟域太多时钟树结构复杂。每个时钟域都要单独建树域越多越慢。flop数量太大。CTS的计算复杂度和flop数量正相关。useful skew优化太激进。CCOpt在尝试useful skew时会做大量时序分析很耗时。机器资源不够。CTS是多线程的如果CPU核数不够或者内存不足会显著变慢。6.2 加速CTS的配置和技巧第一合理设置多线程。Innovus的CTS支持多线程确保set_multi_cpu_usage设置正确# 设置多线程 set_multi_cpu_usage -local_cpu 8第二分阶段跑CTS。先跑一个快速的CTS看结果确认没问题再跑完整的# 快速CTS关闭详细优化 set_ccopt_property effort low ccopt_design -cts # 确认没问题后跑完整CTS set_ccopt_property effort high ccopt_design -cts第三限制useful skew的搜索范围。前面讲过useful skew很耗时限制它可以加速set_ccopt_property useful_skew_max 0.03第四分时钟域跑CTS。如果design有多个独立的时钟域可以分域跑CTS每个域单独优化# 只对某个时钟域跑CTS ccopt_design -cts -clock [get_clocks clk_core]第五增量CTS。如果只是小改动不需要全量重跑CTS可以用增量模式# 增量CTS ccopt_design -cts -incremental6.3 CTS运行时间的参考基准根据我的经验不同规模design的CTS运行时间大致如下8核CPU32GB内存Design规模flop数量CTS运行时间参考小型 10万10-30分钟中型10万-50万30分钟-2小时大型50万-200万2-6小时超大型 200万6小时以上如果运行时间明显超过这个范围就要检查是不是配置有问题或者机器资源不够。6.4 一个加速CTS的实际案例我之前做过一个超大型designflop数量超过300万CTS跑了整整一个晚上还没跑完。后来做了几个调整把useful skew从默认的激进模式改成限制模式运行时间减少了40%。把CTS分成两个阶段先跑一个低effort的版本确认时钟树结构没问题再跑高effort版本。增加了CPU核数从8核加到16核。最后CTS运行时间从超过12小时降到了4小时左右。这个案例说明CTS慢不一定要换机器优化配置往往更有效。7. 几个CTS调试的通用心得前面讲了5个具体问题最后再分享几个通用的调试心得这些是我在多个项目里踩坑总结出来的不一定写在手册里但很实用。7.1 永远先看report再调参数我见过太多人一遇到CTS问题就开始改参数改了半天不知道哪个参数起了作用。正确的做法是先跑一遍CTS仔细看report定位到具体是哪个环节出了问题再针对性地调参数。Innovus的CTS report很详细report_clock_tree、report_clock_timing、report_ccopt_clock_tree_structure这几个report要养成习惯去看。7.2 保存每个版本的CTS结果CTS调试是一个迭代过程你可能会试很多组参数。建议每跑完一次CTS就保存一个版本方便对比# 保存CTS结果 saveDesign cts_version_1.enc # 对比不同版本的时序 report_timing -max_paths 100 cts_version_1_timing.rpt这样如果某一版效果特别好你可以随时回退如果某一版出了问题你也可以对比找出是哪个参数导致的。7.3 注意CTS和place的协同CTS不是孤立的它和place是强耦合的。如果place阶段的结果不好比如拥塞严重、flop分布不均CTS再怎么调也很难做好。所以遇到CTS问题有时候要回去看place的结果甚至重新跑place。7.4 建立自己的CTS检查清单每个项目的情况不同但有些检查是通用的。我习惯在CTS后跑一遍下面这个检查清单# # CTS后通用检查清单 # # 1. 时钟定义检查 report_clocks # 2. skew检查 report_clock_timing -type skew # 3. latency检查 report_clock_timing -type latency # 4. transition检查 report_clock_timing -type transition # 5. clock tree结构检查 report_clock_tree -summary # 6. ICG cell检查 report_clock_tree -icg # 7. 时序检查 report_timing -max_paths 100 # 8. DRV检查 report_check_types -max_transition -max_capacitance -max_fanout这个清单跑一遍大概几分钟但能帮你发现大部分常见问题。7.5 关于TCL脚本的一点建议最后说下TCL脚本。Innovus的CTS配置项很多建议把常用的配置写成一个proc方便复用# CTS配置proc proc setup_cts {target_skew max_trans useful_skew} { set_ccopt_property target_skew $target_skew set_ccopt_property target_max_trans $max_trans set_ccopt_property useful_skew $useful_skew puts CTS setup done: skew$target_skew, trans$max_trans, useful_skew$useful_skew } # 使用 setup_cts 0.15 0.2 false ccopt_design -cts这样每次调参数只需要改一行不用在脚本里到处找配置项。而且proc可以带参数方便你做参数扫描。CTS这件事说到底是一个理解工具行为理解design需求的过程。工具的参数是死的但每个design的情况是活的。同样一组参数在这个design上效果好换个design可能就出问题。所以与其死记参数不如理解每个参数背后的逻辑然后根据design的实际情况去调。上面这5个问题和对应的解决方案覆盖了我在实际项目里遇到的大部分情况但肯定不是全部。如果你遇到了这里没提到的问题欢迎一起交流数字后端这个领域经验都是在踩坑中积累出来的。