set_clock_latency 和 set_source_latency 差别:时钟网络延迟与源延迟到底怎么分
这是 SDC 时序约束簇里最容易被混用的一对命令:两条命令都写"时钟延迟",数值还经常写得差不多,很多新手干脆只写其中一条。其实它们描述的是时钟路径上以芯片引脚为界的两段完全不同的延迟,写错位置会让静态时序分析(Static Timing Analysis,STA)在最坏情况下算出过于乐观或过于悲观的时钟偏斜(skew)。本篇一次讲清。
本文要回答的问题
- set_clock_latency 和 set_source_latency 各自描述时钟路径上的哪一段?
- 为什么 SDC 要以芯片引脚为界把时钟延迟拆成两条命令?
- 理想时钟树(pre-CTS)和真实时钟树(post-CTS)阶段,这两条命令应该怎么改?
- 写错或漏写会造成什么后果?有哪些常见误区?
一、简短结论
一句话:set_source_latency 管"芯片引脚之外"的片外延迟,set_clock_latency 管"引脚到触发器时钟端"的片内网络延迟。时钟从晶振(或 PLL)出发,先在 PCB 上走到芯片引脚,这一段归 source latency;进芯片后经过时钟缓冲链、时钟树走到触发器的 CK 端,这一段归 clock latency(也叫 network latency)。STA 计算到达触发器时钟端的总延迟时,把两者加起来:总时钟延迟 = source latency + clock latency。
二、把时钟路径按"引脚"切成两段
先建立一个直觉模型。一个 SoC 的时钟不是凭空出现在触发器上的,它要走完整的一条链:
读这张图抓住一个分界点:芯片引脚。引脚左边的一切(时钟源抖动外的传播时间、板级走线、时钟分配芯片的延迟)对芯片内部的 STA 工具来说是不可见的,只能用 set_source_latency 以约束的形式"告诉"工具;引脚右边的一切,在时钟树综合(Clock Tree Synthesis,CTS)之前也是黑盒,要用 set_clock_latency 预估,CTS 完成之后工具从真实时钟树网表里逐段算出来,命令就该撤掉。
为什么 SDC 要设计这个分界?因为片内和片外的建模方式根本不同。片内时钟树在 post-CTS 阶段有真实网表,延迟可以直接计算,约束只是占位符;片外路径永远不在芯片数据库里,无论流程走到哪一步都必须靠约束声明。两条命令各管一段,才能保证"理想时钟建占位、真实时钟走计算"的切换不丢信息。
三、两条命令的分工对比
这张对比卡里最值得注意的是第二行"性质":clock latency 是一个临时占位符,等时钟树真实存在之后就要让位给传播延迟(propagated latency);source latency 是永久声明,从综合到签核(signoff)一直有效。这也解释了为什么很多约束模板里 set_clock_latency 带 -source 选项——SDC 标准里 source latency 就是 clock latency 的一个"特殊档位",用 -source 开关区分,而不是两个完全独立的命令族。
写法上还有几个细节值得记住:
set_clock_latency 1.2 [get_clocks SYS_CLK]:不带-source,表示片内网络延迟。set_clock_latency -source 0.8 [get_clocks SYS_CLK]:带-source,表示片外源延迟。- 两者都可以加
-rise/-fall/-early/-late细分沿口和最快最慢路径,用于片上偏差(On-Chip Variation,OCV)建模。 - 指定
-source的延迟永远参与计算;不带的那个在set_propagated_clock [all_clocks]之后被忽略。
四、pre-CTS 与 post-CTS:用法随流程阶段切换
两条命令真正的坑不在定义,而在流程切换。综合和布局前期时钟树还没长出来,STA 用的是理想时钟模型;CTS 之后时钟树是真实缓冲链,延迟要改成"算"而不是"猜"。切换时的标准动作是:
上面的流程图用橙色标出了切换动作这个关键节点,蓝色(默认色)是两个阶段各自该写的约束。要点只有一个:切换只影响片内段,片外段纹丝不动。source latency 不会因为时钟树长出来就变得可计算,它描述的是 PCB 上那几厘米的走线和时钟扇出芯片的群延迟,芯片数据库里永远没有这些对象,自然永远靠人告诉工具。
漏掉这个切换的代价很具体:如果 post-CTS 阶段忘了加 set_propagated_clock,工具会继续用你 pre-CTS 拍脑袋写的 clock latency 估值,整条时钟树的 OCV、偏斜全都失真;反过来,如果 pre-CTS 阶段就打开 propagated clock,时钟树还不存在,工具算出的时钟延迟是 0,时序报告会乐观得离谱。这类问题在真实项目里并不少见,PrimeTime 报 IO latency 时钟未定义就是这类"约束声明和流程阶段对不上"的典型报错。
五、一个数值例子
假设一颗 MCU 挂在 24 MHz 晶振后面,板级时钟路径量出来约 0.8 ns,预估片内时钟树延迟约 1.5 ns。pre-CTS 阶段的约束写成:
create_clock -name SYS_CLK -period 41.67 [get_ports clk_in]
# 片外:晶振 → 芯片引脚,板级测量值 0.8 ns
set_clock_latency -source 0.8 [get_clocks SYS_CLK]
# 片内:引脚 → 触发器 CK 端,CTS 前的预估值
set_clock_latency 1.5 [get_clocks SYS_CLK]
# 时钟不确定性(抖动 + 偏斜裕量),另算
set_clock_uncertainty 0.2 [get_clocks SYS_CLK]此时 STA 认为时钟到达触发器的总延迟是 0.8 + 1.5 = 2.3 ns。CTS 完成之后,把片内那条换成传播模式:
set_propagated_clock [all_clocks]
# set_clock_latency 1.5 那一条被忽略,延迟由真实时钟树算出
# set_clock_latency -source 0.8 继续生效注意第三行 set_clock_uncertainty 是另一回事:它建模的是抖动和裕量,不参与"延迟从哪来到哪去"的划分,两条 latency 命令都不该拿它来凑数。
六、常见误区
误区一:两条命令二选一,写一条就够。 错。两段路径物理上都存在,只写一条等于声明另一段延迟为 0。只写 clock latency 的约束,等于告诉工具"时钟从引脚瞬间出现",片外 0.8 ns 的公共路径被人为抹掉,对保持时间(hold)检查尤其危险。
误区二:-source 延迟也会被 propagated clock 取代。 不会。-source 声明的延迟在任何阶段都参与计算,这是它和网络延迟最本质的区别。 CTS 之后如果发现 source latency 变了,唯一原因是板级设计改了,而不是流程阶段变了。
误区三:用 source latency 去"调"时序。 有人把 source latency 当免费的有用偏斜(useful skew)旋钮,给关键路径的时钟加负延迟来抢时间。这在多时钟域设计里会制造跨域的虚假偏斜,而且片外延迟是物理事实,改约束不改板子等于自欺。片内时序不够用,该走时钟树调整或有用的偏斜,别动源延迟。
误区四:把 clock latency 和 clock uncertainty 混为一谈。 latency 是"平均走多久",uncertainty 是"上下浮动多少"。前者影响时钟到达时刻的标称值,后者影响建立/保持检查的裕量。两条命令解决的是不同维度的问题,缺一个报告就缺一角。关于 uncertainty 本身怎么取值,另有专文展开,不在本篇范围。
误区五:片上生成的时钟(PLL 输出)也照抄这套。 片上 PLL 的输出是生成时钟(generated clock),它的源延迟要从 PLL 的锁定特性里单独考虑,不能简单把"晶振到引脚"那套搬过来。多数流程里对 generated clock 不额外声明 source latency,而是靠 create_generated_clock 的源引脚定义自动继承上游路径。
这一篇在知识体系里的位置
本篇属于 SDC 时钟约束簇的"延迟划分"问答篇,向上承接 SDC 时序约束总览里 create_clock / set_clock_latency / set_clock_uncertainty 的整体地图,向下与"uncertainty 怎么取值"的专篇互补。时钟树长出来之后延迟怎么从网表算,参见 时钟树综合 CTS;setup/hold 检查怎么消费这些延迟数值,参见 建立时间、保持时间与亚稳态;多周期与伪路径这类"路径级"约束见 multicycle 与 false path。
一句话总结
以芯片引脚为界:引脚之外的片外时钟路径用 set_clock_latency -source(源延迟,全流程保留、工具永远算不出来),引脚之内的片内时钟树用 set_clock_latency(网络延迟,pre-CTS 阶段占位、set_propagated_clock 之后由真实网表计算),两段相加才是触发器看到的完整时钟延迟。