第三章 生成时钟约束: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 实战中的避坑指南
我总结几个常见问题:
- source节点选错:一定要选物理节点,不是时钟名。用get_pins或get_ports,不要用get_clocks。
- 忘记加 -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]
- 分频系数写反:divide_by 2 是二分频,不是乘以2。这个别搞混了。
- 忽略占空比:默认占空比是50%,如果实际不是,要用 -duty_cycle 指定。
3.6 知识体系图
下面这张图,把生成时钟约束的核心逻辑串起来了:
3.7 总结一下
生成时钟约束,说白了就是告诉工具:这个时钟是怎么来的。你只要把分频比、倍频比、相移这些参数写清楚,工具就能自动推导出时序关系。
我个人习惯是:先确认主时钟约束正确,再逐个添加生成时钟。每加一个,就跑一次report_clock_interaction看看关系对不对。嗯,这样虽然慢一点,但能避免很多低级错误。
最后提醒一句:不要偷懒用自动约束。Vitis平台虽然能自动生成一些约束,但复杂场景下还是手动写更靠谱。我曾经在一个多时钟域的项目里,就是因为相信自动约束,结果漏掉了一个生成时钟,导致整个时序分析白做。
可可精品资料库