4. 生成时钟约束:create_generated_clock,分频、倍频、相移时钟的约束方法

各位同学,咱们今天聊聊 create_generated_clock。这个命令,说白了就是给芯片内部的衍生时钟「上户口」。

你想想看,一个 PLL 出来,可能分频出 100MHz,倍频出 500MHz,再移个相。这些时钟如果不告诉工具它们跟源时钟的关系,工具就会傻眼——它不知道这些时钟是同步的还是异步的,时序分析也就无从谈起。

我个人习惯,拿到 RTL 代码后,第一件事就是找所有时钟生成逻辑。不管是 divider 还是 gated clock,一个都不能漏。

4.1 分频时钟的约束

分频时钟是最常见的。比如你用寄存器做二分频:

// Verilog 二分频
reg clk_div2;
always @(posedge clk_src or negedge rst_n) begin
    if (!rst_n)
        clk_div2 <= 1'b0;
    else
        clk_div2 <= ~clk_div2;
end

对应的约束怎么写?

create_clock -name clk_src -period 10 [get_ports clk_src]
create_generated_clock -name clk_div2 -source [get_ports clk_src] \
    -divide_by 2 [get_pins U_DIV/clk_div2_reg/Q]

这里有个关键点:-source 指定的是源时钟的 时钟根节点,不是时钟名字。我见过不少新手写成 -source [get_clocks clk_src],结果工具报 warning 说找不到源。

核心原则:-source 指向的是时钟源端口或引脚,-master_clock 才是用来指定主时钟名字的。两者不要搞混。

4.2 倍频时钟的约束

倍频时钟通常来自 PLL 或 MMCM。比如一个 100MHz 输入,PLL 输出 500MHz:

create_clock -name clk_in -period 10 [get_ports clk_in]
create_generated_clock -name clk_500m -source [get_ports clk_in] \
    -multiply_by 5 [get_pins PLL_INST/clk_out]

嗯,这里要注意:-multiply_by 和 -divide_by 可以同时使用。比如你要生成 250MHz(乘 5 除 2):

create_generated_clock -name clk_250m -source [get_ports clk_in] \
    -multiply_by 5 -divide_by 2 [get_pins PLL_INST/clk_out]

我曾经在一个项目中,PLL 输出频率算错了。原因是只看 -multiply_by 忘了 -divide_by。后来我养成了习惯:每次写完约束,手动算一遍频率,再跟 spec 对一下。

4.3 相移时钟的约束

相移时钟稍微复杂一点。比如一个 90° 相移的时钟:

create_generated_clock -name clk_90 -source [get_pins PLL_INST/clk_in] \
    -edges {1 3 5} [get_pins PLL_INST/clk_90_out]

-edges 参数指定了衍生时钟的上升沿、下降沿、下一个上升沿分别对应源时钟的第几个边沿。这里 {1 3 5} 表示:

  • 第一个上升沿 = 源时钟的第 1 个边沿
  • 第一个下降沿 = 源时钟的第 3 个边沿
  • 第二个上升沿 = 源时钟的第 5 个边沿

说白了,就是告诉工具:我这个时钟的波形,是从源时钟的哪些边沿「切」出来的。

小技巧:如果你不确定 -edges 怎么写,可以用 -edge_shift 加延迟的方式。比如 90° 相移 = 2.5ns 延迟(100MHz 下):

create_generated_clock -name clk_90 -source [get_pins PLL_INST/clk_in] \
    -divide_by 1 -edge_shift {2.5 2.5 2.5} [get_pins PLL_INST/clk_90_out]

4.4 组合逻辑生成的时钟

还有一种情况:时钟经过组合逻辑。比如 clk_gated = clk_src & enable:

create_generated_clock -name clk_gated -source [get_ports clk_src] \
    -combinational [get_pins U_GATE/clk_gated]

用 -combinational 告诉工具:这个时钟跟源时钟是组合关系,没有分频倍频。工具会自动推导边沿关系。

避坑指南:我曾经遇到一个 bug,组合逻辑时钟的 -source 写错了引脚,结果时序分析完全乱套。检查了三天才发现。所以我的建议是:用 report_clock_interaction 检查所有生成时钟的源是否正确。

4.5 多级生成时钟

有时候时钟会经过多级处理。比如先分频再倍频:

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 U_DIV/clk_div2_reg/Q]
create_generated_clock -name clk_final -source [get_pins U_DIV/clk_div2_reg/Q] \
    -multiply_by 3 [get_pins PLL_INST/clk_out]

注意第二级生成时钟的 -source 指向的是第一级生成时钟的输出引脚,不是源时钟。工具会自动追踪到最原始的时钟。

4.6 知识体系图

下面这张图总结了生成时钟约束的核心逻辑:

生成时钟约束核心逻辑 源时钟 create_clock 生成时钟 create_generated_clock -source -master_clock 分频时钟 -divide_by N 倍频时钟 -multiply_by N 相移时钟 -edges / -edge_shift 关键参数说明 • -source:指定源时钟的物理节点(端口或引脚) • -master_clock:指定源时钟的时钟对象(可选,用于多源场景) • -divide_by / -multiply_by:分频/倍频系数 • -edges:手动指定边沿对应关系(精确控制波形) • -edge_shift:在 -edges 基础上增加延迟偏移 • -combinational:组合逻辑生成的时钟(无分频倍频)

4.7 常见错误与检查方法

最后,我总结几个常见错误:

错误类型 现象 解决方法
-source 指向时钟名 工具报 warning 改为指向端口或引脚
分频系数写反 频率不对 手动计算验证
多级时钟源选错 时序路径误判 用 report_clock_interaction 检查
相移边沿数错 波形相位错误 用 report_clock -skew 查看

我的习惯:写完所有生成时钟约束后,跑一遍 report_clock -generated,检查每个时钟的频率、相位、源是否正确。这一步花不了 5 分钟,但能省下后面 debug 的好几天。

好了,生成时钟约束就讲到这里。记住一句话:每个衍生时钟都要有户口,每个户口都要写对源。下次咱们聊聊异步时钟域的约束,那个更刺激。

可可精品资料库