时钟生成的艺术:create_generated_clock 深度解析
时钟,是数字芯片的「心跳」。
做 STA 这么多年,我见过太多因为时钟约束没写对而导致的时序问题。说实话,create_generated_clock 这个命令,看起来简单,但坑特别多。今天我就把压箱底的经验拿出来,跟你聊聊分频、倍频、相移这些事儿。
一、什么是生成时钟?
简单说,生成时钟就是从主时钟(master clock)派生出来的时钟。比如你有一个 100MHz 的输入时钟,经过 PLL 或者分频器后,得到 50MHz、200MHz 或者相位偏移的时钟,这些就是生成时钟。
为什么要单独定义它们?因为工具需要知道这些时钟之间的时序关系。你不告诉它,它就没法做正确的分析。
核心命令格式:
create_generated_clock -name <clock_name> \
-source <master_pin> \
-divide_by <N> \
-multiply_by <M> \
-phase <degrees> \
[get_pins <output_pin>]
二、分频时钟:最常用的场景
分频,就是把时钟频率降下来。比如你有个 100MHz 的主时钟,想要个 50MHz 的时钟给低速模块用。
我在项目中遇到过这样的情况:设计师直接用了一个二分频器,但忘了在 SDC 里定义生成时钟。结果 STA 工具把分频后的时钟当成了独立时钟,跟主时钟之间没有任何约束,时序分析完全乱套了。
正确的写法:
# 二分频
create_generated_clock -name clk_div2 \
-source [get_ports clk_in] \
-divide_by 2 \
[get_pins divider/Q]
# 四分频
create_generated_clock -name clk_div4 \
-source [get_ports clk_in] \
-divide_by 4 \
[get_pins div4_reg/Q]
我的习惯:分频时钟的 source 一定要指定到主时钟的源点,而不是分频器的输入 pin。这样工具才能正确计算时钟沿的对齐关系。
三、倍频时钟:PLL 的常见用法
倍频通常通过 PLL 实现。比如输入 50MHz,PLL 输出 200MHz,那就是 4 倍频。
说实话,倍频时钟的约束比分频要复杂一些。因为 PLL 内部有锁定时间、抖动等参数需要考虑。
# 4倍频
create_generated_clock -name clk_4x \
-source [get_ports clk_in] \
-multiply_by 4 \
[get_pins pll/clk_out]
# 同时分频和倍频(比如 3/2 倍频)
create_generated_clock -name clk_1_5x \
-source [get_ports clk_in] \
-multiply_by 3 \
-divide_by 2 \
[get_pins pll/clk_out]
注意:倍频时钟的 source 要选 PLL 的输入 pin,而不是输出 pin。我曾经见过有人把 source 设成了 PLL 输出,结果工具报了一堆莫名其妙的时序违例。
四、时钟相移:调整时序窗口
相移,说白了就是把时钟沿往前或往后挪一挪。这在 DDR 接口、源同步接口中特别常见。
为什么要做相移?你想想看,数据在时钟沿附近变化最快,采样点放在数据稳定的区域才安全。相移就是帮你找到那个「安全区」。
# 90度相移
create_generated_clock -name clk_90 \
-source [get_ports clk_in] \
-multiply_by 1 \
-phase 90 \
[get_pins pll/clk_90]
# 180度相移(常用于 DDR)
create_generated_clock -name clk_180 \
-source [get_ports clk_in] \
-multiply_by 1 \
-phase 180 \
[get_pins pll/clk_180]
避坑指南:我曾经在某个项目中,把相移设成了 270 度,但工具分析时发现 setup 和 hold 都过不了。后来才发现,相移 270 度等价于 -90 度,但工具对负相移的处理方式不一样。所以,我建议尽量用 0~360 度范围内的正相移。
五、时钟关系:同步还是异步?
生成时钟和主时钟之间,默认是同步关系。但实际情况往往更复杂。
比如,两个生成时钟来自不同的 PLL,或者经过了不同的分频路径,它们的相位关系可能是不确定的。这时候就需要手动指定时钟关系。
| 时钟关系 | 命令 | 适用场景 |
|---|---|---|
| 同步 | 默认(无需额外命令) | 同源时钟,相位确定 |
| 异步 | set_clock_groups -asynchronous | 不同源时钟,或相位不确定 |
| 逻辑互斥 | set_clock_groups -logically_exclusive | MUX 选择的时钟 |
| 物理互斥 | set_clock_groups -physically_exclusive | 测试模式与功能模式 |
# 异步时钟组
set_clock_groups -asynchronous \
-group [get_clocks clk_a] \
-group [get_clocks clk_b]
# 逻辑互斥(MUX 输出)
set_clock_groups -logically_exclusive \
-group [get_clocks clk_pll1] \
-group [get_clocks clk_pll2]
我的经验:对于不确定的时钟关系,宁可设为异步,也不要默认同步。因为同步分析会假设最严格的时序关系,导致过度约束。异步分析反而更宽松,只要做好同步器设计就行。
六、知识体系总览
下面这张图,是我自己总结的生成时钟知识框架。每次做新项目,我都会对照着检查一遍,确保没有遗漏。
七、实战中的几个关键点
嗯,最后再唠叨几句。这些是我踩过的坑,你遇到了可以少走弯路。
- source 的选择:一定要选主时钟的源点,而不是中间节点。否则工具会报「source not found」或者时序分析错误。
- 时钟沿对齐:分频时钟的上升沿默认与主时钟对齐。如果你需要下降沿对齐,记得加
-invert选项。 - 组合逻辑生成时钟:尽量避免用组合逻辑产生时钟。如果实在要用,记得加
-combinational选项,并且要特别小心毛刺问题。 - 多个生成时钟:同一个输出 pin 上可以有多个生成时钟(比如测试模式和功能模式),用
-add选项来叠加。
重要提醒:生成时钟的 -name 不要跟主时钟重名。工具不会报错,但分析结果会乱套。我习惯在生成时钟名字上加后缀,比如 clk_in_div2、clk_in_4x,一目了然。
好了,关于生成时钟的核心内容就这些。记住一句话:时钟约束写得好,时序分析没烦恼。下次遇到复杂的时钟树,别慌,按照分频、倍频、相移、时钟关系这个思路一步步来,准没错。