set_clock_uncertainty 含义:它到底把时钟的哪部分不确定性算进了时序分析
芯片设计进阶系列里,SDC 约束怎么写 和 STA 报告怎么读 是两个高频问题;夹在中间的
set_clock_uncertainty往往是「照抄模板值、其实没懂」的那一条。这一篇专门把它的含义拆开讲。
本文要回答的问题
set_clock_uncertainty这条约束在时序分析里到底代表什么?- 它由哪几类不确定性构成,各自从哪来?
- 分析 setup 和 hold 时,它分别加在哪一侧?
- 综合阶段和 CTS(时钟树综合)之后,取值为什么不一样?
- 和
set_clock_latency、derate 是什么关系,会不会重复扣?
简短结论
一句话:set_clock_uncertainty 告诉静态时序分析(STA)工具「时钟沿到达的实际时刻,会在理想时刻附近一个窗口内浮动,分析时按最悲观的那一侧算」。它把时钟抖动(jitter)、时钟偏斜(skew,主要是尚未建模的偏斜)和工程裕量(margin)三类不确定性打包成一个数,从时序余量(slack)里扣掉。它不是时钟「本身的延迟」——延迟归 set_clock_latency 管(见 时钟延迟 source 与 network),uncertainty 管的是「延迟算不准的那部分」。
理由一:理想时钟是假的,三个来源让沿位置浮动
SDC 里 create_clock 声明的是一个理想时钟:周期精确、占空比精确、所有触发器同一拍到达。真实芯片里这三条全不成立,浮动来源可以归成三类:
- 抖动(jitter):时钟源(PLL/晶振)自身沿位置的随机与确定性偏移,周期到周期都在变。详细分类见 时钟抖动与时钟不确定性。
- 偏斜(skew):时钟树各分支长度、负载、OCV(片上工艺偏差)不同,两个触发器收到的沿不同步。CTS 之前这个值完全未知,只能靠经验估;CTS 之后工具已建出真实时钟树,skew 可以直接算出来,uncertainty 里只需保留「建模不到位的残余」。
- 裕量(margin):工艺角覆盖不到的系统性风险、串扰、电压降等,项目按经验再压一层保险。
三者的分工可以用一张对比卡看清:
读这张卡抓住一点:三列的「来源」差别很大,但最后一行收敛到同一句话——它们都被打包进一个 uncertainty 数值,统一从余量里扣。jitter 与 skew 的物理区别如果还想深挖,jitter 与 skew 到底差在哪 有专门辨析。
理由二:setup 扣在 capture 侧,hold 两侧同时咬
很多人记住了「uncertainty 会让 slack 变小」,但说不清它出现在公式的哪个位置。用建立时间(setup)检查来推一遍:
- 理想情况:capture 沿在 T(一个周期)之后到来,数据只要在 T 之前稳定即可。
- 计入 uncertainty:悲观假设是 launch 沿晚了半个窗口、capture 沿早了半个窗口(jitter 在两侧各咬一半),合成效果相当于 capture 沿提前了 uncertainty 这么长,所以 setup 的可用窗口 = 周期 − uncertainty。
保持时间(hold)检查更狠:launch 与 capture 看的是同一个沿附近的相对关系,两侧同时悲观化,等于在数据到达时间上加了 uncertainty 这么长的要求。这就是为什么 hold 违例在不确定性设大之后常常成片出现。
读这张图:灰色是整个周期窗口,蓝、橙两个检查各自从窗口里咬走 uncertainty(红块)对应的一段——setup 是单边扣、hold 是双边扣,所以同样的 U 值对 hold 更致命。图中 U 就是约束里给的那个数值。
理由三:取值随设计阶段变化,CTS 是分水岭
uncertainty 不是一条写死的约束,它应该跟着设计阶段走:
| 阶段 | 典型构成 | 量级参考(1 GHz 量级设计) | 依据 |
|---|---|---|---|
| 综合 / CTS 前 | jitter + 预估 skew + margin | 几十到上百 ps | 时钟树还没建,skew 全靠估 |
| CTS 后 / 布线前 | jitter + 残余 skew + margin | 明显收窄 | 真实树已建模,预估 skew 部分要拿掉 |
| 布线后 sign-off | jitter + margin | 最小 | OCV/串扰由 derate 与 SI 分析覆盖 |
最常见的翻车点就是 CTS 后不降 uncertainty:真实 skew 已经被工具算进报告了,约束里再扣一份预估 skew 就是双重惩罚,白白吃掉几十 ps 余量,把本来收敛的设计做出一堆假违例。时钟树怎么建、建完之后还剩什么不确定性,可以对照 时钟树综合 与 理想时钟 vs 传播时钟 两篇看。顺带一提,jitter、derate、margin 三者在项目里如何分账(哪些进 uncertainty、哪些进 derate),抖动与 derate 的裕量政策 给了完整的口径,避免同一风险被扣两次。
理由四:和 latency、derate 的分工,别重复扣
时钟相关三条约束各管一段,边界划清了才不会把同一份悲观扣两次:
set_clock_latency:管「沿平均什么时候到」,是确定性的延迟建模(source latency 对应片外、network latency 对应片内时钟树)。set_clock_uncertainty:管「到了之后沿位置有多不确定」,是围绕均值的浮动窗口,只装 jitter、未建模 skew、margin。set_timing_derate:管「路径延迟本身算乐观了怎么办」,按百分比放大/缩小数据路径和时钟路径的延迟,承接 OCV(片上偏差)这类随路径长度累积的效应。
一个实用的判断法:随绝对位置波动的归 uncertainty,随路径长度比例累积的归 derate,均值本身归 latency。jitter 不随路径变长而变大,所以走 uncertainty;OCV 是逐单元、逐段互连累积的偏差,走 derate 更准。如果把 OCV 也拍一个 ps 数塞进 uncertainty,长路径扣少了、短路径扣多了,两头都不对。项目落地时的具体分账口径,同样参考 抖动与 derate 的裕量政策。
例子:一段典型的两阶段写法
实际项目里常见的写法是对 setup 和 hold 分别给值,并在 CTS 前后换约束:
# CTS 前(综合 / 布局阶段)
create_clock -name clk -period 1.0 [get_ports clk]
set_clock_uncertainty -setup 0.080 [get_clocks clk]
set_clock_uncertainty -hold 0.030 [get_clocks clk]
# CTS 后(时钟树已建好,换一组更小的值)
set_clock_uncertainty -setup 0.040 [get_clocks clk]
set_clock_uncertainty -hold 0.020 [get_clocks clk]
set_propagated_clock [get_clocks clk]要点有三个:-setup 的值几乎总是大于 -hold(因为 setup 把 jitter 的单边悲观化算得重);CTS 后数值下调,把已建模的 skew 让位给真实树;set_propagated_clock 切换到传播时钟模式后,latency 由工具沿真实树计算,uncertainty 只管「算不准」的残差。接口时序里给虚拟时钟配 uncertainty 的套路相同,见 接口时序与虚拟时钟。
常见误区
把 uncertainty 相关的常见错误收成一张导图,方便自查:

读这张导图:五个分支里前两个是「数值错」(重复扣或忘降),中间一个是「结构错」(setup/hold 不分),最后两个是「职责错」——把本该由 derate、信号完整性分析承担的悲观项也塞进 uncertainty,余量账就再也算不清了。
这一篇在知识体系里的位置
本篇是芯片设计进阶线 SDC 时序约束簇里的一篇问答,上承 SDC 约束总览 与 时序分析导论,下接 STA 报告解读(报告里的 clock uncertainty 列就来自这条约束)与 时钟抖动详解。多周期路径、伪路径等其他「让分析偏离默认行为」的约束见 multicycle 与 false path。
一句话总结
set_clock_uncertainty 的含义是「把 jitter、未建模 skew 和工程 margin 打包成时钟沿位置的悲观浮动窗口」:setup 从 capture 侧单边扣、hold 两侧同时扣,取值应随设计阶段收窄——CTS 建出真实时钟树后必须下调,否则就是把已经算过一遍的 skew 再扣第二次。