3、生成时钟约束:create_generated_clock命令,分频、倍频与相移时钟的约束方法

大家好,我是老李。做FPGA时序约束这么多年,我敢说生成时钟这块是新手最容易翻车的地方。很多人觉得create_clock就够用了,结果综合后时序乱成一锅粥。今天咱们就好好聊聊create_generated_clock这个命令,把分频、倍频、相移这些场景一次说透。

3.1 为什么需要生成时钟约束?

先问个问题:你的PLL输出了一个200MHz时钟,这个时钟跟输入时钟是什么关系?

Vivado不会自动推导这种关系。你得明确告诉工具:这个时钟是从哪个主时钟派生出来的,分频比是多少,相位偏移是多少。否则工具会把它当成独立时钟,跨时钟域分析全乱套。

我在一个项目中遇到过这种情况:PLL输出时钟没加generated_clock约束,结果setup time violation报了上千条。排查了半天,发现是工具把两个相关时钟当成了异步时钟来处理。加了约束后,violation直接清零。

核心原则:所有由PLL、MMCM、BUFR、ODDR等产生的时钟,都必须用create_generated_clock约束。

3.2 create_generated_clock命令详解

命令格式其实不复杂,我习惯这么写:

create_generated_clock -name <时钟名> \
  -source <主时钟引脚> \
  -divide_by <分频系数> \
  -multiply_by <倍频系数> \
  -phase <相移角度> \
  -edges <边沿列表> \
  [get_pins <目标引脚>]

参数看着多,但常用的就几个。我一个个说。

3.3 分频时钟约束

分频是最常见的场景。比如主时钟100MHz,你用一个计数器分频出50MHz时钟。

方法一:用divide_by参数

create_clock -name clk_100m -period 10.000 [get_ports clk_in]
create_generated_clock -name clk_50m \
  -source [get_ports clk_in] \
  -divide_by 2 \
  [get_pins clk_div_reg/Q]

这里要注意:-source要指向主时钟的源端,不是分频器的输出。我见过有人写成-source [get_pins clk_div_reg/C],这是错的。

方法二:用edges参数(更灵活)

create_generated_clock -name clk_50m \
  -source [get_ports clk_in] \
  -edges {1 3 5} \
  [get_pins clk_div_reg/Q]

edges参数指定了生成时钟的边沿位置。{1 3 5}表示:第一个上升沿对应主时钟的第1个边沿,第一个下降沿对应第3个边沿,第二个上升沿对应第5个边沿。说白了就是每两个主时钟周期产生一个生成时钟周期。

我的建议:简单整数分频用divide_by,非整数分频或特殊波形用edges。edges参数虽然写起来麻烦,但能处理任意波形。

3.4 倍频时钟约束

倍频通常靠PLL或MMCM实现。比如输入100MHz,PLL输出200MHz。

create_clock -name clk_100m -period 10.000 [get_ports clk_in]
create_generated_clock -name clk_200m \
  -source [get_ports clk_in] \
  -multiply_by 2 \
  [get_pins mmcm_inst/CLKOUT0]

嗯,这里有个坑:倍频时钟的-source要指向PLL的输入时钟引脚,不是PLL的输出。我刚开始做的时候也搞反过,结果时序分析完全不对。

如果同时分频和倍频呢?比如输入100MHz,输出150MHz(3/2倍):

create_generated_clock -name clk_150m \
  -source [get_ports clk_in] \
  -multiply_by 3 \
  -divide_by 2 \
  [get_pins mmcm_inst/CLKOUT1]

Vivado会自动计算周期:10ns × 2 / 3 ≈ 6.667ns。

3.5 相移时钟约束

相移时钟在DDR接口、ADC采样中很常见。比如主时钟100MHz,需要偏移90度。

create_generated_clock -name clk_90deg \
  -source [get_ports clk_in] \
  -multiply_by 1 \
  -phase 90 \
  [get_pins mmcm_inst/CLKOUT2]

这里-phase的单位是度。90度对应2.5ns(10ns周期)。

注意:phase参数只对PLL/MMCM的输出有效。如果你用逻辑门做相移,Vivado无法自动计算相位关系,需要用set_clock_relationships手动指定。

我曾经在一个DDR3项目中,因为相移时钟没加-phase参数,导致读数据窗口一直对不准。加了之后,眼图立马打开了。你想想看,工具不知道时钟偏移了多少,它怎么帮你做时序优化?

3.6 知识体系结构图

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

生成时钟约束知识体系 主时钟 (create_clock) 生成时钟 (create_generated_clock) 分频时钟 -divide_by N -edges {1 3 5 ...} 倍频时钟 -multiply_by N -multiply_by / -divide_by 相移时钟 -phase 角度 -edges {偏移边沿} 所有派生时钟必须显式约束,否则时序分析不可靠

3.7 实战中的避坑指南

说了这么多,我总结几个实战中容易踩的坑:

  1. source选错位置:-source要指向主时钟的源端,不是生成时钟的源端。PLL的输出时钟,source是PLL的输入引脚。
  2. 忘记加生成时钟约束:PLL输出时钟如果不加约束,Vivado会把它当成独立时钟,跨时钟域分析会出问题。
  3. 分频比写反:divide_by 2是二分频,不是二倍频。我见过有人把divide_by 2当成倍频用,结果时钟周期变成了20ns。
  4. 相移时钟没加-phase:PLL输出相移时钟时,一定要加-phase参数,否则工具不知道相位关系。
小技巧:写完约束后,在Vivado的Timing Constraints窗口里查看Generated Clocks列表。如果看到时钟关系是"Related"而不是"Independent",说明约束对了。

好了,生成时钟约束就讲到这里。说白了就是一句话:所有派生时钟都要用create_generated_clock告诉工具它的来源和变换关系。下一节咱们聊聊输入延迟约束,那个更贴近实际项目。

可可精品资料库