4、生成时钟:create_generated_clock,分频与倍频时钟的约束方法
好,咱们接着聊时钟约束。前面讲了主时钟怎么创建,但芯片里不可能只有一个时钟源。PLL锁相环、分频器、门控时钟……这些衍生出来的时钟,才是真正驱动你逻辑的“主力军”。
说白了,create_generated_clock 就是用来定义这些“子时钟”的。它告诉工具:这个时钟是从哪个主时钟变过来的,变的方式是分频、倍频,还是相移。
4.1 为什么不能直接用 create_clock?
我见过不少新手,直接在分频输出端口上又写一个 create_clock。嗯,这样工具也能跑,但时序分析会出大问题。
为什么?因为 create_clock 定义的是一个“独立”的时钟源。工具会认为这个时钟和主时钟之间没有任何关系。但实际电路里,分频时钟和主时钟是同步的,它们的边沿有确定的相位关系。
如果你用了两个 create_clock,工具在做跨时钟域分析时,会保守地认为这两个时钟完全异步,从而插入大量不必要的同步器,或者报出虚假的时序违例。
记住:只要时钟是同一个源头衍生出来的,就必须用 create_generated_clock。
4.2 基本语法与参数
先看个最简单的例子。假设你有一个主时钟 clk_in 是 100MHz,经过一个二分频寄存器后得到 clk_div2。
create_clock -name clk_in -period 10 [get_ports clk_in]
create_generated_clock -name clk_div2 \
-source [get_ports clk_in] \
-divide_by 2 \
[get_pins reg_div/Q]
这里几个关键参数:
- -source:指定源时钟的端口或引脚。注意,这里要填主时钟定义的那个点。
- -divide_by 或 -multiply_by:分频或倍频系数。
- -edges:更灵活的方式,直接指定生成时钟的边沿相对于源时钟的边沿位置。
- -edge_shift:在
-edges基础上做偏移。 - -invert:反相,相当于 50% 占空比下的二分频取反。
我个人习惯:能用 -divide_by 和 -multiply_by 解决的,尽量不用 -edges。后者虽然灵活,但写起来容易出错,尤其是边沿编号搞混的时候。
4.3 分频时钟的约束
分频是最常见的场景。整数分频、偶数分频、奇数分频,写法上略有不同。
4.3.1 偶数分频
比如四分频,占空比 50%:
create_generated_clock -name clk_div4 \
-source [get_ports clk_in] \
-divide_by 4 \
[get_pins reg_div4/Q]
工具会自动计算边沿位置。源时钟周期 10ns,四分频后周期 40ns,上升沿在 0ns、40ns、80ns……
4.3.2 奇数分频
奇数分频稍微麻烦点,因为占空比往往不是 50%。比如三分频,占空比 1/3 或 2/3。
这时候用 -edges 更直观:
create_generated_clock -name clk_div3 \
-source [get_ports clk_in] \
-edges {1 3 4} \
[get_pins reg_div3/Q]
这里 {1 3 4} 是什么意思?
- 第一个边沿:源时钟的第 1 个边沿(上升沿)
- 第二个边沿:源时钟的第 3 个边沿(下降沿)
- 第三个边沿:源时钟的第 4 个边沿(上升沿)
这样生成的就是一个周期为 3 个源时钟周期、高电平占 1 个周期、低电平占 2 个周期的波形。
避坑指南:我曾经在写奇数分频时,把边沿编号数错了。源时钟的边沿编号是从 1 开始,上升沿为奇数,下降沿为偶数。第 1 个边沿是上升沿,第 2 个是下降沿,依此类推。这个顺序千万别搞反。
4.4 倍频时钟的约束
倍频通常由 PLL 产生。比如 PLL 输出 500MHz,源时钟 100MHz,5 倍频。
create_generated_clock -name clk_pll \
-source [get_ports clk_in] \
-multiply_by 5 \
[get_pins pll_instance/clk_out]
这里有个细节:PLL 的输出引脚通常不是寄存器输出,而是模拟模块的输出。工具需要知道这个时钟的源头是哪个时钟。
如果 PLL 有多个输出,比如 500MHz 和 200MHz,那就分别定义:
create_generated_clock -name clk_500m \
-source [get_ports clk_in] \
-multiply_by 5 \
[get_pins pll/clk_out1]
create_generated_clock -name clk_200m \
-source [get_ports clk_in] \
-multiply_by 2 \
[get_pins pll/clk_out2]
4.5 相移与占空比调整
有时候我们需要一个相移 90 度的时钟。可以用 -edge_shift:
create_generated_clock -name clk_shift90 \
-source [get_ports clk_in] \
-edges {1 2} \
-edge_shift {2.5 2.5} \
[get_pins reg_shift/Q]
这里 -edge_shift {2.5 2.5} 表示把两个边沿都往后移 2.5ns。如果源时钟周期是 10ns,那这个生成时钟的上升沿就在 2.5ns 处,下降沿在 7.5ns 处。
嗯,这个功能在 DDR 接口中很常用。数据在时钟的中间变化,需要把采样时钟偏移半个周期。
4.6 组合逻辑生成的时钟
还有一种情况:时钟不是从寄存器出来的,而是从组合逻辑出来的。比如一个与门、或门。
这时候要加 -combinational 选项:
create_generated_clock -name gated_clk \
-source [get_ports clk_in] \
-combinational \
[get_pins and_gate/Y]
工具会认为这个生成时钟的波形和源时钟完全一样,只是经过了组合逻辑延迟。
注意:组合逻辑生成的时钟,在时序分析中要特别小心。组合逻辑的延迟会引入毛刺,而且 PVT 变化下延迟不稳定。我建议尽量用寄存器或 PLL 产生时钟,少用组合逻辑。
4.7 多个生成时钟的级联
有时候生成时钟的源不是主时钟,而是另一个生成时钟。比如先二分频,再三分频。
工具是支持这种级联的:
create_generated_clock -name clk_div2 \
-source [get_ports clk_in] \
-divide_by 2 \
[get_pins reg_div2/Q]
create_generated_clock -name clk_div6 \
-source [get_pins reg_div2/Q] \
-divide_by 3 \
[get_pins reg_div6/Q]
工具会自动推导出 clk_div6 相对于主时钟 clk_in 的关系。你不需要手动计算 2×3=6。
4.8 验证生成时钟
约束写完了,怎么知道对不对?
在工具里用 report_clock 命令查看:
report_clock -generated
你会看到每个生成时钟的源时钟、分频/倍频系数、边沿位置、占空比等信息。
我习惯再跑一个 check_timing,看看有没有未约束的生成时钟,或者源时钟找不到的情况。
总结一下我的经验:
- 能用
-divide_by/-multiply_by的,优先用 - 奇数分频或特殊占空比,用
-edges - PLL 输出一定要指定正确的源时钟引脚
- 组合逻辑生成时钟,能避免就避免
- 写完约束后,用
report_clock和check_timing验证
生成时钟的约束,说白了就是告诉工具:这个时钟是从哪来的,怎么变的。你描述得越准确,工具分析得就越精准。下次咱们聊聊如何约束输入输出延迟,那是连接芯片内部和外部的桥梁。