
数字后端做了几年经常有新人问我综合做完、floorplan摆完接下来干什么十有八九答案就是时钟树综合CTSClock Tree Synthesis。这步在整个数字后端流程里属于那种“看起来就是绕绕线、插插buffer实际上一堆门道”的环节。芯片能不能按时收敛、功耗和面积是否可控、时序有没有隐藏的坑很大程度上都压在CTS这块。这篇笔记我就把时钟树综合的方法和实际项目里的操作心得摊开来讲从核心原理到Innovus里的具体做法再到那些文档里不会写的踩坑经验一次说清楚。1. 时钟树综合的核心思路与方案选型1.1 时钟树综合到底在解决什么问题先说个最朴素的比喻。时钟信号就像全芯片的“心跳”所有寄存器都靠着这个心跳来同步工作。理想情况下每个寄存器的时钟边沿应该同时到达这样数据从一个寄存器传到下一个寄存器时大家都踩着同一个节拍。但现实里时钟信号从时钟源比如PLL的输出到各个寄存器要经过很长的布线路径有的路径长、有的路径短到达时间自然就有先有后。这个先有后的时间差就是时钟偏斜业内叫clock skew。如果skew太大就可能出现两种情况数据到达太晚目标寄存器采样不到正确值setup违例或者数据到达太早把还没被采样的旧数据冲掉了hold违例。所以CTS的核心任务非常单一在满足物理约束和时序约束的前提下插入buffer和inverter把时钟信号从源头平稳地送到每一个时序单元的时钟端让skew尽量小、clock latency尽量可控同时功耗和面积不能失控。这里还要分清两个概念CTS之前我们管这叫时钟树综合CTS之后还要做时钟树优化CTOClock Tree Optimization。CTS阶段主要解决全局skew和基本latency问题CTO则是在布线前针对具体的setup/hold违例做局部优化比如插delay cell修hold、调整buffer大小修transition。很多新人以为CTS做完时钟树就完事了其实后面还有个拉锯战。1.2 实际项目中怎么选时钟树结构数字后端的时钟树结构一般有三种常见形态H树、网格树Mesh、以及最常用的缓冲树Buffer Tree。H树靠平衡的物理拓扑把时钟路径做到等长skew天生小但缺点是很吃布线资源而且对block形状和摆放位置太敏感自适应能力差在真实项目里很少单独用。网格树靠顶层金属把时钟短接到一片区域里skew能做到极小但功耗和短路风险都很突出一般只在超高速、对skew要求极其苛刻的模块里局部使用。我们日常做的主流方案是缓冲树。它的思路很直接从clock root出发工具自动分析所有sink寄存器时钟端、宏单元时钟端、时钟门控单元等的位置逐级插入buffer把信号扇出摊平。缓冲树的好处是灵活能跟着floorplan走功耗和面积也可控配合工具里的target skew设置就能满足绝大多数设计场景。在Innovus里还有个常见选择是使用balanced tree还是default tree。Balanced tree会强制工具按H树的思想去平衡各路径长度收敛出来的skew更漂亮但代价是buffer数量明显变多、布线资源消耗更大。默认模式则是工具按时序和物理拥塞程度动态决定树的形态功耗和面积更好。以我自己的项目经验除非是高速接口或者DDR相关的时钟域默认模式足够用把target skew和useful skew配置到位就行。纯为了skew好看而无脑开balanced tree后期修congestion和DRC的时候会想哭。1.3 CTS阶段的关键性能指标做CTS不是跑完命令就拍屁股走人你得会看报告、会判断结果好还是坏。核心指标我一般盯这几个global skew全局偏斜、local skew局部偏斜、insertion delay时钟插入延迟、transition time时钟信号跳变时间、以及时钟网络的功耗和面积占比。Global skew是所有sink里最大和最小时钟延迟之差报告里经常叫clock skew。Local skew则要精细得多它看的是时序路径上有时序关系的两个寄存器之间的skew比如一个launch flop和一个capture flop在物理上紧挨着那么它们之间的skew才真正影响这条路径的时序收敛能力。很多新人只看global skew结果全局数字挺好看一到时序分析就疯狂报错原因就是local skew被人忽略了。Insertion delay指从clock root到sink的总延迟它决定了时钟树对片外或片内其他模块的时序接口过大会影响I/O时序过小又可能导致CTS难收敛。Transition time则是时钟信号从一个电平跳到另一个电平的时间太大会导致寄存器采样不稳定太小则说明buffer驱动过强、功耗浪费。这些参数互相牵制所以CTS本质上是个平衡的艺术不是单点最优。2. 时钟树综合前的准备与关键配置2.1 CTS之前必须完成的检查项很多人一拿到place后的设计就急吼吼地跑CTS这是大忌。CTS是全局性操作前面任何一步留了隐患到这里都会成倍放大。我习惯在CTS前跑一轮pre-CTS检查清单每项都确认没问题再动手。第一项是时钟定义检查。用report_clock_tree和check_timing把时钟源、时钟门控、生成时钟generated clock都确认一遍。这里最常见的坑是generated clock的master pin选错导致CTS的root不对后面怎么调都白费。第二项是scan chain check扫描链上的shift时钟和功能时钟必须正确mux否则CTS时工具会把scan clock和func clock当成两个不相干的domain处理时序会出大问题。第三项是floorplan检查硬宏hard macro的pin位置是否合理、blockage是否堵住了时钟走线方向、端到端的channel有没有预留这些都会直接影响CTS的绕线质量和拥塞度。还有一项容易被忽略的是power intent。时钟树上的buffer用的通常是标准单元库里的单元它们的电源域必须和所在区域的逻辑一致如果floorplan阶段没把power domain切干净CTS插出来的buffer可能直接被工具放在没有power connection的区域后期要么修起来麻烦要么直接造成IR drop问题。2.2 时钟树综合spec文件的编写技巧CTS spec是整个CTS阶段的“剧本”Innovus、ICC2这些工具都是照着spec来干活的。Spec里定义了时钟树的root、sink、需要balanced的时钟组、skew目标、最大transition、最大insertion delay、可用的buffer/inverter单元列表、禁止使用的层和区域等。新手最容易犯的错就是从项目模板里拷一份过来改两笔就上结果每个clock domain的约束要求其实差别很大。我写spec时有一个习惯把时钟按功能分成几类不同类型的时钟给不同的skew和transition目标。比如核心逻辑时钟对skew要求适中、但时钟频率高transition必须控制紧一些低速外设接口时钟频率低、时序裕量大skew给松一点能省大量buffer功耗和面积都划算高速DDR或者SerDes相关的时钟那是特殊对待不仅skew目标紧还要在旁边加useful skew的约束让工具能利用skew去优化关键路径。还有一个细节是关于sink的。Spec里可以手动指定某些pin是sink比如宏单元memory的时钟pin、模拟IP的时钟输入这些如果不显式加进去工具可能会漏掉或者用默认方式处理后续很容易出现局部skew超标。手动加sink时还要注意把inverter的input pin设成ignore否则工具会把反相器的输入也当成sink加进去做出一个废树。2.3 Useful skew的正确打开方式Useful skew也叫skew scheduling是个好工具但也容易坑人。它允许非零skew存在利用launch clock晚到或capture clock早到来给关键路径让出时间裕量从而修掉setup违例。听着很美好但它的代价是让别的路径的时序压力变大或者让hold变得难修所以实际中使用useful skew得“定向”而不是“全局”。在Innovus里开启useful skew的主要方法是给时钟组设置max useful skew的值并且配合set_clock_tree_options里的useful skew mode。这里我的经验是只有当某几条路径的setup violation很顽固、通过优化数据路径已经修不动的情况下才启用而且max useful skew一开始给个几十皮秒的量级试跑看看整体时序有什么变化切忌一上来就设个几百ps那是给自己找麻烦。另外useful skew还会影响hold修复的难度。因为hold修复需要在时钟路径上插入delay cell如果本来就有useful skewCTS后的hold slack会变差后期ECO修复的量也会增加。所以setup和hold像天平的两端用useful skew修setup时一定要同步跑一遍post-CTS hold检查别按下葫芦浮起瓢。3. 时钟树综合实操流程与关键实现3.1 从place到CTS的完整操作步骤这里我以Innovus工具为主线把从place完成后到CTS结束的典型流程走一遍命令和参数都是我在项目里实际用过的可以直接参考。第一步读入设计并做预处理。Place完成后先跑optDesign -preCTS做一轮预CTS优化把数据路径上的setup问题粗修一轮。这一步的主要目的是让CTS开始时数据路径的时序状态尽量干净工具做时钟树时能更专注于skew和latency的收敛而不是同时操心一堆setup违例。第二步创建并检查CTS spec。用create_clock_tree_spec生成初始spec文件然后手动检查或修改关键参数。我通常会重点看这几行Clock tree name、Root、Skew group、Target skew、Target transition、Insertion delay target。如果有多个时钟域需要平衡可以在spec里用BalanceClockTree加上时钟组名。第三步执行CTS。命令是clock_design_opt这个命令会一次性完成时钟树的综合和优化。跑之前我会先ccopt_design -cts只做CTS不做优化查看报告确认时钟树本身的指标是否达标再跑clock_design_opt做全面优化。分两步走的好处是问题定位更清晰如果结果不好一眼就能看出是CTS阶段的问题还是优化阶段的问题。第四步检查CTS结果。跑完CTS后用report_clock_tree和report_clock_timing分别看时钟树结构和时序报告。重点看global skew、per-clock-domain skew、还有每个domain的max transition是否超限。我一般还会打开时钟树视图在GUI里看一看有没有特别长的绕线或者大量buffer挤在一个区域的情况这种东西只看数字往往是看不出来的。第五步post-CTS优化和hold修复。CTS完成后数据路径上会暴露出新的setup和hold违例这时跑optDesign -postCTS和optDesign -hold分别处理。注意hold修复通常在setup修完之后单独跑因为如果先修hold再修setup可能会把hold又弄坏。3.2 Innovus中时钟树相关核心命令与参数工具命令这种东西用多了就熟但新手往往不知道该重点记哪些。我把CTS阶段高频用到的几组命令整理成了一张速查表项目里可以直接对照着用。命令作用关键参数与实例create_clock_tree_spec生成CTS spec文件create_clock_tree_spec -file cts.specsource cts.spec加载spec文件修改后重新加载注意路径和文件名report_clock_tree查看时钟树结构-detail看每一级buffer-matrix看sink矩阵report_clock_timing查看skew和delay报告-type skew或-type latency按domain过滤cco_opt_design只做时钟树综合-cts模式先看CTS本身质量clock_design_optCTS优化全流程常用-through指定优化路径-outDir存logopt_design优化数据路径时序-preCTS、-postCTS、-hold按阶段选参数set_clock_tree_options设置CTS全局参数-target_skew、-target_transition_time、-max_insertion_delayset_clock_tree_exceptions设置时钟树例外-stop_pin、-through_pin、-dont_bufferset_ccopt_property设置CCopt属性-max_capacitance、-max_transition等按需调整我这里特别说一下set_clock_tree_exceptions。这个命令处理的是时钟树上的特殊点比如某个pin需要stop不加buffer、某个macro的时钟pin需要作为leaf处理、某段net不允许插buffer。我之前遇到过一个模拟IP的时钟pin工具总是给它加buffer导致模拟模块的时钟相位不对最后就是用set_clock_tree_exceptions -stop_pin指定这个pin才解决的。所以遇到奇怪的CTS行为先别急着怀疑工具坏了检查一下exception是不是有遗漏或者误设。3.3 congestion map和density map怎么从Innovus的database里导出来这个是我被问过最多的问题之一特别是刚接手别人项目、想看place阶段的拥塞情况和密度分布时。Innovus里查看和导出congestion map、density map的方法其实不复杂但新手经常在GUI里点半天找不到。一个直接的办法是在GUI里通过菜单操作。打开Innovus GUI后点击菜单栏的Tools选择Global Status或者直接在命令行敲summaryReport可以查看基本的congestion概览。但要看具体的map图需要在GUI的物理视图Physical View里通过菜单Display - Congestion然后选择对应的layer和type。Density map则在Display菜单下的Density里可选cell density、power density、pin density等不同模式。如果你需要在命令行里把图导出来方便放到报告里或者分享给同事可以用下面的方法。Innovus支持通过saveImage命令截图先用菜单或命令打开对应的map显示再用saveImage把当前视图保存成图片文件。命令格式大致是saveImage -file congestion.png -format png。但一定要先把congestion显示打开不然截出来的就是一张普通的版图。另外还有一个思路是直接导出congestion数据而不是图。用命令report_congestion -report_type summary可以输出拥塞数据文本再用脚本比如Perl或者Python把拥塞热点画成自定义的热力图。这个方法灵活能给到更直观的热点区域标识适合做flow的人。如果只是想快速看一眼GUI加saveImage足够了。3.4 实际操作里怎么判断CTS结果能不能继续往后走CTS跑完不能光看报告零违例就说可以了我一般还有一套额外的“体检项”。第一项是打开时钟树buffer的分布图看看是不是有几个区域buffer堆得特别密。这种情况通常意味着这些区域的sink特别集中或者布线资源紧张后续跑到Route阶段大概率会出congestion甚至引发DRC问题。如果发现这类区域我会回到floorplan阶段微调一下布局把高密度区域的逻辑疏散一些或者调整blockage而不是硬顶着继续走流程。第二项是检查时钟树上的串扰风险。时钟网络是最敏感的网络之一如果CTS后的时钟路径上有长距离并行线或者和高速数据路径平行走线后期布线完成后串扰带来的delay变化会非常大甚至直接让timing崩掉。Innovus里可以用report_clock_tree -traverse看时钟path的物理走线挑出特别长的net检查。最好是跑一轮快速布线trial route再评估CTS质量因为trial route后的数据比CTS刚完成时更接近真实情况。第三项是功耗评估。时钟树一般是芯片里功耗的大头尤其在低功耗设计中CTS好不好直接影响动态功耗的最终数字。跑完CTS后用report_power看一下clock网络的功耗占比如果明显高于预期需要检查是不是buffer尺寸偏大、插得过多或者时钟门控没起作用。有时候单纯把一些load小的分支换成小尺寸buffer功耗能省下一大截代价只是稍微增大一点skew但如果这个domain的skew裕量足够这笔交易完全划算。4. 时钟树综合常见问题与排查技巧实录4.1 时钟树skew超标但找不到原因项目里最磨人的问题之一就是skew超标而且报告里看不出明显的root cause。我自己排查这种问题有一套固定的顺序。第一步看报告本身。用report_clock_timing -type skew按group输出所有sink的skew分布找出最大的几个outlier。如果是同一个group里某几个sink特别慢或者特别快先看它们的物理位置是否有共性比如是不是聚在某个角落、是不是在一个深沟槽channel的尽头、是不是在hard macro的遮挡后面。物理位置有共性的大概率是绕线路径异常打开时钟树视图沿着出问题的path走一遍就能看到是不是走了远路。第二步查sink的属性。有些pin被工具识别成sink但实际物理上离其他sink很远或者这个pin的时钟负载特别小。比如某些ICGintegrated clock gating单元的时钟端和普通register的时钟端混在一个group里容易造成sink之间电容差异大skew自然难收敛。这时候看看能不能把这个ICG的时钟pin单独列成一个sink group去平衡。第三步检查exception。之前加过stop_pin或者dont_touch的时钟路径很容易变成skew outlier。我遇到过一个大IP的时钟pin设了dont_touch结果它后面带的几百个寄存器和主树之间产生了巨大skew最后把dont_touch的范围收缩到最小必要区域才解决。所以排查时要反查exception看看是不是自己挖的坑。4.2 post-CTS阶段hold违例越修越多修hold是每个后端工程师的“老朋友”但post-CTS阶段hold越修越多是很多新人的噩梦。这个问题的根因往往不是hold本身难修而是修复的时机和方法不对。CTS后出现的hold违例和数据路径上cell的delay以及时钟路径上的buffer delay都有关系。如果直接用optDesign -hold去插delay cell工具会在数据路径上大量插入buffer来补偿时钟延迟差但这些buffer会改变数据路径的电容和延迟影响setup更麻烦的是插进去的delay cell本身也要时钟同步密集插入会挤压布线资源导致局部拥塞而拥塞又反作用在时钟树上让skew恶化。我的习惯是先用report_clock_timing -hold看一下hold违例的分布如果违例集中在某几个clock domain先看这个domain的useful skew是不是设置得太激进如果是先调整spec里的useful skew设置重跑CTS这往往比后期猛插delay cell高效得多。如果违例分布很散那再用optDesign -hold修同时用set_clock_tree_options把时钟树的max transition稍微收紧一点给留出余量。还有一个容易忽略的点hold修复用的delay cell一定别插入到时钟路径上。有些工具优化策略在某些模式下会做出这种坑操作直接污染时钟树。跑完hold修复后建议习惯性重跑一次report_clock_tree确认时钟树的结构没有被优化器动过。4.3 时钟树上的信号完整性问题和功耗陷阱时钟网络的信号完整性SI问题比数据路径上的SI更难修。因为时钟信号如果受到串扰影响产生glitch或者延迟漂移影响的是一大片寄存器的采样窗口属于系统性风险。在CTS阶段就要提前布局后期才能少遭罪。一个很实用的办法是给时钟网络加shielding屏蔽线。在floorplan阶段或者CTS后的优化阶段手动或者用工具自动在关键时钟路径旁边加VSS/VDD屏蔽线能大幅减少串扰耦合。但屏蔽线会消耗布线资源所以一般只对长距离的时钟主干和跨模块走线做屏蔽普通分支就靠width/spacing约束来控制风险。功耗角度时钟树插入buffer的数量和尺寸直接影响动态功耗。有个小技巧CTS spec里可以把max transition稍微放宽一点例如从默认的60ps放宽到80ps工具就能用更小的buffer数量也没那么多功耗能省不少代价是skew会变大一点点但很多时候skew预算并没有用到极限省下这笔功耗很划算。这个思路我喜欢叫“按需定价”每个指标在有裕量的前提下尽量去换省功耗省面积。4.4 多时钟域交互时的时钟树处理现在的芯片基本都是多时钟域设计CTS时最怕的其实是跨时钟域路径上的时序问题没有得到正确约束。如果两个时钟域之间的同步器synchronizer没设好false path或者max delay约束CTS工具会尝试去平衡两个毫不相干的时钟域白白浪费大量buffer。我一般会在CTS之前用set_false_path把跨时钟域的同步路径标注干净。注意这里不是说所有跨时钟域路径都要设false path而是同步器内部的跨域路径设false path同步器下游的路径还是要保留时序约束。另外如果两个时钟域之间有频率倍频关系比如200MHz和400MHzCTS时要给倍频时钟单独建skew group并设置好两个group之间的关系否则工具很难做到各自的收敛目标。跨时钟域的CTS还有一个容易踩的坑如果一个时钟域是从另一个时钟域分频出来的比如MMCM/PLL生成的clock那么spec里的root必须是在分频器输出上而不是上游的源时钟。放错root之后整个group的sink路径会被强行加了一长段逻辑skew和latency都很难看而且这种问题报错不一定明显往往要等仿真或者时序收敛时才暴露。5. 时钟树综合的进阶用法与个人经验总结5.1 低功耗设计中的时钟门控与CTS协同低功耗设计里时钟门控clock gating地位极高而CTS和clock gating的配合直接决定了功耗优化效果。时钟门控单元ICG被插入在逻辑综合阶段但真正让它发挥省电效果要靠CTS阶段把它正确地挂进时钟树里。CTS时ICG的时钟输入是作为一个sink接入时钟树的而ICG输出的gated clock又会驱动后面的一堆寄存器。工具会把ICG从sink到它的负载作为一个独立的子树来处理。这里有个关键点ICG的输入时钟端必须设置成CTS的through pin或者sink否则工具可能不会为它正确构建子树。很多时候clock gating省电效果不佳就是CTS阶段没把ICG处理对导致ICG后面的寄存器仍然被当作直连时钟处理gating形同虚设。另外多级clock gating的树结构比如一个enable控制一级ICG再级联下一级ICG在CTS里也容易出问题主要是不好控制各级之间的skew。我的做法是对多级ICG结构把第一级ICG的时钟输出设成clock tree的through pin手工引导工具的平衡策略让每一级的relative skew都能满足要求而不是放任工具自动处理。5.2 CTS与布线阶段的衔接要点CTS做完不是终点后面还要经历布线、STA signoff、物理验证等一系列阶段。这之间的衔接如果没处理好CTS阶段辛苦攒下来的时序裕量可能在后面被消耗光。最大的坑是布线后的时钟树走线变化。CTS阶段生成的时钟树是理想化或者粗略布线后的结果真实布线完成后时钟net的电容、电阻会变化skew和latency都会跟CTS报告有偏差。所以业界成熟的流程都是在CTS后加一轮时钟树布线Clock Net Routing然后在signoff STA里基于真实时钟树布线结果重新提取RC和计算时序。如果你用的是只做了一次全局布线的快速流程一定要记得在最后signoff前重新提取时钟网络的实际寄生参数。还有一点是关于时钟树的SPEF提取。正式流程里CTS后要单独对时钟网络做高精度RC提取比如用starrc抽取clock net因为在片内片上误差OCV分析里时钟路径的悲观度最大提取精度直接关系到芯片能不能跑到目标频率。很多小团队在自研流程里忽略了这步用默认的全局提取精度去跑结果在实测时发现时序和仿真对不上。精度这东西平时感觉不到差别关键时刻就是救命的。5.3 我积累下来的几条CTS实操心得做了这么多项目踩了这么多坑我把自己觉得最值钱的几条CTS心得整理一下分享给同样在后端这条路上走的人。第一CTS的spec一定不要图省事复用旧项目的。每个芯片的频率、规模、floorplan、时钟拓扑都不一样spec里的参数哪怕看起来很像实际最优值也可能相去甚远。我见过有人图省事连续几个版本沿用同一个spec结果skew和transition的margin被慢慢吃光最后在signoff阶段才爆雷。第二CTS跑完看报告时别只看平均值要看最大值和最小值。平均值漂亮的时钟树可能有一堆个别的sink正在悬崖边上。report_clock_tree -detail的每一行都值得认真扫一遍特别是那些transition、skew指标接近上限的sink提前想办法处理好过最后在tapeout前手忙脚乱地修。第三在数控芯片这种对时序非常敏感的设计里CTS的迭代往往不止一轮。我通常做完一版CTS后会拿它去跑一轮完整的post-CTS STA看全芯片的setup/hold情况如果整体裕量还有富余就回头把target skew合理收紧一点换取更好的功耗和面积如果整体裕量紧张就反过来放松skew给数据路径让路。时钟树和数据路径是跷跷板的两端永远在博弈中找平衡。第四也是最重要的一点永远给自己留一条后路。CTS阶段的每一步都建议设置好可回退的checkpoint。因为CTS一旦跑得不对数据路径的优化结果可能也被污染了有checkpoint就能快速回到某个干净的状态重新开始不需要重新跑place和optimize。我自己的习惯是在CTS前、CTS后、hold修复后各存一个snapshot每个关键节点能保存数据库就保存别怕占硬盘。时钟树综合这步放在整个数字后端流程里看既不是最炫技的环节也不是最耗时的环节但它是最能体现一个后端工程师对时序本质理解程度的环节之一。把CTS的原理吃透、把工具命令用熟、把各种异常状况都摸过一遍之后你会发现后端的其他环节也突然“通”了很多因为CTS就像一根线串起了floorplan的效率、时序的收敛、功耗的平衡和布线的顺畅。这期的笔记就到这里下次有空我再聊聊时钟树综合之后的路由优化和signoff阶段那些事。