2. 时序约束入门:创建时钟、生成时钟、输入延迟、输出延迟

各位同学,咱们今天聊聊时序约束的四个基本功。说实话,我刚入行那会儿,觉得约束就是随便写几条命令,让工具别报错就行。后来被现实狠狠教育了一顿——有一次芯片回来,功能全对,就是频率上不去,查了三天,发现是生成时钟的相位设错了。从那以后,我再也不敢小看这几条命令了。

时序约束,说白了就是告诉工具:你的设计要在什么样的节奏下工作。时钟是心跳,输入输出延迟是手脚的响应时间。搞不清楚这些,后面修时序违例就是瞎忙活。

2.1 创建时钟(create_clock)

这是最基础的命令。你想想看,芯片里那么多寄存器,它们靠什么同步?靠时钟。所以第一步,就是告诉工具时钟长什么样。

create_clock -name clk -period 10 [get_ports clk_in]

这条命令的意思是:在端口 clk_in 上创建一个名为 clk 的时钟,周期是 10ns,也就是 100MHz。

我个人习惯,创建时钟时一定会加上 -waveform 参数,明确指定占空比。虽然默认是 50%,但万一你用的是非对称时钟呢?

create_clock -name clk -period 10 -waveform {0 5} [get_ports clk_in]

这里 {0 5} 表示上升沿在 0ns,下降沿在 5ns。嗯,这里要注意:波形列表的个数必须是偶数,前一个数是上升沿,后一个是下降沿。

我的小技巧: 如果设计中有多个时钟域,建议给每个时钟起个有意义的名字,别用默认的。我在项目中吃过亏,默认名字一多,分析报告根本分不清谁是谁。

2.2 生成时钟(generate_clock)

生成时钟,也叫衍生时钟。它是在芯片内部由主时钟通过分频、倍频或门控产生的。比如你用了一个 PLL,或者用计数器做了个分频时钟。

create_generated_clock -name clk_div2 -source [get_ports clk_in] -divide_by 2 [get_pins u_div/clk_out]

这条命令告诉工具:在 u_div/clk_out 这个引脚上,有一个时钟叫 clk_div2,它是从 clk_in 分频 2 倍得到的。

我曾经遇到过一个坑:生成时钟的源路径没指定对。工具默认会找最近的时钟源,但有时候它会找到别的地方去。结果时序分析全乱套了。所以我建议,-source 参数一定要写清楚,别偷懒。

注意: 生成时钟的源必须是已经定义好的时钟。你不能在一个生成时钟上再生成另一个时钟,除非你很清楚自己在做什么。这种级联生成时钟,很容易让工具的分析变得复杂,也容易出错。

还有一种情况是门控时钟。比如你用 AND 门把时钟和使能信号合在一起。这时候也要用生成时钟来描述:

create_generated_clock -name gclk -source [get_pins u_and/A] -combinational [get_pins u_and/Y]

这里的 -combinational 表示这个生成时钟是组合逻辑产生的,没有分频或倍频。

2.3 输入延迟(set_input_delay)

输入延迟描述的是:数据从芯片外部到达内部寄存器之前,已经花了多少时间。说白了,就是外部器件把数据送出来,经过 PCB 走线,再到芯片引脚,这中间的时间。

set_input_delay -clock clk -max 2.5 [get_ports data_in]

这条命令的意思是:在 clk 时钟域下,数据 data_in 到达芯片引脚时,相对于时钟沿已经过去了 2.5ns。这个 2.5ns 是最大延迟。

你可能会问:为什么要分最大和最小?因为芯片内部建立时间检查要用最大延迟,保持时间检查要用最小延迟。这是 STA 的基本逻辑。

核心要点: 输入延迟的值 = 外部器件的时钟到输出延迟(Tco)+ PCB 走线延迟。如果你不知道具体值,可以留一些裕量,但别太夸张,否则约束太紧会导致工具过度优化,反而影响面积和功耗。

我一般会这样写:

set_input_delay -clock clk -max 3.0 [get_ports {data_in[*]}]
set_input_delay -clock clk -min 1.0 [get_ports {data_in[*]}]

注意这里用了 [*] 表示总线。如果数据位宽很大,这样写省事多了。

2.4 输出延迟(set_output_delay)

输出延迟和输入延迟是对称的。它描述的是:芯片内部的数据从寄存器输出,经过芯片引脚,再到外部器件被采到,这中间需要多少时间。

set_output_delay -clock clk -max 4.0 [get_ports data_out]

这条命令的意思是:数据 data_out 从芯片引脚输出后,外部器件会在 4.0ns 之后才采样它。所以芯片内部必须在时钟沿之后的 4.0ns 内把数据送到引脚。

嗯,这里有个容易混淆的地方:输出延迟是相对于外部器件的建立时间要求的。说白了,外部器件要求数据在时钟沿之前多久稳定,这个时间就是输出延迟的一部分。

我的经验: 输出延迟的值 = 外部器件的建立时间(Tsu)+ PCB 走线延迟。如果你用的是标准接口,比如 SPI、I2C,这些值通常在 datasheet 里写得清清楚楚。别自己瞎猜,去查手册。

举个例子,假设外部器件的建立时间是 2ns,保持时间是 1ns,PCB 走线延迟是 0.5ns,那么:

set_output_delay -clock clk -max 2.5 [get_ports data_out]
set_output_delay -clock clk -min -0.5 [get_ports data_out]

注意这里的 -min 是负值。为什么?因为保持时间要求数据在时钟沿之后还要稳定一段时间。如果外部器件的保持时间是 1ns,PCB 走线是 0.5ns,那么芯片内部的数据必须在时钟沿之后至少 0.5ns 内保持不变。这个 0.5ns 就是输出延迟的最小值,但它是相对于时钟沿之后的,所以用负值表示。

警告: 输出延迟的 min 值写成负值,是很多新手容易搞错的地方。如果你不确定,可以先用正数试试,然后看时序报告里的 slack 是不是合理。如果 slack 是负的,再检查约束。

2.5 知识体系总览

说了这么多,咱们用一张图来总结一下这四个约束的关系。我画了个简单的流程图,帮你理清思路:

时序约束四大核心命令 create_clock 定义主时钟 create_generated_clock 定义衍生时钟 set_input_delay 定义输入路径延迟 外部→芯片内部 set_output_delay 定义输出路径延迟 芯片内部→外部 时序约束是 STA 的基石 时钟定义准确 → 输入输出延迟合理 → 时序分析可信

这张图很直观:先有主时钟,再有生成时钟。然后输入延迟和输出延迟分别描述数据进出芯片的时序关系。这四个命令搞定了,你的时序约束框架就搭起来了。

最后说一句:约束不是写完了就完事的。我每次写完约束,都会跑一遍 report_timing 看看有没有 unexpected 的路径。如果发现有些路径没被约束到,或者约束得太松,赶紧回头改。别等到后端跑完了才发现问题,那时候改起来成本就高了。

可可精品资料库