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约束的知识结构。你可以把它当作一个检查清单,写约束的时候对照着看,不容易漏项。

SDC约束文件知识结构 SDC约束文件 时钟约束 输入延迟约束 输出延迟约束 主时钟 生成时钟 不确定性 max延迟 min延迟 负延迟 max延迟 min延迟 参考时钟 约束写得好,STA没烦恼 时钟是基础,IO延迟要算准,伪路径别乱设

3.6 避坑指南

写约束文件这么多年,我踩过的坑不少。这里列几个最常见的,你写的时候多留个心眼:

  • 时钟名别写错:create_clock 里的 -name 和 get_ports 里的端口名要一致。我见过有人把端口名写成了内部节点名,工具直接报错。
  • IO延迟的时钟域别搞混:输入延迟和输出延迟都要指定是相对于哪个时钟的。如果芯片有多个时钟域,每个域的IO延迟要分开写。
  • max和min别写反:max一定大于min。如果max比min小,工具会报出奇怪的违例,你查半天都查不出来。
  • 别忘了加注释:三个月后你回头看自己的约束文件,如果没有注释,你根本想不起来当初为什么这么写。

我的习惯:每写完一个模块的约束,我都会跑一遍 report_timing 看看结果。如果发现setup slack很小或者hold违例很多,我会先检查约束是不是写对了,而不是急着去改电路。很多时候,问题出在约束上,不是电路上。

好了,这一章的内容就到这里。SDC约束入门其实不难,关键是要理解每个命令背后的物理意义。时钟是时序的基准,IO延迟是芯片和外部世界的接口。把这两个搞清楚了,后面的多周期路径、伪路径、时序例外什么的,学起来就快了。

可可精品资料库