3、生成时钟约束:create_generated_clock命令,分频、倍频与相移时钟的约束方法
大家好,我是老李。做FPGA时序约束这么多年,我敢说生成时钟这块是新手最容易翻车的地方。很多人觉得create_clock就够用了,结果综合后时序乱成一锅粥。今天咱们就好好聊聊create_generated_clock这个命令,把分频、倍频、相移这些场景一次说透。
3.1 为什么需要生成时钟约束?
先问个问题:你的PLL输出了一个200MHz时钟,这个时钟跟输入时钟是什么关系?
Vivado不会自动推导这种关系。你得明确告诉工具:这个时钟是从哪个主时钟派生出来的,分频比是多少,相位偏移是多少。否则工具会把它当成独立时钟,跨时钟域分析全乱套。
我在一个项目中遇到过这种情况:PLL输出时钟没加generated_clock约束,结果setup time violation报了上千条。排查了半天,发现是工具把两个相关时钟当成了异步时钟来处理。加了约束后,violation直接清零。
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个边沿。说白了就是每两个主时钟周期产生一个生成时钟周期。
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周期)。
我曾经在一个DDR3项目中,因为相移时钟没加-phase参数,导致读数据窗口一直对不准。加了之后,眼图立马打开了。你想想看,工具不知道时钟偏移了多少,它怎么帮你做时序优化?
3.6 知识体系结构图
下面这张图总结了生成时钟约束的核心逻辑:
3.7 实战中的避坑指南
说了这么多,我总结几个实战中容易踩的坑:
- source选错位置:-source要指向主时钟的源端,不是生成时钟的源端。PLL的输出时钟,source是PLL的输入引脚。
- 忘记加生成时钟约束:PLL输出时钟如果不加约束,Vivado会把它当成独立时钟,跨时钟域分析会出问题。
- 分频比写反:divide_by 2是二分频,不是二倍频。我见过有人把divide_by 2当成倍频用,结果时钟周期变成了20ns。
- 相移时钟没加-phase:PLL输出相移时钟时,一定要加-phase参数,否则工具不知道相位关系。
好了,生成时钟约束就讲到这里。说白了就是一句话:所有派生时钟都要用create_generated_clock告诉工具它的来源和变换关系。下一节咱们聊聊输入延迟约束,那个更贴近实际项目。