☰
ICG时序违例修复指南:set_clock_gating_check与latency设置实战
2026/10/6 11:45:51 网站建设 项目流程

ICG(Integrated Clock Gating)的时序问题,大概是数字后端里最让人又爱又恨的一类。爱它是因为它省功耗、面积小、逻辑简单;恨它是因为一旦setup或hold违例,尤其是CTS之后冒出来的那些,往往不像普通寄存器路径那样“加buffer就能压下去”,很多时候你越修越乱,甚至把本来干净的时钟树搞得一团糟。我自己在几个中低功耗项目里都踩过ICG的坑,从RTL阶段没约束好,到CTS后hold崩掉,再到set_clock_gating_check参数写错导致工具误报,几乎每一类问题都遇到过。这篇就把我这些年处理ICG时序违例的完整思路拆开讲,重点落在set_clock_gating_check和latency设置这两个最容易被忽视、却又最关键的约束点上。不管你是刚接触后端约束的新人,还是已经做过几个项目但总在ICG上翻车的老手,应该都能从里面找到能直接抄作业的东西。

1. ICG时序违例到底难在哪:先搞清楚它和普通路径的本质区别

很多人修ICG违例修得痛苦,根本原因是一开始就没把它当成一类特殊路径来看待。普通寄存器的setup/hold,你脑子里有一套成熟的修复套路:setup不够就降频率、拆逻辑、加pipeline;hold不够就插delay、调时钟树。但ICG不一样,它的时序检查对象、检查方式、以及CTS对它的影响,都和普通路径有本质差异。不把这层差异想明白,后面所有操作都是盲修。

1.1 ICG的使能路径和时钟路径是两套完全不同的检查

一个典型的ICG单元,内部结构可以简化成“一个锁存器 + 一个与门”。时钟信号从CP端进,使能信号EN从锁存器D端进,锁存器由时钟的反相或同相版本控制,最后与门输出gated clock。这里就出现了两条性质完全不同的路径:

  • 使能路径(EN到锁存器D):这是一条普通的数据路径,工具会做setup和hold检查,检查的参考时钟是ICG内部锁存器所对应的时钟。
  • 时钟路径(CP到gated clock输出):这是时钟树的一部分,工具默认不做普通setup/hold检查,而是通过clock gating check来约束。

问题就出在这里。很多工程师只盯着使能路径的setup/hold,却忽略了clock gating check本身也会报违例,而且这类违例的修复方式和数据路径完全不同。更麻烦的是,这两类检查在CTS前后行为差异极大,CTS之前时钟是理想化的,CTS之后时钟树真实插入延迟出来了,原本“看起来没问题”的使能路径可能突然hold崩掉。

1.2 为什么CTS之后ICG违例特别容易集中爆发

CTS之前,工具用的是理想时钟,所有时钟到达时间(latency)都是你约束里写的值,路径延迟估算也比较乐观。CTS之后,真实的时钟树buffer插进来了,每个ICG的CP端和它上游/下游寄存器的时钟到达时间都发生了变化。这时候会出现两个典型现象:

第一,使能路径的hold违例激增。因为使能信号往往来自某个寄存器,而这个寄存器的时钟和ICG的时钟在CTS后可能产生了较大的skew。如果使能寄存器时钟到得早,ICG锁存器时钟到得晚,hold就很容易崩。

第二,clock gating check的setup违例冒出来。clock gating check要求使能信号在时钟有效沿之前稳定一段时间(setup),并在之后保持一段时间(hold)。CTS后如果时钟树插入延迟让ICG的CP端到得比预期晚,而使能信号没跟着调整,setup就会违例。

我见过最典型的一个案例:一个模块里几十个ICG,CTS前时序全绿,CTS后hold违例一下子冒出上百条,全是使能路径。当时第一反应是去插delay,结果插完发现clock gating check又开始报setup,来回折腾了两天才意识到根子在latency约束没设对。

1.3 一个容易被忽略的事实:ICG的latency不是“一个值”

很多人设latency的时候,习惯性地给整个时钟域写一个统一的latency值,比如set_clock_latency 1.0 [get_clocks clk]。这在CTS之前勉强能用,但对ICG来说是有问题的。因为ICG的CP端和它输出驱动的下游寄存器,在真实时钟树里往往处于不同的层级,它们的latency天然就不一样。如果你在约束里把它们当成同一个latency,工具在CTS前的优化方向就会偏,等到CTS后真实latency出来,违例就集中爆发。

正确的做法是:对ICG的时钟输入和它驱动的下游时钟分别设置合理的latency,让工具在CTS前就“知道”这两者之间存在差异。这个差异不需要非常精确,但方向要对。下面会详细讲怎么设。

2. set_clock_gating_check:参数写不对,工具报的违例全是假的

set_clock_gating_check这个约束,是控制工具如何对clock gating单元做时序检查的核心命令。但它的参数语义比较绕,很多人要么不写,要么随便写一个,结果工具报出来的违例要么是假的,要么漏报真问题。我先把它的核心参数拆清楚,再讲实际怎么用。

2.1 setup和hold参数到底在检查什么

set_clock_gating_check -setup <value> -hold <value>,这两个值定义的是clock gating检查的额外裕量要求。注意,它不是直接设定“使能信号必须提前多少时间稳定”,而是叠加在工具默认检查规则之上的一个约束。

具体来说,工具对clock gating的默认检查逻辑是:使能信号必须在时钟有效沿之前满足setup要求(这个要求由库单元本身的时序arc决定),并在有效沿之后满足hold要求。你通过-setup和-hold参数,可以在这个默认要求上再加一层裕量。

举个例子,假设ICG库单元本身要求使能信号在时钟沿前0.2ns稳定,你写了set_clock_gating_check -setup 0.1,那么工具实际检查时要求使能信号在时钟沿前0.3ns稳定。这个额外裕量在CTS前可以用来“预留”时钟树插入延迟带来的不确定性。

注意:-setup和-hold的值不是越大越好。设得过大,工具会拼命去满足一个过严的要求,可能导致过度优化、面积膨胀,甚至引入新的违例。设得过小,又起不到保护作用。一般建议根据时钟树预估的skew范围来定,常见取值在0.05ns到0.2ns之间。

2.2 不写set_clock_gating_check会怎样

如果你完全不写这个约束,工具会用库单元的默认检查规则。这在简单设计里可能没问题,但在以下场景会出问题:

  • 时钟树skew较大的设计:默认规则没有考虑CTS后的skew,CTS后容易冒违例。
  • 多时钟域交叉的ICG:使能信号来自另一个时钟域时,默认检查可能不适用。
  • 低功耗设计中ICG数量很多的情况:默认规则下工具优化力度不够,CTS后违例集中。

我个人的习惯是:只要设计里ICG数量超过一定规模(比如几十个以上),就一定要显式写set_clock_gating_check,哪怕值设得保守一点,也比不写强。

2.3 实际约束写法与常见错误

一个比较稳妥的写法是这样的:

set_clock_gating_check -setup 0.1 -hold 0.05 [get_clocks clk_core]

这里[get_clocks clk_core]指定了作用范围。如果你有多个时钟域,建议分别设置,因为不同时钟域的skew特性可能不同。

常见的错误写法有几种:

第一种,不指定对象,直接set_clock_gating_check -setup 0.1。这样会对所有时钟生效,包括那些你并不关心或者特性完全不同的时钟,可能引入不必要的约束。

第二种,setup和hold设成一样的值。setup和hold的物理意义不同,setup对应的是时钟沿前的稳定要求,hold对应的是时钟沿后的保持要求,两者应该分别根据实际情况设定,通常hold的裕量可以比setup小一些。

第三种,在CTS之后才想起来设。这个约束最好在CTS之前就设好,让工具在CTS前的优化阶段就考虑进去。CTS后再设,工具只能做局部修补,效果差很多。

2.4 和set_clock_gating_check配合的derate设置

除了setup/hold裕量,还有一个常被忽略的点是derate。在OCV(on-chip variation)分析下,时钟路径和数据路径的derate不同,clock gating检查也会受影响。如果你发现CTS后clock gating check的违例在OCV模式下特别多,可以考虑对clock gating路径单独设置derate:

set_timing_derate -clock_gating_check -early 0.95 -late 1.05

这个设置的意思是,对clock gating检查的early路径用0.95的derate,late路径用1.05的derate。具体值要根据工艺和项目要求来定,不要照搬。我一般会在项目初期和STA工程师确认好derate策略,避免后期返工。

3. latency设置:CTS前把“预期”告诉工具,CTS后才不会翻车

latency设置是ICG时序修复里另一个关键点,也是最容易设错的地方。很多人对latency的理解停留在“CTS前给个估计值”,但实际上,latency设得对不对,直接决定了工具在CTS前的优化方向,进而影响CTS后的时序质量。

3.1 source latency和network latency的区别

在讲ICG的latency设置之前,必须先分清两个概念:

  • source latency:时钟源到时钟定义点之间的延迟,比如PLL输出到芯片时钟输入端的延迟。这个值通常由时钟源特性决定,CTS不会改变它。
  • network latency:时钟定义点到各个寄存器时钟端之间的延迟,也就是时钟树插入延迟。这个值在CTS前是估计的,CTS后会被真实值替代。

对ICG来说,我们主要关心的是network latency,因为ICG的CP端和它驱动的下游寄存器都在时钟树网络里,它们的network latency差异是导致时序问题的核心。

3.2 CTS前给ICG设latency的正确姿势

CTS前,工具不知道真实时钟树长什么样,所以需要你告诉它一个预期的latency。对ICG,我通常这样设:

set_clock_latency -source 0.5 [get_clocks clk_core] set_clock_latency 0.3 [get_clocks clk_core] set_clock_latency 0.35 -clock [get_clocks clk_core] [get_pins icg_inst/CP]

这里第一行设source latency,第二行设整个时钟域的network latency估计值,第三行单独给ICG的CP端设一个稍大的latency。为什么要给ICG的CP端单独设?因为在实际时钟树里,ICG往往比普通寄存器更靠近时钟树的某个分支点,或者因为驱动能力、负载不同,它的插入延迟和普通寄存器不完全一样。给它设一个略大的latency,可以让工具在CTS前就意识到“ICG的时钟可能到得晚一点”,从而在优化使能路径时留出更多裕量。

这个“略大”是多少?一般比时钟域的平均latency大0.05ns到0.1ns比较合适。设得太大,工具会过度优化,可能导致其他路径出问题;设得太小,起不到保护作用。

3.3 下游寄存器latency要不要跟着调

ICG驱动的下游寄存器,它们的时钟来自ICG的输出,所以它们的latency实际上包含了ICG本身的延迟加上ICG输出到寄存器的时钟树延迟。在CTS前,你可以给这些寄存器单独设一个latency:

set_clock_latency 0.4 -clock [get_clocks clk_core] [get_pins reg_downstream/CK]

这个值应该比ICG CP端的latency再大一点,因为信号从ICG CP端到gated clock输出还有一段单元内部延迟,再加上输出后的时钟树延迟。具体大多少,取决于ICG单元的CLK-to-Q延迟和预估的输出网络延迟。一般可以设成ICG CP端latency加上0.1ns到0.2ns。

这样设置之后,工具在CTS前做时序分析时,就会知道ICG的时钟和下游寄存器的时钟之间存在一个正向的latency差,从而在优化使能路径时考虑到这个因素。

3.4 CTS后latency被真实值替代,怎么验证设置是否合理

CTS之后,工具会用真实的时钟树插入延迟替代你设的network latency估计值。这时候你需要做一件事:对比CTS前设的latency和CTS后真实的latency,看看差异有多大。

如果CTS后真实latency和你设的估计值差异在0.1ns以内,说明你的估计比较准,CTS前的优化方向基本正确。如果差异很大,比如你设了0.3ns,实际出来0.6ns,那CTS前的优化可能就偏了,CTS后违例会比较多。

验证方法很简单,在CTS后的STA报告里看clock latency的报告:

report_clock_timing -type latency -clock clk_core

对比ICG CP端和下游寄存器CK端的latency值,看看它们的差是否和你CTS前设的差一致。如果不一致,就要分析原因:是时钟树结构问题,还是ICG位置问题,还是约束本身设错了。

我自己的经验是,CTS前latency估计值和CTS后真实值的差异控制在20%以内比较理想。超过这个范围,就要回头检查约束或者时钟树综合策略。

4. 从CTS前到CTS后的完整修复链路:一个真实案例的排查过程

光讲理论不够,我用一个实际项目里的案例,把从CTS前约束设置到CTS后违例修复的完整链路走一遍。这个案例是一个中等规模的SoC模块,里面有大约60个ICG,时钟频率800MHz,工艺是较成熟的节点。

4.1 问题现象:CTS后hold违例集中爆发

CTS之前,模块的时序报告基本干净,setup和hold都有正裕量。CTS之后重新跑STA,发现:

  • 使能路径hold违例:87条
  • clock gating check setup违例:23条
  • 部分下游寄存器setup违例:15条

违例集中在几个ICG密集的区域,而且很多违例的slack不大,在-0.05ns到-0.15ns之间。这种“小违例但数量多”的情况,通常不是某个单点问题,而是约束或者时钟树策略的系统性问题。

4.2 第一步:检查set_clock_gating_check设置

我先看了CTS前的约束文件,发现set_clock_gating_check只写了setup,没写hold:

set_clock_gating_check -setup 0.1 [get_clocks clk_core]

这就是第一个问题。没有设hold裕量,工具在CTS前对clock gating的hold检查用的是默认规则,没有预留额外裕量。CTS后时钟树skew出来,hold就崩了。

修复:补上hold裕量,改成:

set_clock_gating_check -setup 0.1 -hold 0.05 [get_clocks clk_core]

4.3 第二步:检查latency设置

接着看latency约束,发现只设了一个全局的network latency,没有给ICG和下游寄存器单独设:

set_clock_latency -source 0.5 [get_clocks clk_core] set_clock_latency 0.3 [get_clocks clk_core]

这就是第二个问题。工具在CTS前认为所有寄存器和ICG的latency都是0.3ns,没有考虑到ICG和下游寄存器之间的latency差异。CTS后真实latency出来,ICG CP端是0.42ns,下游寄存器CK端是0.55ns,差了0.13ns,而CTS前工具是按0差异优化的,hold自然崩。

修复:给ICG CP端和下游寄存器分别设latency:

set_clock_latency 0.35 -clock [get_clocks clk_core] [get_pins icg_inst*/CP] set_clock_latency 0.45 -clock [get_clocks clk_core] [get_pins reg_downstream*/CK]

4.4 第三步:CTS后针对残余违例的局部修复

改完约束重新跑CTS和STA,违例数量大幅下降:hold违例从87条降到12条,clock gating check setup违例从23条降到5条。剩下的这些是局部问题,需要针对性修复。

对残余的hold违例,我用了两种方法:

第一种,在使能路径上插入小delay。注意,这里插delay要谨慎,因为使能路径的hold修复不能影响setup。我一般会在使能路径的中间节点插入一个很小的buffer或delay cell,delay值控制在0.05ns到0.1ns,刚好补上hold的缺口,又不会让setup变差。

第二种,调整ICG的位置。有几个ICG离时钟树根节点太近,导致它的CP端latency偏小,和使能寄存器的时钟skew大。把ICG往时钟树下游挪一点,让它的latency更接近使能寄存器,hold就改善了。这个方法需要和时钟树综合工程师配合,不能自己随便挪。

对残余的clock gating check setup违例,主要是检查使能信号的到达时间。有几个使能信号经过了较长的组合逻辑,到达ICG锁存器D端太晚。修复方法是在使能路径上做逻辑优化,减少组合逻辑级数,或者把使能信号打一拍再送进ICG。

4.5 第四步:验证修复效果并做OCV复查

修完之后,不能只看typical corner的时序,一定要做OCV复查。我在OCV模式下重新跑STA,发现又有几条hold违例冒出来,slack在-0.03ns左右。这是因为OCV下early和late路径的derate不同,原本typical下刚好满足的路径在OCV下就不够了。

对这几条,我用了set_timing_derate对clock gating路径单独调整:

set_timing_derate -clock_gating_check -early 0.97 -late 1.03

调整后OCV下的违例也清了。这里要说明的是,derate值不能随便设,要和STA工程师确认,确保符合项目的signoff要求。

5. 几个容易踩的坑和我的实操心得

上面讲的是完整流程,下面单独拎几个我踩过的坑,都是文档里不会写、但实际项目中很容易遇到的。

5.1 坑一:set_clock_gating_check设了但没生效

有一次我明明写了set_clock_gating_check,但工具报的违例还是按默认规则来的。查了半天发现,是因为这个约束写在了读入库之前。set_clock_gating_check依赖于库单元里的clock gating检查arc,如果库还没读入,这个约束就无法正确关联到具体的单元上。

正确顺序是:先读库,再读网表,再写set_clock_gating_check。这个顺序问题很隐蔽,因为工具不会报错,只是约束静默失效。

5.2 坑二:latency设了负值

set_clock_latency是可以设负值的,但负值在ICG场景下要非常小心。我见过有人为了让ICG的latency“看起来小一点”,设了个负值,结果工具在CTS前把使能路径优化得过于激进,CTS后真实latency出来,setup全崩。

latency的物理意义是延迟,负值意味着“时钟提前到达”,这在真实时钟树里几乎不可能出现。除非你有特殊的时钟树结构(比如时钟先从某个分支反向走),否则不要设负值。

5.3 坑三:ICG的使能信号跨时钟域

如果ICG的使能信号来自另一个时钟域,clock gating check的参考时钟就变得复杂了。工具默认会用ICG CP端的时钟作为参考,但使能信号的实际到达时间是由它自己的时钟域决定的。这种情况下,需要额外设置clock group或者false path,否则工具会报大量假违例。

我的做法是:对这种跨时钟域的ICG,先确认使能信号是否真的需要做clock gating check。如果使能信号已经做了同步处理,且同步后的信号在目标时钟域里是稳定的,可以考虑对clock gating check设false path,避免假违例干扰。

5.4 坑四:CTS后盲目插delay修hold

这是最常见的错误。CTS后看到hold违例,第一反应就是插delay。但ICG的使能路径插delay会影响clock gating check的setup,插多了setup就崩。而且使能路径往往扇出较大,插一个delay可能影响多个ICG。

我的经验是:CTS后修ICG hold,先看latency约束是否合理,再看时钟树skew是否过大,最后才考虑插delay。插delay时优先在扇出小的节点插,并且要同时检查setup和clock gating check。

5.5 一个实用技巧:用report_clock_gating_check快速定位问题

很多工具都有report_clock_gating_check命令,可以单独报告clock gating检查的结果。我习惯在CTS前后都跑一次,对比违例数量和分布。如果CTS后违例数量突然增加很多,基本可以确定是latency约束或者时钟树策略的问题,而不是单点路径问题。

report_clock_gating_check -verbose > cts_post_icg_check.rpt

这个报告里会列出每个ICG的setup和hold检查结果,以及对应的slack。结合latency报告一起看,能很快定位到是哪个环节出了问题。

6. 关于ICG时序修复,我最后想说的几点

ICG的时序问题,说到底是一个“约束先行”的问题。CTS前把set_clock_gating_check和latency设对,CTS后80%的违例都不会出现。剩下的20%,才是真正需要局部修复的。很多人把顺序搞反了,CTS前约束随便写,CTS后拼命修,结果越修越乱。

另外,ICG的时序修复一定要和时钟树综合工程师紧密配合。latency设置、ICG位置调整、时钟树结构优化,这些都不是后端约束工程师一个人能搞定的。我在项目里通常会拉着CTS工程师一起看ICG的latency报告和违例分布,很多时候他们从时钟树结构角度给出的建议,比单纯在约束上调参数有效得多。

还有一点,不同工艺节点、不同库单元,ICG的时序特性差异很大。上面讲的参数值都是参考,实际项目中一定要根据库单元的实际时序arc和项目的时钟树特性来调整。我一般会在项目初期做一个小规模的ICG时序实验,把set_clock_gating_check和latency的几组典型值跑一遍,看看哪组值在CTS后违例最少,然后再推广到整个设计。这个前期投入看起来费时间,但比CTS后大规模返工要划算得多。

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

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

立即咨询