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 知识体系总结

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

主时钟 create_clock create_generated_clock -source -divide_by -multiply_by -edges 分频时钟 -divide_by N 倍频时钟 -multiply_by M 相移时钟 -edge_shift 生成时钟约束核心要点 • 所有衍生时钟必须显式约束,不能依赖工具自动推断 • -source 指向物理引脚,而非时钟名称 • 分频/倍频系数必须为整数,非整数用 -edges 自定义 • 相位偏移注意单位:Vivado 用 ps,其他工具可能用 ns

说白了,生成时钟约束就是告诉工具:这个时钟从哪里来,频率是多少,相位偏移了多少。掌握了这三个要素,大部分场景都能搞定。

我个人习惯在写约束时,先列一个表格,把PLL的每个输出频率、相位、分倍频系数都写清楚,然后再一条条写命令。这样不容易漏,也方便review。

好了,生成时钟约束就聊到这儿。记住:时钟是时序分析的基石,约束写对了,后面的分析才有意义。

我的建议:写完约束后,用 report_clocks 命令检查一下。看看生成的时钟名字、周期、波形是不是你想要的。这一步花不了1分钟,但能省下后面排查问题的半天时间。

可可精品资料库