3. 约束文件入门:SDC约束文件结构、时钟约束、输入输出延迟约束
说到时序约束,很多刚入行的朋友会觉得这东西很玄乎。其实说白了,SDC(Synopsys Design Constraints)就是一份给工具看的“说明书”。你告诉工具:我的时钟长什么样,数据什么时候来,什么时候走。工具才能帮你算得准。
我个人习惯,写约束文件之前,先画一张图。把芯片的边界、时钟的源头、数据的流向都标清楚。这样写出来的约束才靠谱。不信你试试,直接上手写,十有八九要返工。
核心要点:SDC约束是STA(静态时序分析)的输入。约束写错了,分析结果就是错的。流片回来发现芯片跑不动,多半是约束没写对。
3.1 SDC文件长什么样?
SDC文件其实就是一堆命令的集合。每一行是一条命令,工具按顺序执行。我见过有人把几百行约束堆在一个文件里,找起来特别费劲。我建议按功能分块,加注释,这样后期维护会轻松很多。
一个典型的SDC文件结构是这样的:
# ============================================
# 时钟约束
# ============================================
create_clock -name clk_sys -period 10 [get_ports clk]
create_generated_clock -name clk_div2 -divide_by 2 -source [get_ports clk] [get_pins u_div/clk_out]
# ============================================
# 输入延迟约束
# ============================================
set_input_delay -clock clk_sys -max 5.0 [get_ports data_in]
set_input_delay -clock clk_sys -min 2.0 [get_ports data_in]
# ============================================
# 输出延迟约束
# ============================================
set_output_delay -clock clk_sys -max 6.0 [get_ports data_out]
set_output_delay -clock clk_sys -min 1.5 [get_ports data_out]
# ============================================
# 伪路径与多周期路径
# ============================================
set_false_path -from [get_clocks clk_sys] -to [get_clocks clk_div2]
set_multicycle_path -setup 2 -from [get_pins u_reg_a/Q] -to [get_pins u_reg_b/D]
你看,注释用 # 开头。命令用 get_ports、get_pins、get_clocks 来指定对象。这些对象名要和网表里的名字完全一致,大小写都不能错。我曾经因为一个端口名的大小写问题,查了整整两天。
3.2 时钟约束——STA的基石
时钟是时序分析的心脏。没有时钟,STA就没法做。时钟约束主要做三件事:定义主时钟、定义生成时钟、设置时钟的不确定性。
3.2.1 主时钟(Primary Clock)
主时钟是从芯片的输入端口进来的时钟。用 create_clock 命令定义。最基本的写法:
create_clock -name sys_clk -period 10.0 [get_ports clk_in]
这里 -period 10.0 表示周期是10ns,也就是频率100MHz。你想想看,如果芯片实际跑的是200MHz,你约束写100MHz,那工具只会帮你检查到100MHz的时序。流片回来一跑200MHz,直接就崩了。
有时候时钟还有占空比要求,比如40%占空比:
create_clock -name sys_clk -period 10.0 -waveform {0 4} [get_ports clk_in]
-waveform {0 4} 表示上升沿在0ns,下降沿在4ns。高电平持续4ns,低电平6ns,占空比40%。
3.2.2 生成时钟(Generated Clock)
芯片内部经常会有分频、倍频、门控时钟。这些时钟要用 create_generated_clock 来定义。举个例子:
create_generated_clock -name clk_div2 -divide_by 2 -source [get_ports clk_in] [get_pins u_div/clk_out]
这里 -source 指定源时钟,-divide_by 2 表示二分频。生成时钟的源必须是已经定义好的时钟。我见过有人把源写成了内部节点,结果工具报错说找不到时钟源。
小技巧:生成时钟的名字最好能体现它的来源和分频关系。比如 clk_sys_div2,一看就知道是从系统时钟二分频来的。别用 clk1、clk2 这种名字,三个月后你自己都看不懂。
3.2.3 时钟不确定性(Clock Uncertainty)
时钟不是完美的。有抖动(jitter)、有偏斜(skew)。这些都要通过 set_clock_uncertainty 来告诉工具:
set_clock_uncertainty -setup 0.2 [get_clocks sys_clk]
set_clock_uncertainty -hold 0.1 [get_clocks sys_clk]
setup uncertainty 通常比 hold uncertainty 大。因为setup受时钟抖动影响大,hold受时钟偏斜影响大。这个值怎么定?我一般参考芯片的PLL数据手册,再留点余量。
3.3 输入延迟约束——告诉工具数据从哪来
芯片的输入端口,数据是从外部器件来的。外部器件有自己的时钟和输出延迟。我们需要用 set_input_delay 来模拟这个外部路径。
输入延迟的定义:
set_input_delay -clock clk_sys -max 5.0 [get_ports data_in]
set_input_delay -clock clk_sys -min 2.0 [get_ports data_in]
-max 对应setup分析,-min 对应hold分析。这两个值怎么算?
假设外部器件在时钟上升沿之后,经过2ns到5ns输出数据。那么:
- max input delay = 外部器件的最大输出延迟 + PCB走线延迟 = 5ns + 0ns(假设走线很短)= 5ns
- min input delay = 外部器件的最小输出延迟 + PCB走线延迟 = 2ns + 0ns = 2ns
我曾经遇到过一个案例,客户给的输入延迟约束写反了。max写成了2,min写成了5。结果工具报了很多hold违例,我们查了三天才发现是约束写反了。嗯,从那以后我每次写完约束都会自己手算一遍,确认max比min大。
注意:输入延迟是相对于时钟边沿的。如果数据在时钟上升沿之前就有效,输入延迟可以是负值。比如DDR接口的数据,就是在时钟边沿之前就切换的。
3.4 输出延迟约束——告诉工具数据要去哪
输出延迟和输入延迟是对称的。它描述的是外部器件需要多少时间来采样数据。用 set_output_delay 来定义:
set_output_delay -clock clk_sys -max 6.0 [get_ports data_out]
set_output_delay -clock clk_sys -min 1.5 [get_ports data_out]
输出延迟的计算:
- max output delay = 外部器件的setup时间 + PCB走线延迟 = 5ns + 1ns = 6ns
- min output delay = 外部器件的hold时间 - PCB走线延迟 = 0.5ns + 1ns = 1.5ns
你想想看,输出延迟越大,留给芯片内部的时间就越少。所以max output delay要尽量小,这样芯片内部的时序压力会小一些。
3.5 本章知识体系
下面这张图,是我画的一个SDC约束的知识结构。你可以把它当作一个检查清单,写约束的时候对照着看,不容易漏项。
3.6 避坑指南
写约束文件这么多年,我踩过的坑不少。这里列几个最常见的,你写的时候多留个心眼:
- 时钟名别写错:
create_clock里的-name和get_ports里的端口名要一致。我见过有人把端口名写成了内部节点名,工具直接报错。 - IO延迟的时钟域别搞混:输入延迟和输出延迟都要指定是相对于哪个时钟的。如果芯片有多个时钟域,每个域的IO延迟要分开写。
- max和min别写反:max一定大于min。如果max比min小,工具会报出奇怪的违例,你查半天都查不出来。
- 别忘了加注释:三个月后你回头看自己的约束文件,如果没有注释,你根本想不起来当初为什么这么写。
我的习惯:每写完一个模块的约束,我都会跑一遍 report_timing 看看结果。如果发现setup slack很小或者hold违例很多,我会先检查约束是不是写对了,而不是急着去改电路。很多时候,问题出在约束上,不是电路上。
好了,这一章的内容就到这里。SDC约束入门其实不难,关键是要理解每个命令背后的物理意义。时钟是时序的基准,IO延迟是芯片和外部世界的接口。把这两个搞清楚了,后面的多周期路径、伪路径、时序例外什么的,学起来就快了。
可可精品资料库