3、时序约束入门:主时钟约束(create_clock)、生成时钟约束(create_generated_clock)

时序约束,说白了就是告诉工具你的芯片想跑多快、怎么跑。很多新手觉得这步就是写几个命令,没什么技术含量。我刚开始也这么想,直到有一次因为少写了一个生成时钟约束,导致后仿时序崩得一塌糊涂……嗯,从那以后我再也不敢轻视这步了。

今天咱们就聊聊最基础、也最重要的两个约束:create_clock 和 create_generated_clock。你想想看,整个STA的起点就是时钟,时钟定义错了,后面全是白搭。

3.1 主时钟约束:create_clock

create_clock 是时序约束的基石。它定义了一个时钟的周期、波形、以及它从哪个端口或引脚进入芯片。

我个人习惯,拿到一个新设计,第一件事就是先看时钟结构图。搞清楚有几个主时钟、频率多少、从哪个PAD进来。这一步做扎实了,后面写约束就顺了。

3.1.1 基本语法

create_clock -name <clock_name> -period <period_value> [ -waveform <edge_list> ] [ <source_objects> ]

参数说明:

参数 含义 示例
-name 时钟名称,建议有意义 clk_sys
-period 时钟周期,单位ns 10.0
-waveform 上升沿和下降沿时间 {0 5}
source_objects 时钟源端口或引脚 [get_ports clk]

3.1.2 实战示例

假设我们有一个系统时钟,频率100MHz,从端口 sys_clk 进入芯片:

create_clock -name clk_sys -period 10.0 -waveform {0 5} [get_ports sys_clk]

这里周期10ns对应100MHz,波形是50%占空比,0ns上升,5ns下降。

我的小技巧: 如果时钟是差分对(比如P/N),只需要在P端定义时钟即可。工具会自动识别差分结构。

3.1.3 多时钟定义

一个芯片往往有多个时钟域。比如一个SoC可能有CPU时钟、总线时钟、外设时钟。每个都要单独定义:

create_clock -name clk_cpu -period 5.0 [get_ports cpu_clk]
create_clock -name clk_bus -period 10.0 [get_ports bus_clk]
create_clock -name clk_uart -period 83.33 [get_ports uart_clk]

这里要注意,不同时钟域之间的跨时钟域路径(CDC)需要特殊处理。我建议在约束阶段就标记好异步时钟组,后面会省很多麻烦。

3.2 生成时钟约束:create_generated_clock

生成时钟,说白了就是由主时钟经过分频、倍频或门控后产生的派生时钟。比如PLL的输出、分频器的输出。

为什么要单独定义生成时钟?因为工具需要知道这个时钟和主时钟之间的相位关系。你想想看,如果只是简单分频,工具不知道分频比,就没法正确分析时序。

3.2.1 基本语法

create_generated_clock -name <gen_clk_name> -source <master_clock_pin> -divide_by <factor> [ -multiply_by <factor> ] [ -edges <edge_list> ] [ -combinational ] <target_pin>

关键参数:

  • -source:指定主时钟的源引脚(不是主时钟名)
  • -divide_by / -multiply_by:分频/倍频系数
  • -edges:自定义边沿关系(高级用法)
  • -combinational:纯组合逻辑生成的时钟

3.2.2 常见场景:分频时钟

假设主时钟100MHz,经过一个2分频器得到50MHz时钟:

create_clock -name clk_100m -period 10.0 [get_ports clk_in]
create_generated_clock -name clk_50m -source [get_pins div_inst/clk_in] -divide_by 2 [get_pins div_inst/clk_out]

这里有个坑:-source 要指定主时钟到达分频器输入端的引脚,而不是主时钟的端口。我曾经见过有人写成 -source [get_ports clk_in],结果工具报了一堆warning,时序分析也不对。

3.2.3 倍频时钟

PLL输出通常需要倍频:

create_clock -name clk_ref -period 20.0 [get_ports ref_clk]
create_generated_clock -name clk_pll -source [get_pins pll_inst/ref_clk] -multiply_by 5 [get_pins pll_inst/out_clk]

这样100MHz的参考时钟被5倍频到500MHz。

3.2.4 门控时钟

门控时钟在低功耗设计中很常见。比如用AND门把时钟和使能信号组合:

create_generated_clock -name clk_gated -source [get_pins and_inst/clk_in] -combinational [get_pins and_inst/out]

-combinational 告诉工具这个生成时钟是纯组合逻辑产生的,没有时序单元。工具会特殊处理它的传播。

3.3 主时钟与生成时钟的关系

我画了一张图,帮你理清它们之间的关系:

主时钟 create_clock 分频/倍频 门控 多级级联 生成时钟(分频) create_generated_clock 生成时钟(门控) -combinational 生成时钟(级联) 多级生成 时序 逻辑 路径 主时钟是源头,生成时钟是派生。两者必须一一对应,不能乱。

从图上可以看得很清楚:主时钟是源头,生成时钟是它的派生。每个生成时钟都必须指定一个主时钟作为 -source。工具通过这个关系来推导时钟之间的相位和周期关系。

3.4 常见错误与避坑指南

我曾经踩过的坑:
  • source指定错误:-source 必须指向主时钟到达生成点之前的引脚,而不是主时钟的端口名。很多人写成 -source [get_clocks clk_main],这是错的。
  • 忘记定义主时钟:生成时钟必须先有主时钟。如果主时钟没定义,生成时钟会报错。
  • 分频比写反:-divide_by 2 是周期变2倍(频率减半),别搞反了。
  • 门控时钟漏掉 -combinational:如果门控时钟是组合逻辑输出,不加这个参数,工具会认为它经过了时序单元,分析会出错。
我的调试习惯: 写完约束后,先用 report_clocks 看看所有时钟是否定义正确。再跑一下 check_timing,工具会告诉你有没有未约束的时钟路径。这一步花不了几分钟,但能省后面几天debug的时间。

3.5 小结

主时钟和生成时钟是时序约束的起点。说白了,就是告诉工具你的时钟长什么样、从哪里来、到哪里去。

我个人习惯,每次写约束前先画个时钟树草图,标清楚每个时钟的源、频率、分频关系。这样写出来的约束清晰、不容易漏。你想想看,如果连时钟都定义错了,后面的时序分析还有什么意义?

嗯,这一节就到这里。记住:约束写得好,STA没烦恼。

可可精品资料库