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下降。
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 主时钟与生成时钟的关系
我画了一张图,帮你理清它们之间的关系:
从图上可以看得很清楚:主时钟是源头,生成时钟是它的派生。每个生成时钟都必须指定一个主时钟作为 -source。工具通过这个关系来推导时钟之间的相位和周期关系。
3.4 常见错误与避坑指南
- source指定错误:
-source必须指向主时钟到达生成点之前的引脚,而不是主时钟的端口名。很多人写成-source [get_clocks clk_main],这是错的。 - 忘记定义主时钟:生成时钟必须先有主时钟。如果主时钟没定义,生成时钟会报错。
- 分频比写反:
-divide_by 2是周期变2倍(频率减半),别搞反了。 - 门控时钟漏掉 -combinational:如果门控时钟是组合逻辑输出,不加这个参数,工具会认为它经过了时序单元,分析会出错。
report_clocks 看看所有时钟是否定义正确。再跑一下 check_timing,工具会告诉你有没有未约束的时钟路径。这一步花不了几分钟,但能省后面几天debug的时间。
3.5 小结
主时钟和生成时钟是时序约束的起点。说白了,就是告诉工具你的时钟长什么样、从哪里来、到哪里去。
我个人习惯,每次写约束前先画个时钟树草图,标清楚每个时钟的源、频率、分频关系。这样写出来的约束清晰、不容易漏。你想想看,如果连时钟都定义错了,后面的时序分析还有什么意义?
嗯,这一节就到这里。记住:约束写得好,STA没烦恼。