结果比对与时序检查协同实战 - 第3节
一、什么是时序约束?
各位同学,咱们今天聊聊时序约束。说白了,时序约束就是告诉工具:你的芯片要在多快的速度下工作。
我刚开始做设计那会儿,觉得时序约束就是个形式。随便写几条,工具能跑就行。直到有一次,芯片流片回来,死活跑不到目标频率。查了三天,发现是约束写错了。嗯,从那以后,我再也不敢马虎了。
1.1 为什么需要时序约束?
你想想看,芯片里有成千上万个寄存器。它们之间要传递数据。数据从A寄存器出发,经过组合逻辑,到达B寄存器。这个过程需要时间。
如果时钟周期是10ns,数据必须在10ns内到达。否则,B寄存器就可能采到错误的数据。这就是所谓的建立时间违例。
时序约束的作用,就是告诉工具:
- 时钟长什么样(频率、占空比)
- 数据什么时候来(输入延迟)
- 数据什么时候走(输出延迟)
- 哪些路径是假的(false path)
工具根据这些约束,去计算每条路径的延迟。如果发现某条路径太慢,就会报错。你就要去优化它。
核心思想:没有约束,工具就不知道你的设计要跑多快。它只能保证功能正确,不能保证时序正确。
1.2 时序约束的分类
我个人习惯把时序约束分成三大类:
| 类别 | 说明 | 常用命令 |
|---|---|---|
| 时钟约束 | 定义时钟的周期、波形 | create_clock, create_generated_clock |
| IO约束 | 定义输入输出延迟 | set_input_delay, set_output_delay |
| 例外约束 | 定义特殊路径 | set_false_path, set_multicycle_path |
今天咱们重点讲时钟约束。这是最基础,也是最重要的部分。
二、主时钟约束(create_clock)
2.1 什么是主时钟?
主时钟,就是芯片的外部输入时钟。它通常来自晶振、PLL或者外部芯片。
举个例子。你的芯片有一个时钟输入引脚叫clk_in。外部晶振给它送了一个100MHz的时钟。那么,你就要用create_clock命令告诉工具:
create_clock -name clk -period 10 [get_ports clk_in]
这条命令的意思是:
-name clk:给这个时钟起个名字,叫clk-period 10:周期是10ns,对应100MHz[get_ports clk_in]:时钟源是clk_in这个端口
2.2 波形定义
有时候,时钟不是50%占空比的。比如,有些芯片要求高电平只占30%。这时候就要指定波形:
create_clock -name clk -period 10 -waveform {0 3} [get_ports clk_in]
-waveform {0 3}表示:上升沿在0ns,下降沿在3ns。高电平持续3ns,低电平持续7ns。占空比就是30%。
小技巧:如果不指定waveform,默认是50%占空比。上升沿在0,下降沿在period/2。
2.3 多个主时钟
一个芯片可能有多个时钟输入。比如,一个用于CPU,一个用于外设。每个都要单独约束:
create_clock -name cpu_clk -period 10 [get_ports clk_cpu]
create_clock -name periph_clk -period 20 [get_ports clk_periph]
我遇到过一种情况:同一个端口,在不同模式下频率不同。比如,正常模式100MHz,测试模式50MHz。这时候可以用-add选项:
create_clock -name clk_normal -period 10 [get_ports clk_in]
create_clock -name clk_test -period 20 [get_ports clk_in] -add
注意那个-add。不加的话,第二条会覆盖第一条。
三、生成时钟约束(create_generated_clock)
3.1 什么是生成时钟?
生成时钟,就是由主时钟经过分频、倍频或相移得到的时钟。它通常来自PLL、分频器或门控时钟电路。
举个例子。你的PLL输入是100MHz,输出是200MHz。这个200MHz就是生成时钟。你不能用create_clock去定义它,因为它的源头是PLL内部,不是外部引脚。
正确的做法是:
create_clock -name clk_in -period 10 [get_ports clk_in]
create_generated_clock -name clk_pll -source [get_ports clk_in] -divide_by 1 -multiply_by 2 [get_pins pll/clk_out]
这条命令的意思是:
-source:指定源时钟,也就是主时钟-divide_by 1 -multiply_by 2:倍频2倍[get_pins pll/clk_out]:生成时钟的引脚
3.2 分频时钟
分频是最常见的生成时钟。比如,一个计数器把100MHz分成50MHz:
create_generated_clock -name clk_div2 -source [get_ports clk_in] -divide_by 2 [get_pins divider/clk_out]
这里-divide_by 2表示2分频。周期从10ns变成20ns。
我曾经犯过一个错误:分频时钟的源时钟写错了。我写成了分频器的输出引脚,而不是输入引脚。结果工具报了一堆奇怪的时序错误。查了半天才发现是source写错了。
注意:create_generated_clock的-source必须是主时钟的源点,不能是生成时钟本身。否则工具会搞混时钟关系。
3.3 相移时钟
有时候,我们需要一个相位偏移的时钟。比如,PLL输出0°和90°两个时钟:
create_generated_clock -name clk_0 -source [get_ports clk_in] -multiply_by 2 [get_pins pll/clk_0]
create_generated_clock -name clk_90 -source [get_ports clk_in] -multiply_by 2 -phase 90 [get_pins pll/clk_90]
-phase 90表示相位偏移90°。这样,clk_90的上升沿比clk_0晚了2.5ns(周期10ns,90°对应2.5ns)。
3.4 生成时钟的波形
生成时钟的波形,工具会自动计算。你不需要手动指定-waveform。工具会根据源时钟和分频/倍频系数,推导出正确的波形。
但有一种情况例外:门控时钟。比如,一个AND门把时钟和使能信号相与:
create_generated_clock -name gated_clk -source [get_ports clk_in] -combinational [get_pins and_gate/z]
-combinational表示这是组合逻辑产生的时钟。工具会认为它的波形和源时钟一样,只是多了使能控制。
四、知识体系总览
下面这张图,是我画的时序约束入门知识体系。你可以看到,时钟约束是整个时序分析的基石。
五、常见问题与避坑指南
5.1 时钟源写错了
我曾经在项目中,把生成时钟的-source写成了分频器的输出。结果工具认为这个时钟是独立的,和主时钟没关系。时序分析全乱了。
正确做法:-source一定要写主时钟的源点,也就是create_clock定义的那个端口。
5.2 忘记加-add
同一个端口定义多个时钟时,一定要加-add。否则后面的会覆盖前面的。
我见过一个案例:工程师在同一个端口定义了100MHz和50MHz两个时钟,但没加-add。结果工具只认50MHz那个。芯片在100MHz模式下时序全错。
5.3 生成时钟的波形
不要手动给生成时钟指定-waveform。工具会自动计算。你手动指定反而容易出错。
除非是门控时钟或者特殊电路,否则让工具自己算。
我的习惯:写完约束后,用report_clocks命令检查一下。看看每个时钟的周期、波形、源点是否正确。这一步花不了1分钟,但能省下后面几小时的调试时间。
六、小结
今天咱们讲了时序约束的核心:
- 主时钟约束:用
create_clock定义外部输入时钟 - 生成时钟约束:用
create_generated_clock定义内部衍生时钟 - 注意
-source的正确写法 - 多个时钟要加
-add
说白了,时钟约束就是告诉工具:你的时钟长什么样。工具知道了时钟,才能去分析时序。这是STA的第一步,也是最关键的一步。
下次咱们聊IO约束。到时候我会讲讲输入延迟和输出延迟怎么设,以及我在项目中踩过的坑。