第四章:时钟约束详解
时钟,是数字芯片的「心跳」。
做综合和时序分析时,时钟约束要是写错了,后面所有工作都是白费。我见过太多工程师,花了两周跑PR,结果因为create_clock少写了一个参数,整个时序全乱套。嗯,今天咱们就把时钟约束的四个核心命令讲透。
4.1 创建时钟:create_clock
create_clock是时序约束的起点。说白了,就是告诉工具:「嘿,这个端口或节点上有个时钟,频率是多少,占空比怎样。」
基本语法很简单:
create_clock -name CLK -period 10 [get_ports clk_in]
这条命令创建了一个名为CLK的时钟,周期10ns(也就是100MHz),加在clk_in端口上。
但实际项目中,很少有这么干净的时钟。我遇到过最头疼的情况——时钟分频。比如一个PLL输出两路时钟,一路200MHz,一路100MHz,而且它们之间有固定的相位关系。
这时候需要用到-waveform参数:
create_clock -name CLK_200M -period 5 [get_pins pll/clk_out1]
create_clock -name CLK_100M -period 10 [get_pins pll/clk_out2] \
-waveform {0 5}
注意-waveform的写法:第一个数字是上升沿时间,第二个是下降沿时间。默认是50%占空比,也就是{0 5}表示上升沿在0ns,下降沿在5ns。
我曾经在某个28nm项目里,把-waveform写成了{0 2.5},结果占空比变成了25%。工具报了一堆hold violation,我排查了整整两天才发现是这里写错了。记住:waveform的单位是ns,不是周期百分比。
4.2 时钟不确定性:set_clock_uncertainty
时钟不确定性,就是给时序分析留点「余量」。为什么需要这个?因为实际芯片里,时钟边沿不可能每次都精确落在同一个时间点。
造成不确定性的原因主要有三个:
- 时钟抖动(Jitter):PLL或时钟缓冲器产生的随机相位变化
- 时钟偏斜(Skew):时钟到达不同寄存器的路径长度差异
- 余量(Margin):给工艺、电压、温度变化留的裕度
命令格式:
set_clock_uncertainty -setup 0.2 [get_clocks CLK]
set_clock_uncertainty -hold 0.05 [get_clocks CLK]
setup和hold的uncertainty通常不一样。setup留的余量更大,因为setup violation更难修。我个人习惯:setup给0.2~0.3ns,hold给0.05~0.1ns。
如果你不确定该设多少,可以查一下foundry的PDK文档。一般成熟工艺(如28nm)会给出推荐值。我见过有人把uncertainty设成0.5ns,结果时序收敛不了,最后发现是余量留太多了。
4.3 时钟延迟:set_clock_latency
时钟延迟,指的是时钟信号从源端(比如PLL输出)到寄存器时钟端的传播时间。这个延迟包括两部分:
- source latency:时钟源到芯片边界的延迟
- network latency:芯片边界到寄存器时钟端的延迟
命令示例:
set_clock_latency -source 1.5 [get_clocks CLK]
set_clock_latency 0.8 [get_clocks CLK]
这里-source 1.5表示source latency是1.5ns,后面的0.8是network latency。总延迟就是2.3ns。
你想想看,如果这个延迟设错了,工具计算arrival time就会出问题。我记得有一次做后端分析,发现setup slack总是偏大,后来发现是clock latency设小了0.5ns。改回来之后,slack就正常了。
在综合阶段,clock latency通常设得比较保守(偏大),因为综合工具不知道最终的时钟树长什么样。等做完时钟树综合(CTS)之后,再用实际的latency值去更新约束。
4.4 时钟分组:set_clock_groups
时钟分组,是处理异步时钟域的关键。当两个时钟之间没有确定的相位关系时,我们需要告诉工具:「这两个时钟是异步的,不要分析它们之间的时序路径。」
基本语法:
set_clock_groups -asynchronous \
-group [get_clocks CLK_A] \
-group [get_clocks CLK_B]
这条命令告诉工具:CLK_A和CLK_B是异步的,它们之间的路径不需要做时序分析。
除了-asynchronous,还有另外两种分组方式:
| 分组类型 | 含义 | 使用场景 |
|---|---|---|
| -asynchronous | 异步时钟,不分析跨域路径 | 两个独立时钟源产生的时钟 |
| -logically_exclusive | 逻辑互斥,同一时刻只有一个时钟有效 | MUX选择的两路时钟 |
| -physically_exclusive | 物理互斥,不可能同时存在 | 测试时钟和功能时钟 |
我建议你养成一个习惯:只要设计中存在多个时钟域,就主动加上set_clock_groups。否则工具会默认所有时钟都是同步的,然后去分析那些根本不需要分析的路径,白白浪费时间。
我曾经在一个项目中,忘了给两个异步时钟加set_clock_groups。结果工具报了上千条false path的violation,我花了整整一天去排查。后来发现,只要加一行约束就能解决。所以,时钟分组一定要在约束文件的开头就写好。
知识体系总览
下面这张图,帮你把四个时钟约束命令的关系理清楚:
这四个命令,是时钟约束的「四根柱子」。create_clock是基础,set_clock_uncertainty和set_clock_latency是精细调整,set_clock_groups是处理复杂场景的利器。把它们用好,你的时序约束就成功了一大半。
每次写约束文件,我都会先画一个时钟关系图——哪些时钟是同步的,哪些是异步的,哪些是互斥的。画清楚了再写代码,基本不会出错。你也不妨试试这个方法。
好了,时钟约束就讲到这里。记住:时钟是芯片的命脉,约束写对了,后面的路就好走了。