☰
CCOpt复杂时钟树配置调试:extract到skew group避坑
2026/10/29 14:54:15 网站建设 项目流程

简介:这份资料面向使用 Cadence Innovus 进行物理实现、需要处理复杂时钟网络的集成电路设计工程师,尤其适合已具备 CTS 基础、希望深入掌握 CCOpt 配置与调试的中高级读者。内容围绕时钟网络约束设置、时钟树结构配置与优化目标展开,依次讲解时钟网络分析、时钟树构建、时钟优化、后处理和验证四个主要阶段各自的调试要点,并介绍基本属性检查、CCOpt 时钟树调试器、时钟路径追踪与跨视图探测、报告命令以及利用属性洞察设计等实用技术,还涉及架构级时钟控制与 sink 修改、刻意偏斜、独立时钟树平衡等进阶话题。资源包共 1 个 PDF 文件,大小约 1.3MB,为 Cadence 官方 2019 年版技术手册,适合作为随查随用的案头参考。目前已有 211 人学习,可帮助读者建立分阶段的调试思路,定位时钟树瓶颈与延迟不均等问题,提升时序收敛效率。

1. 一份讲“复杂时钟”的应用笔记,为什么值得到手

“CTS 跑完,skew 全在 30ps 以内,时序也过了,面积却比上一版多了 8%。”这是 cadence innovus cts 流程里最容易被忽略的一类失败:结果看起来没有问题,代价却藏在中间阶段。这份 41 页的应用笔记只讲一件事:CCOpt 在结构复杂的时钟网络下怎么配置、怎么调试。它默认你已经会跑命令序列,所以把篇幅全放在 cluster、trial、full 三段各自能观察到什么,以及在哪一步动手成本最低。它适合手上有多个时钟域、大量 clock gating、macro feedthrough 的物理设计工程师;如果设计只有一两个时钟、门控结构很浅,默认设置基本够用,不必折腾。真正需要它的是那些 SDC 已经无法准确描述时钟架构的场合。

2. CCOpt 的六个流程阶段:每一步能看到什么、该在哪停

CCOpt 全称 clock concurrent optimization,做的是时钟树综合和 datapath 的并发优化。对时钟少、门控结构浅的设计,直接跑默认设置基本能出结果。但结构一复杂,一次跑到底再回头看,很多问题已经埋在中间步骤里,改一个属性就要整跑一遍,一次几小时。比较划算的做法是把流程拆开看,让每一步都变成可观测的节点。这份笔记把整个流程切成九个动作:post-CTS 优化与 timing 设置、布线规则、影响提取的属性、时钟提取、修改 clock/skew group、时钟构建(cluster/trial/full)、early global route 与 conditioning、Nanoroute 与 conditioning、datapath 优化。对调试而言,前六步是主战场,后三步主要用来验证结果。

2.1 extraction 之前:所有影响裁剪的设置必须落在这里

CCOpt 会自己从库、设计和 SDC 里推断出一整套设置。问题是这些数据源本身是给 signoff 用的,不一定适合拿来生成时钟网络。笔记里有一句话很关键:时钟提取这一步会读取全局属性和 pin 级属性,提取完成之后再改,前面已经建立的结构不会自动重算。换句话说,buffer/inverter/clock gating 单元列表、transition 目标、skew 目标、sink_type 这些东西,必须在 extract 之前设好;extract 之后能做的只是修改 clock 和 skew group、增删 skew group、按 clock_tree/skew_group/power domain 覆盖属性。

常见做法是先把 post-CTS 的 timing 口径定下来,再设布线规则,最后才设单元和 target。顺序反了,前面的设置会被后面的提取过程抹掉一部分。下面这段是 legacy 和 Stylus 两种写法的对照,实际跑的时候选一套即可:

# post-CTS 优化与 timing 口径,必须在 extract 之前定 # 关闭 IO latency 修改:chip level 和结构超复杂的设计基本都要关 set_ccopt_property update_io_latency false set_db cts_update_clock_latency false # OCV 延迟模式,和 signoff 的口径保持一致 setAnalysisMode -analysisType onChipVariation set_db timing_analysis_type ocv

update_io_latency控制 CCOpt 是否改写 IO 端口的 latency。chip level 设计里 IO 的 latency 往往由顶层约束决定,让工具去动它,后面很容易和 signoff 对不上。onChipVariation与timing_analysis_type ocv是同一件事的两种写法,前者是 legacy 的分析模式开关,后者是 Stylus 那一套。OCV 开不开,直接影响 CCOpt 在平衡时对每条路径的悲观程度,进而影响插进去的 buffer 数量。

布线规则同理,必须早设。时钟网络一般走非默认规则,而且 top、trunk、leaf 三段可以分开给:

# 先建一个 NDR 名字,具体规则参数按工艺给的规则名填 create_route_type -name clock_non_default_rule_1 # 三段分别绑到同一个 NDR,也可以各绑各的 set_ccopt_property route_type -net_type {top trunk leaf} clock_non_default_rule_1 set_db cts_route_type_top clock_non_default_rule_1 set_db cts_route_type_trunk clock_non_default_rule_1 set_db cts_route_type_leaf clock_non_default_rule_1

这里最容易出问题的地方是只给了 top 和 trunk,漏了 leaf。leaf net 数量最多、扇出最碎,恰恰是最需要 NDR 的一层。如果发现某棵树的 leaf 段宽度和 trunk 一样却在报告里看不到规则生效,先回来查-net_type列表里有没有leaf。

2.2 cluster、trial、full:三段构建各自能看出什么

时钟构建被拆成三段,这个划分不是为了好看,而是每段暴露的信息不一样。cluster 阶段按 skew group 的结构做初步聚类,决定哪些 sink 归到哪棵树、缓冲层级大致怎么分。这一步能看出的是“分组对不对”:如果某个 skew group 里混进了本该独立的时钟域,cluster 之后的层级结构就会显得很怪,比如某条分支突然多出两三级 buffer,或者两棵逻辑上无关的树被塞进了同一个根。

trial 阶段是试算,估算 insertion delay 和 buffer 用量,不落最终结构。它给出的价值是量级判断:某棵树预估要插 800 个 buffer,而你预期是 300,那大概率是 skew 目标太紧或者 sink 数被多算了,这时候回头改属性比 full 跑完再改便宜得多。full 阶段才真正建树、落物理结构,后面接 global route 和 Nanoroute。

三段的关系是递进的,但有个硬限制要记住:cluster 和 trial 阶段停下来之后,这次 run 不能重启继续。所以这两段适合用来“看”,不适合用来“停在那改完再续”。我一般把观察点放在 extraction 之后、进 cluster 之前,因为那个位置改任何东西都只需要重跑提取和构建,不用从头加载设计。

2.3 在流程里插检查点:哪些能停、哪些停了要重跑

能安全停下来做修改的位置只有一个:extraction 完成之后、时钟构建开始之前。这个点上整个时钟网络已经从设计和约束里提取出来了,clock tree 和 skew group 都在库里,可以增删、可以改属性、可以补 sink_type,改完直接往下跑构建。笔记里提到的“修改创建的 clock/skew group”“增删 skew group”“按 clock/skew group 覆盖属性”全部发生在这一段。

不能停的位置是 cluster、trial、full 内部。这不是工具保守,是这三段共享中间状态,中断之后没有可恢复的锚点。所以调试节奏应该是:先跑到 extract,用报告命令把树和组打印出来核对,确认无误再一口气跑完构建。如果非要在 trial 之后看数据,那就接受“看完重跑”的成本,别指望续跑。

还有一个常被忽略的点:即使最终时序和拥塞看起来都正常,也值得在中间阶段停下来看。笔记里明确写了,中间结果里能发现的面积、运行时间、时序、功耗改进机会,到了流程末尾基本看不出来。因为末尾所有优化都已经叠加在一起,你分不清某个 buffer 是平衡需要的还是约束逼出来的。这也是为什么复杂时钟设计的调试成本高——不是工具慢,是信息在末尾被压缩掉了。

3. 跑之前的配置清单:单元、target、sink_type 三件事

配置项看起来琐碎,但真正影响结果的就三类:用哪些单元、平衡到什么程度、哪些点不算时钟终点。这三类都必须在 extract 之前落定,而且都能在 extract 之后按 clock_tree、skew_group、power domain 做更细的覆盖。下面按影响面从大到小排。

3.1 单元列表:空列表的特殊含义与 don't use 的优先级

buffer_cells、inverter_cells、clock_gating_cells、logic_cells这四个列表不设也能跑,工具会自动从库里抽。但自动抽取的结果往往不优,因为它不知道你的 Vt 策略和 always-on 需求。更麻烦的是空列表有特殊语义:它表示“全库都参与挑选,不管 Vt 家族和驱动能力”,这在多 Vt 工艺里基本等于放弃控制。

# 显式给出可用单元,别留空 set_ccopt_property buffer_cells {BUF_X2 BUF_X4 BUF_X8} set_ccopt_property inverter_cells {INV_X2 INV_X4 INV_X8} set_ccopt_property clock_gating_cells {CKGATE_X2 CKGATE_X4} set_ccopt_property logic_cells {AND2_X2 OR2_X2 MUX2_X2} # Stylus 写法 set_db cts_buffer_cells {BUF_X2 BUF_X4 BUF_X8} set_db cts_inverter_cells {INV_X2 INV_X4 INV_X8} set_db cts_clock_gating_cells {CKGATE_X2 CKGATE_X4} set_db cts_logic_cells {AND2_X2 OR2_X2 MUX2_X2}

clock_gating_cells和logic_cells最好一起给。门控单元参与优化之后,工具可以把门控本身当成时钟路径的一部分来平衡,而不是在门控前后各插一堆 buffer 去凑延迟,插 buffer 的数量能明显降下来。always-on 单元必须显式写进列表,否则提取阶段可能被过滤掉,后面低功耗域一跑就报找不到可用单元。

优先级这块有两个版本行为要记清。第一,写在 CCOpt 属性里的单元会覆盖库定义的dont_use,也就是说库说别用,你写进列表它照样用。第二,从 18.1 开始,用户自己加的dont_touch会被遵守,不管这个单元在不在 CTS 单元列表里。这两条合起来的意思是:属性列表不是万能通行证,人为加的 dont_touch 优先级更高。另外,instance 可以设成 size-only,给工具留更多调整余地。

单元给了不等于都会用上,库列表本身有过滤和裁剪。查原因用这两条:

report_ccopt_cell_filtering_reasons report_cts_cell_filtering_reasons

输出会告诉你某个单元是因为驱动不足、Vt 不匹配、还是被 dont_use 挡掉。我见过最常见的情况是列表里混进了一个只在某工艺 corner 存在的单元,综合阶段没报错,CTS 阶段静默过滤,最后树的结构和预期差一大截。

3.2 target_max_trans 与 target_skew:怎么定数值

这两个目标值是控制时钟网络规模的主要旋钮,笔记里对它们的评价是“非常重要”,尤其在低功耗设计里。target_max_trans定得太松,工具会用大驱动单元和长走线去凑,动态功耗上去;定得太紧,buffer 数量和面积一起涨。target_skew定得太紧的后果更直接:过度平衡(excess balancing),工具为了让两条本来物理距离就很远的路径对齐,硬插 buffer 拉延迟。

# 全局目标,extract 之前设 set_ccopt_property target_max_trans 80ps set_ccopt_property target_skew 50ps # Stylus 写法 set_db cts_target_max_transition_time 80ps set_db cts_target_skew 50ps

数值怎么定,我的习惯是先看 signoff 的 transition 上限,取它的 70%~80% 作为 CTS 目标,留出布线后的余量。skew 目标则看时钟周期和下游寄存器的 setup 余量,一般取周期的 3%~5%,超过这个范围再收紧收益很小,代价却是 buffer 数量线性上涨。要注意 flow 本身会避开库相关的 DRV,所以target_max_trans再松也不会突破库的硬限制,但它会影响工具在“贴着上限”还是“留余量”之间怎么选。

这两个值在 extract 之后还能按 clock_tree、skew_group、power domain 分别覆盖。比如某个低速域可以把 skew 放到 150ps,省下来的 buffer 留给高速域。这种分级设置是复杂设计里省面积的主要手段之一。

还有一层容易忽略:如果没有设全局 target,工具会去用 SDC 派生的 transition/skew 目标。SDC 里的值通常是给时序分析用的,未必适合指导建树。所以要么显式设全局值,要么清楚地知道自己在继承什么。

3.3 sink_type 的 stop / ignore / exclude:三种裁剪语义

sink_type是 shaping 时钟网络提取的核心属性,三个取值stop、ignore、exclude用来控制追踪在哪停、哪些分支不算终点。这是把“结构上是一个时钟、逻辑上该分开”的分支切断的主要手段,比事后改树便宜得多。

# 让某个 pin 成为追踪终点,不再往下追 set_ccopt_property -pin u_core/u_clkdiv/CP sink_type stop # 整个分支跳过,既不当终点也不往下追 set_ccopt_property -pin u_core/u_rtl_dbg/gclk sink_type ignore

语义上,stop是把追踪停在这里并把这个点当作 sink,适合分频器输出、门控后的局部时钟这类“到此为止”的位置;ignore是完全跳过该分支,适合 DFT 或调试逻辑里那些不该进平衡的时钟;exclude是把该点从时钟网络里摘出去,不参与平衡也不计入 sink 统计。三者差别细微,具体行为建议以所用版本的文档为准,设完一定要用报告命令核对 sink 数量对不对。

一个常见的误用是把stop当exclude用。结果是该点仍然被当成 sink 参与平衡,工具为了让它和主树对齐插了一串 buffer,而你本以为它已经被摘出去了。核对方法很简单:设之前记一下树的 sink 数,设之后再报一次,数字没变就说明属性没生效或者语义用错了。

3.4 非默认布线规则与 OCV:容易漏的两处

布线规则在 2.1 已经给过写法,这里补两个落地细节。一是create_route_type的名字一旦被引用就别再改,改名之后旧的绑定会静默失效,报告里看不出报错,只是 leaf net 悄悄回到了默认规则。二是 top、trunk、leaf 三段可以绑不同 NDR,高速域给宽线宽间距,低速域给普通规则,这样能在不牺牲主树质量的前提下省布线资源。

OCV 这块,除了前面说的onChipVariation和timing_analysis_type ocv,还要确认延迟计算模式和分析模式是同一套。两者不一致的时候,CCOpt 平衡时用的悲观度和后面 timing 报告用的悲观度不同,表现就是 CTS 报告里 skew 很漂亮,signoff 一跑却冒出一堆违例。这类问题查起来很费时间,因为它不报错,只是数字对不上。

4. 调试技术:从报告命令到 clock tree debugger

调试这件事,工具给的手段其实不少,难的是知道先看什么。我的顺序是:先做基础检查确认提取没问题,再进图形界面看结构,最后用属性反查定位到具体是哪个约束在起作用。三步的顺序不能反,结构没确认就去看时序,容易在错误的前提上分析半天。

4.1 提取完成后的五项基础检查

第一项,时钟树数量和每棵树的 sink 数。数量对不上,说明 extraction 阶段的裁剪出了问题。第二项,skew group 的划分,以及它和 clock tree 是不是 1:1。笔记里明确说,多数设计是 1:1,复杂结构才会出现一个 clock tree 对应多个 skew group。如果你的设计出现了大量一对多,先确认这是架构本身的需要,还是属性设错导致的分裂。第三项,clock gating 有没有被正确识别,门控单元如果被当成普通组合逻辑,后面的平衡会完全走偏。第四项,sink 总数和预期是否一致,少了通常是sink_type裁多了,多了通常是漏设。第五项,检查有没有单元被过滤,用 3.1 里那两条报告命令。

# 时钟树与 skew group 的基本盘 report_ccopt_clock_trees report_ccopt_skew_groups # 单元过滤原因 report_ccopt_cell_filtering_reasons

这三条命令的输出建议在 extract 之后立刻存一份,改完属性再存一份,做 diff 比肉眼回看靠谱得多。复杂设计里改一个 sink_type 可能影响几十个 sink 的归属,靠记忆核对迟早出错。

4.2 clock tree debugger 里看什么

图形化的时钟树调试器主要解决一个问题:把报告里的数字和物理结构对上。我一般按这个顺序看。先按 clock tree 高亮,确认整棵树的覆盖范围符合预期,有没有跨 die 或者跨电源域的长分支被强行拉进来。再按 skew group 高亮,看组内的 sink 分布是不是集中,如果一组 sink 散落在芯片两端,那这组的 skew 目标再合理也平衡不好。

然后看层级。正常的树,buffer 层级从根到叶应该是平滑递增的,如果某个分支突然多出两三级,大概率是那个方向有特殊的约束或者物理障碍。最后看长尾路径,也就是延迟最大的那几条,判断它是物理距离造成的还是约束造成的。物理距离造成的,改约束没用,得改 floorplan 或者接受它;约束造成的,回去查那个点的 sink_type 和所属 skew group。

交叉探测(cross probing)到 layout 和 schematic 视图是这一步的关键动作。报告里的一个 pin 名字,在 layout 里看到它周围被塞了一圈 buffer,和只在表格里看到“该点延迟偏大”,判断完全不同。

4.3 报告命令与属性反查:从 pin 推回约束

定位到具体某个 pin 之后,下一步是搞清楚“是谁在管它”。这时候要反查它属于哪个 skew group、哪个 clock tree、上面挂了哪些属性。常见的排查路径是从 pin 到 group 到属性,一层层往上问。

想确认的事常用手段重点看什么
某个 pin 归哪棵树报告命令按 pin 过滤所属 clock tree 名称、层级深度
某个 group 有多少 sinkskew group 报告sink 数量、source 列表
属性到底生效没有逐条 get 属性值返回值是默认值还是你设的值
单元为什么没被用cell filtering 报告过滤原因字段
树的结构是否合理clock tree debugger层级递增、长尾分支

反查的价值在于避免“凭感觉调参”。我见过不止一次,调了半天target_skew,最后发现那个点压根不在预期的 skew group 里,是 extraction 阶段被另一个属性划走了。属性生效性一定要逐条确认,尤其是带-pin、-skew_group、-clock_tree限定的写法,限定符写错的时候工具通常不报错,只是静默不生效。

5. 避坑与排查:五条踩过的记录

这一节按“现象、原因、解决”写,都是我在多时钟域设计里真实遇到过的。有些问题的代价不是多跑一次,而是把错误的结构带进了 signoff。

5.1 buffer 类型五花八门,面积莫名上涨

现象:CTS 跑完,时钟树上出现了七八种不同驱动、不同 Vt 的 buffer,面积比预估高出一截,但时序并没有更好。

原因:四个单元列表留空或者只给了部分。空列表的特殊语义是全库参与挑选,工具会在满足 DRV 的前提下到处试,多 Vt 工艺下尤其容易挑花。另外,如果列表里没有显式写 always-on 单元,工具在某些域里会退而求其次选别的。

解决:四个列表全部显式给,按 Vt 分档写清楚;always-on 单独补进去;跑之前用report_ccopt_cell_filtering_reasons确认没有意外过滤。改完重跑,buffer 种类一般能收敛到三四种。

5.2 skew 目标收紧后,buffer 数量翻倍但收益为零

现象:把target_skew从 50ps 收到 20ps,buffer 数量涨了近一倍,运行时间翻倍,signoff 的 setup 余量几乎没变。

原因:过度平衡。skew 目标过紧时,工具会为了让物理上相距很远的点对齐而硬插 buffer 拉延迟,这些 buffer 增加了功耗和面积,却对真正的关键路径没有帮助,因为瓶颈在别处。

解决:先确认 skew group 的划分是否合理,把不该在一组的点拆开,再谈目标值。目标值按周期的 3%~5% 起步,逐级收紧,每级都看 buffer 增量和时序收益的比值。比值明显变差就停手。

5.3 在 trial 阶段停下来改完,想续跑发现续不上

现象:跑到 trial 看了一眼数据,停下来改了属性,想继续构建,发现不能重启,只能从头加载设计重跑。

原因:cluster 和 trial 阶段共享中间状态,没有可恢复的锚点,工具不支持从中断处续跑。

解决:把改动点前移到 extraction 之后。那个位置可以增删 skew group、改属性、调 sink_type,改完直接进构建,不用重来。确实需要在 trial 之后看数据的,就当它是一次性观察,看完接受重跑成本。

5.4 目标值和预期不一样,查不到是谁设的

现象:明明没有设全局target_max_trans,报告里却有一个具体值,而且和你以为的 SDC 值对不上。

原因:没有全局目标时,工具会继承 SDC 派生的 transition/skew 目标。SDC 里那些值是给时序分析用的,可能来自某个 corner 的约束,不是你想要的建树目标。

解决:显式设全局 target,再按 clock_tree/skew_group 覆盖。设完之后逐条 get 回来确认生效,别只看命令回显没报错就认为设上了。

5.5 macro feedthrough 的时钟被一起平衡,长路径上插满 buffer

现象:跨 macro 的一条时钟路径被强行拉平,macro 两侧插了大量 buffer,插入延迟明显偏大,功耗也上去了。

原因:CCOpt 把 feedthrough 之后的时钟当成了同一棵树的一部分,试图把两侧一起平衡。结构上它们确实相连,但物理距离和内部延迟决定了它们不该用同一个 skew 目标。

解决:用sink_type在 macro 边界切断追踪,或者把两侧拆成独立 skew group 分别给目标。改完核对两侧的 sink 数和 insertion delay,确认它们各自独立平衡。这类结构调整要在 extraction 之后、构建之前做,晚了就得重跑。

6. 架构级干预与验证闭环:什么时候该动手

前面几章讲的都是“看清”,这一章讲“动手”。笔记里列了一串架构级控制场景:把 datapath 部分从时钟网络里挡出去、对某些时钟点故意加 skew、平衡互不相交的时钟树、阻止从同一 master 派生出来的 slave clock 被一起平衡、让不该被 skew 的复杂控制逻辑保持原样、让驱动输出引脚的逻辑维持相近 skew、处理带时钟 feedthrough 的 macro、禁用某些时钟的合成、防止过度插 buffer。这些手段在多数设计里用不上,一旦用上,基本都是因为 SDC 已经没法准确表达时钟架构。

场景主要手段验证方式
datapath 不该进时钟网络sink_type切断追踪对比切断前后的 sink 数
需要人为 skew按 skew group 覆盖target_skew查该组的实际 skew 分布
两棵不相交的树拆成独立 skew group确认两组 sink 无交叉
slave clock 不该同平衡独立 skew group 或切断追踪查各自 insertion delay
macro feedthrough边界切sink_type两侧分别看延迟与 buffer 数
某些时钟不合成禁用对应 clock tree 的构建查树列表里是否还有它
buffer 插过头放宽target_max_trans/target_skewbuffer 数量与 DRV 报告

动手之后的验证必须有闭环,不然改了什么、好了没有全靠感觉。我的做法是固定三步。第一步,改之前存一份报告快照,包括树列表、skew group 列表、sink 数、buffer 数。第二步,只改一个变量,重跑 extract 和构建,不要一次改三四个属性,否则出了问题归因不了。第三步,用同样的报告做 diff,重点看三件事:目标那棵树的 sink 数有没有按预期变化、buffer 数量是不是朝着预期方向走、关键路径的 insertion delay 有没有改善。三项里有两项没动,说明属性没生效或者改错了对象。

有个细节值得单独提:故意加 skew 这件事,工具不会告诉你加得对不对,只能靠时序结果反推。我的习惯是先给一个保守值,看关键路径的余量变化,再逐步加,每步都留报告。一次性给大值,很容易把别的路径推成新的瓶颈,然后你面对的是一个全新的违例集合,原来的分析全废。从那以后我每次改 skew group 级别的属性,都强制走一遍“存快照、单变量、做 diff”的流程,宁可多跑两次,也不要在结果里猜因果。希望帮到你。

本文还有配套的精品资源,点击获取

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

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

立即咨询