4、生成时钟约束:create_generated_clock命令、分频与倍频时钟、相位关系
时钟树上的分频、倍频、相移,说白了就是给芯片内部的各种衍生时钟「上户口」。我刚开始做FPGA时,总觉得只要PLL配好了,时序工具会自动搞定一切。结果呢?有一次项目里用了MMCM的多个输出,时序报告里一堆违例,查了半天才发现——我根本没告诉工具这些时钟之间的关系。
嗯,从那天起我就记住了:所有由主时钟衍生出来的时钟,都必须用 create_generated_clock 显式声明。今天咱们就聊聊这个命令怎么用,以及分频、倍频、相位关系怎么处理。
4.1 为什么需要生成时钟约束?
你想想看,主时钟(比如板上的晶振)是FPGA的「心跳」。但PLL、MMCM、甚至简单的计数器分频,都会产生新的时钟信号。这些新时钟的频率、相位、占空比都变了。
工具默认不知道这些变化。它只会按照主时钟的周期去分析路径。结果就是:时序分析完全跑偏。
核心原则:所有由组合逻辑或时序逻辑产生的时钟信号,只要不是直接从输入端口进来的,都需要用 create_generated_clock 约束。
4.2 create_generated_clock 命令详解
这个命令的语法其实不复杂,但参数很多。我挑最常用的几个说。
create_generated_clock -name <clock_name> \
-source <master_pin> \
-divide_by <N> \
-multiply_by <M> \
-edges <edge_list> \
-edge_shift <shift_list> \
-duty_cycle <percent> \
-combinational \
[get_pins <target_pin>]
| 参数 | 说明 | 我的经验 |
|---|---|---|
-name |
给生成的时钟起个名字 | 命名要有规律,比如 clk_50m、clk_100m_phase90 |
-source |
指定源时钟的引脚或端口 | 必须是已经约束过的主时钟或生成时钟 |
-divide_by |
分频系数 | 整数,比如2就是二分频 |
-multiply_by |
倍频系数 | 整数,比如2就是二倍频 |
-edges |
自定义边沿序列 | 用于非整数分频或特殊波形 |
-edge_shift |
边沿偏移量 | 单位是ps,用于相移 |
-combinational |
标识是组合逻辑产生的时钟 | 比如用LUT做的门控时钟 |
4.3 分频时钟约束
分频是最常见的场景。PLL输出、计数器分频,都属于这一类。
4.3.1 整数分频(PLL/MMCM输出)
假设主时钟是100MHz,PLL输出一个50MHz的时钟。约束很简单:
create_clock -name clk_100m -period 10.000 [get_ports clk_in]
create_generated_clock -name clk_50m \
-source [get_pins pll_inst/clk_in] \
-divide_by 2 \
[get_pins pll_inst/clk_out]
这里要注意:-source 指向的是PLL的输入引脚,不是主时钟的端口。我见过有人直接写 [get_ports clk_in],这样也能用,但不够精确。工具需要知道生成时钟和源时钟之间的物理路径。
4.3.2 计数器分频(非PLL)
有时候我们会用计数器做分频。比如一个简单的二分频:
always @(posedge clk_in) begin
clk_div2 <= ~clk_div2;
end
约束写法:
create_generated_clock -name clk_div2 \
-source [get_pins reg_div2/C] \
-divide_by 2 \
[get_pins reg_div2/Q]
小技巧:计数器分频的源时钟引脚,要选触发器的时钟引脚(C或CLK),而不是输出引脚。这样工具才能正确计算时钟延迟。
4.4 倍频时钟约束
倍频通常由PLL或MMCM完成。比如100MHz输入,输出200MHz:
create_generated_clock -name clk_200m \
-source [get_pins pll_inst/clk_in] \
-multiply_by 2 \
[get_pins pll_inst/clk_out]
倍频和分频可以组合使用。比如输入100MHz,输出150MHz(3/2倍频):
create_generated_clock -name clk_150m \
-source [get_pins pll_inst/clk_in] \
-multiply_by 3 \
-divide_by 2 \
[get_pins pll_inst/clk_out]
注意:倍频和分频系数必须是整数。如果你需要非整数倍频(比如1.5倍),只能用 -edges 参数手动定义边沿。
4.5 相位关系处理
相位关系是生成时钟约束里最容易出错的地方。我踩过不少坑。
4.5.1 相移时钟
PLL经常输出相移时钟,比如0°、90°、180°、270°。约束时要用 -edge_shift:
# 主时钟 100MHz,周期10ns
create_clock -name clk_100m -period 10.000 [get_ports clk_in]
# PLL输出 100MHz,相位偏移90°(即2.5ns)
create_generated_clock -name clk_100m_90 \
-source [get_pins pll_inst/clk_in] \
-multiply_by 1 \
-divide_by 1 \
-edge_shift {2.500} \
[get_pins pll_inst/clk_out_90]
这里 -edge_shift 的单位是ns。90°相移对应2.5ns(10ns / 4)。
4.5.2 多边沿偏移
如果时钟波形比较复杂,可以用 -edges 和 -edge_shift 组合。比如一个三分频时钟,占空比50%:
create_generated_clock -name clk_div3 \
-source [get_pins reg_div3/C] \
-edges {1 3 5} \
-edge_shift {0 0 0} \
[get_pins reg_div3/Q]
-edges 指定了源时钟的边沿序号(从1开始)。{1 3 5} 表示:上升沿在源时钟的第1个边沿,下降沿在第3个,下一个上升沿在第5个。这样周期就是源时钟的3倍。
关键点:-edges 列表必须包含3个值:上升沿、下降沿、下一个上升沿。工具根据这三个值计算周期和占空比。
4.6 实战中的避坑指南
我总结几个常见问题,都是我曾经掉进去过的坑:
- 源时钟选错:一定要选物理引脚,不要选时钟名字。比如
[get_pins pll_inst/clk_in]而不是clk_100m。 - 忘记约束所有输出:PLL可能有多个输出,每个都要单独约束。别偷懒。
- 相位偏移单位搞错:Vivado里
-edge_shift单位是ps,但有些工具用ns。看清楚文档。 - 组合逻辑时钟不加
-combinational:如果时钟是通过LUT或MUX产生的,必须加这个参数,否则工具会报错。
4.7 知识体系总结
下面这张图概括了生成时钟约束的核心逻辑:
说白了,生成时钟约束就是告诉工具:这个时钟从哪里来,频率是多少,相位偏移了多少。掌握了这三个要素,大部分场景都能搞定。
我个人习惯在写约束时,先列一个表格,把PLL的每个输出频率、相位、分倍频系数都写清楚,然后再一条条写命令。这样不容易漏,也方便review。
好了,生成时钟约束就聊到这儿。记住:时钟是时序分析的基石,约束写对了,后面的分析才有意义。
我的建议:写完约束后,用 report_clocks 命令检查一下。看看生成的时钟名字、周期、波形是不是你想要的。这一步花不了1分钟,但能省下后面排查问题的半天时间。