第三章 生成时钟约束:create_generated_clock命令

时钟约束里,最让人头疼的往往不是主时钟,而是那些分频、倍频出来的衍生时钟。说白了,就是PLL或者MMCM输出的那些时钟。我刚开始做Vitis平台时,就吃过这个亏——约束没写对,时序分析报告里一堆红色violation,查了半天才发现是generated clock没定义好。

3.1 create_generated_clock 的基本用法

这个命令的作用,就是告诉工具:嘿,这里有个时钟,它是从另一个时钟派生出来的。语法其实不复杂:

create_generated_clock -name <时钟名> \
    -source <源时钟端口/网络> \
    -divide_by <分频系数> \
    -multiply_by <倍频系数> \
    [ -edges <边沿列表> ] \
    [ -edge_shift <边沿偏移> ] \
    [ -invert ] \
    [ -combinational ] \
    [ -master_clock <主时钟名> ] \
    [ -add ] \
    [ -phase_shift <相移值> ] \
    [ -duty_cycle <占空比> ] \
    <目标端口/网络>

嗯,参数看着多,但常用的其实就那几个。我一般习惯这样写:

# 简单二分频
create_generated_clock -name clk_div2 \
    -source [get_ports clk_in] \
    -divide_by 2 \
    [get_pins reg_div/Q]

# 倍频时钟(比如PLL输出)
create_generated_clock -name clk_2x \
    -source [get_pins pll_instance/CLKIN] \
    -multiply_by 2 \
    [get_pins pll_instance/CLKOUT0]

关键点:-source 指定的是源时钟的物理节点,不是时钟名。很多人在这里写错,我见过好几次了。

3.2 分频时钟的约束方法

分频时钟是最常见的场景。计数器分频、PLL分频,本质上都是把频率降下来。

场景一:寄存器分频

比如你用D触发器做了一个二分频:

always @(posedge clk_in) begin
    clk_div2 <= ~clk_div2;
end

约束写法:

create_generated_clock -name gen_clk_div2 \
    -source [get_ports clk_in] \
    -divide_by 2 \
    [get_pins u_divider/clk_div2_reg/Q]

这里有个坑——source要选对。我曾经犯过一个错误,把source写成了clk_div2_reg的时钟引脚,结果时序分析完全乱套。正确的做法是:source指向分频器的输入时钟网络,也就是clk_in。

场景二:PLL分频输出

Vitis平台里,PLL的输出时钟通常会自动生成约束,但有时候需要手动调整。比如:

create_generated_clock -name clk_50M \
    -source [get_pins mmcm_inst/CLKIN1] \
    -divide_by 10 \
    -multiply_by 5 \
    [get_pins mmcm_inst/CLKOUT0]

这样就是5倍频再10分频,相当于0.5倍频。嗯,实际项目中很少这么用,但语法上完全支持。

3.3 倍频时钟的约束方法

倍频时钟通常由PLL或MMCM产生。你想想看,如果主时钟是100MHz,PLL输出200MHz,那这个200MHz就是倍频时钟。

约束示例:

create_generated_clock -name clk_200M \
    -source [get_pins pll_inst/CLKIN] \
    -multiply_by 2 \
    [get_pins pll_inst/CLKOUT]

小技巧:如果PLL输出有相移,可以用 -phase_shift 参数。比如相移90度:

create_generated_clock -name clk_shift90 \
    -source [get_pins pll_inst/CLKIN] \
    -multiply_by 2 \
    -phase_shift 90 \
    [get_pins pll_inst/CLKOUT]

3.4 与主时钟的关系

生成时钟和主时钟之间,存在明确的相位和频率关系。工具需要知道这个关系,才能正确分析跨时钟域路径。

关系一:同源时钟

如果生成时钟和主时钟来自同一个源,它们就是同源时钟。比如:

create_clock -name sys_clk -period 10 [get_ports clk_in]
create_generated_clock -name clk_div2 \
    -source [get_ports clk_in] \
    -divide_by 2 \
    [get_pins reg_div/Q]

这种情况下,sys_clk和clk_div2是同步的,工具会自动分析它们之间的路径。

关系二:异步时钟

如果生成时钟和主时钟来自不同的PLL,或者没有明确的相位关系,就需要用set_clock_groups来声明异步:

set_clock_groups -asynchronous \
    -group [get_clocks sys_clk] \
    -group [get_clocks clk_div2]

注意:不要随意把同源时钟设为异步!我见过有人把所有时钟都设成异步,结果时序分析完全失效。同源时钟之间,工具是能自动处理跨时钟域路径的。

3.5 实战中的避坑指南

我总结几个常见问题:

  1. source节点选错:一定要选物理节点,不是时钟名。用get_pins或get_ports,不要用get_clocks。
  2. 忘记加 -master_clock:如果源时钟有多个,需要指定用哪个主时钟。比如PLL有两个输入时钟:
create_generated_clock -name pll_out \
    -source [get_pins pll_inst/CLKIN] \
    -master_clock clk_100M \
    -multiply_by 2 \
    [get_pins pll_inst/CLKOUT]
  1. 分频系数写反:divide_by 2 是二分频,不是乘以2。这个别搞混了。
  2. 忽略占空比:默认占空比是50%,如果实际不是,要用 -duty_cycle 指定。

3.6 知识体系图

下面这张图,把生成时钟约束的核心逻辑串起来了:

主时钟 (create_clock) create_generated_clock -source -divide_by -multiply_by 分频时钟 -divide_by N 倍频时钟 -multiply_by N 相移时钟 -phase_shift / -edges 同源时钟:自动分析 异步时钟:set_clock_groups 多源时钟:-master_clock 核心原则:source指向物理节点,明确频率/相位关系,区分同源/异步 避坑:不要用get_clocks做source,不要随意设异步,注意分频系数

3.7 总结一下

生成时钟约束,说白了就是告诉工具:这个时钟是怎么来的。你只要把分频比、倍频比、相移这些参数写清楚,工具就能自动推导出时序关系。

我个人习惯是:先确认主时钟约束正确,再逐个添加生成时钟。每加一个,就跑一次report_clock_interaction看看关系对不对。嗯,这样虽然慢一点,但能避免很多低级错误。

最后提醒一句:不要偷懒用自动约束。Vitis平台虽然能自动生成一些约束,但复杂场景下还是手动写更靠谱。我曾经在一个多时钟域的项目里,就是因为相信自动约束,结果漏掉了一个生成时钟,导致整个时序分析白做。

可可精品资料库