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 知识体系图
下面这张图总结了生成时钟约束的核心逻辑:
4.7 常见错误与检查方法
最后,我总结几个常见错误:
| 错误类型 | 现象 | 解决方法 |
|---|---|---|
| -source 指向时钟名 | 工具报 warning | 改为指向端口或引脚 |
| 分频系数写反 | 频率不对 | 手动计算验证 |
| 多级时钟源选错 | 时序路径误判 | 用 report_clock_interaction 检查 |
| 相移边沿数错 | 波形相位错误 | 用 report_clock -skew 查看 |
我的习惯:写完所有生成时钟约束后,跑一遍 report_clock -generated,检查每个时钟的频率、相位、源是否正确。这一步花不了 5 分钟,但能省下后面 debug 的好几天。
好了,生成时钟约束就讲到这里。记住一句话:每个衍生时钟都要有户口,每个户口都要写对源。下次咱们聊聊异步时钟域的约束,那个更刺激。