结果比对与时序检查协同实战 - 第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表示这是组合逻辑产生的时钟。工具会认为它的波形和源时钟一样,只是多了使能控制。

四、知识体系总览

下面这张图,是我画的时序约束入门知识体系。你可以看到,时钟约束是整个时序分析的基石。

时序约束入门知识体系 主时钟约束 create_clock 生成时钟约束 create_generated_clock 周期定义 波形定义 多时钟定义 -add选项 分频时钟 倍频时钟 相移时钟 门控时钟 时钟约束是时序分析的基石 主时钟定义外部输入,生成时钟定义内部衍生时钟

五、常见问题与避坑指南

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约束。到时候我会讲讲输入延迟和输出延迟怎么设,以及我在项目中踩过的坑。

可可精品资料库