2、时序约束文件(SDC)编写:create_clock、set_input_delay、set_output_delay、set_max_delay
时序约束文件,也就是我们常说的 SDC,是连接 RTL 设计与物理实现的桥梁。说白了,它就是告诉工具:「我这个芯片要在什么频率下工作,外部信号什么时候来,什么时候走。」
我刚开始做后端时,总觉得 SDC 是前端的事。直到有一次,因为少写了一条 set_output_delay,导致芯片接口时序全乱套,板级调试折腾了两周。嗯,从那以后,我对 SDC 的敬畏心就上来了。
2.1 create_clock:定义芯片的心脏
create_clock 是所有约束的起点。没有时钟,STA 就无从谈起。它定义了时钟的周期、占空比和波形。
最基本的写法是这样的:
create_clock -name clk -period 10 [get_ports clk]
这条命令的意思是:在端口 clk 上创建一个名为 clk 的时钟,周期 10ns,也就是 100MHz。默认占空比是 50%,上升沿在 0ns,下降沿在 5ns。
我个人习惯把 -name 和端口名保持一致,这样后续引用时不容易搞混。你想想看,如果时钟名叫 sys_clk,端口叫 clk_in,时间久了你自己都可能忘记对应关系。
关键参数说明:
- -period:时钟周期,单位 ns。100MHz 就是 10ns,1GHz 就是 1ns。
- -waveform:自定义波形,格式为 {rise_time fall_time}。默认是 {0 period/2}。
- -name:时钟名称,后续约束中引用。
如果时钟有分频或倍频关系,可以用 -source 选项指定源时钟。比如 PLL 输出的时钟:
create_clock -name pll_clk -period 5 -source [get_ports clk] [get_pins pll/clk_out]
这里 -source 告诉工具:pll_clk 是从 clk 衍生出来的,它们之间有相位关系。
注意: 我曾经遇到过一个案例,工程师把 -source 写成了 -master(老版本语法),结果工具直接报错。现在统一用 -source,别再踩这个坑了。
2.2 set_input_delay:告诉工具数据什么时候来
芯片不是孤岛。它要和外部器件通信,比如 FPGA、DDR 颗粒、ADC 等。set_input_delay 就是告诉工具:外部数据相对于时钟边沿,最早和最晚什么时候到达芯片引脚。
基本语法:
set_input_delay -clock clk -max 2.0 [get_ports data_in]
set_input_delay -clock clk -min 0.5 [get_ports data_in]
这里 -max 表示最大延迟(最晚到达),-min 表示最小延迟(最早到达)。工具会用这两个值来检查建立时间和保持时间。
我个人习惯把 -max 和 -min 分开写,这样可读性更好。有些人喜欢写在一行里:
set_input_delay -clock clk -max 2.0 -min 0.5 [get_ports data_in]
两种写法都行,看团队规范。我建议统一风格,别今天这样明天那样。
经验之谈: set_input_delay 的值怎么算?它等于外部器件的 t_co(时钟到输出延迟)加上 PCB 走线延迟。比如 FPGA 的 t_co 是 1.5ns,PCB 走线 0.5ns,那 -max 就是 2.0ns。别拍脑袋写,一定要看外部器件的 datasheet。
还有一个容易忽略的点:-clock_fall 选项。如果数据是在时钟下降沿采样的,需要加上这个:
set_input_delay -clock clk -clock_fall -max 2.0 [get_ports data_in]
我曾经见过一个项目,DDR 接口的输入延迟没加 -clock_fall,结果工具默认用上升沿分析,时序报告全是红的。排查了两天才找到原因。
2.3 set_output_delay:告诉工具数据什么时候走
和输入延迟对应,set_output_delay 定义的是芯片输出数据到外部器件的时序要求。它告诉工具:外部器件需要数据在时钟边沿之前多久稳定(建立时间),之后保持多久(保持时间)。
基本语法:
set_output_delay -clock clk -max 3.0 [get_ports data_out]
set_output_delay -clock clk -min 1.0 [get_ports data_out]
这里 -max 对应外部器件的建立时间要求,-min 对应保持时间要求。注意,这个值是「外部器件的要求」,不是芯片内部的延迟。
理解关键: set_output_delay -max 越大,留给芯片内部路径的时间就越少。因为工具会计算:时钟周期 - set_output_delay - 内部路径延迟 ≥ 0。所以输出延迟设得太大,内部时序就很难收敛。
我遇到过一位工程师,把 set_output_delay 设成了 9ns(周期 10ns),结果内部路径只剩 1ns 可用。他抱怨工具太差,连 100MHz 都跑不了。其实是他自己把约束写死了。
和输入一样,输出也有 -clock_fall 选项:
set_output_delay -clock clk -clock_fall -max 2.5 [get_ports data_out]
另外,-reference_pin 选项可以指定参考时钟的引脚,用于源同步接口。这个比较高级,新手可以先跳过。
2.4 set_max_delay:给路径上「紧箍咒」
set_max_delay 是一个「例外约束」。它用来覆盖默认的时钟周期约束,对特定路径设置更严格的延迟要求。
什么时候用?举个例子:
- 异步时钟域之间的路径,默认不检查,但你想加一个上限。
- 某些关键路径,虽然时钟周期允许,但你希望它跑得更快。
- 复位信号的释放路径,需要保证足够短。
基本语法:
set_max_delay 5.0 -from [get_pins reg_a/CK] -to [get_pins reg_b/D]
这条命令的意思是:从 reg_a 的时钟引脚到 reg_b 的数据引脚,最大延迟不能超过 5ns。
注意: set_max_delay 会覆盖时钟周期约束。如果你设了 set_max_delay 2.0,但时钟周期是 10ns,工具会按 2ns 来检查。所以别乱用,否则可能过度约束,导致面积和功耗爆炸。
我个人的使用习惯是:只在以下三种场景用 set_max_delay:
- 异步 CDC 路径:配合
set_false_path一起用,先设 false_path,再加一个宽松的 max_delay 做兜底。 - 复位网络:保证复位信号在几个周期内到达所有寄存器。
- 调试接口:比如 JTAG,频率不高但路径很长,加个约束防止工具乱优化。
还有一个兄弟命令 set_min_delay,用来设置最小延迟。比如某些路径需要保证一定的延迟才能满足保持时间:
set_min_delay 0.5 -from [get_pins reg_a/CK] -to [get_pins reg_b/D]
不过 set_min_delay 用得少,大部分保持时间问题靠 set_output_delay -min 和时钟偏斜来解决。
2.5 知识体系总览
下面这张图总结了 SDC 中四个核心命令的关系和作用范围:
这张图你看懂了吗?create_clock 是核心,定义了芯片的工作节奏。set_input_delay 和 set_output_delay 是边界约束,连接芯片内外。set_max_delay 是例外约束,用来处理特殊路径。
2.6 实战建议:SDC 编写的「三要三不要」
最后,分享几条我这些年总结的经验:
| 原则 | 要这样做 | 不要那样做 |
|---|---|---|
| 时钟定义 | 每个时钟都加 -name,和端口名区分开 |
依赖工具自动命名,后续引用容易出错 |
| IO 延迟 | 从 datasheet 提取参数,留 10%-20% 余量 | 拍脑袋写,或者直接抄其他项目的值 |
| 例外约束 | 先加注释说明为什么用这个约束 | 不加注释,三个月后自己都看不懂 |
避坑指南: 我曾经接手过一个项目,SDC 里写了 50 多条 set_max_delay,但没有任何注释。我花了整整一周才搞清楚每条约束的用途。从那以后,我要求团队在每条例外约束前加一行注释,写明「为什么」和「谁加的」。
嗯,SDC 编写就是这样——看似简单,实则细节很多。多花点时间把约束写清楚,后面 STA 分析能省一半的精力。