3. 时序约束入门:SDC基本语法、创建时钟与生成时钟、输入输出延迟约束

各位同学,咱们今天聊聊时序约束。说实话,我刚入行那会儿,觉得这玩意儿就是个形式主义——把代码写好不就行了?直到有一次,我设计的接口在实验室里怎么都跑不稳,示波器上一看,数据采样点刚好卡在信号跳变的边缘上。嗯,从那以后,我再也不敢轻视时序约束了。

说白了,时序约束就是告诉工具:你的芯片要在多快的时钟下工作,数据什么时候来,什么时候走。没有约束,工具就像蒙着眼睛开车——能跑,但不知道往哪儿跑。

核心观点:时序约束不是给工具看的,是给你自己看的。它强迫你思考每一个信号的时序关系。

3.1 SDC基本语法——你得会说话

SDC(Synopsys Design Constraints)是业界标准。我习惯把所有约束写在一个文件里,叫 top.sdc。它的语法其实很简单,就是 命令 + 对象 + 参数 的结构。

先看几个最常用的命令:

# 这是注释,用井号开头
current_design top          # 指定当前设计
set_units -time ns          # 设置时间单位,我喜欢用纳秒

你可能会问:为什么要设单位?因为不同工具默认单位不一样。我曾经吃过这个亏——在工具A里写了个 2.5,以为是纳秒,结果工具B当成了皮秒处理,整个时序全乱了。

3.2 创建时钟——一切时序的起点

时钟是时序分析的基准。没有时钟,工具根本不知道什么时候该采样数据。

创建时钟用 create_clock 命令:

create_clock -name clk_sys -period 10.0 [get_ports clk_in]

这条命令的意思是:创建一个叫 clk_sys 的时钟,周期10纳秒(也就是100MHz),它来自顶层端口 clk_in

但实际项目中,时钟往往不是这么简单的。比如PLL产生的时钟:

create_clock -name clk_pll -period 5.0 [get_pins pll_instance/clk_out]

这里我指定了PLL的输出引脚。为什么要这么做?因为工具需要知道时钟的物理来源,才能准确计算延迟。

我的习惯:给每个时钟起一个有意义的名字,比如 clk_ddrclk_pcie。别用 clk1clk2 这种,否则三个月后你自己都看不懂。

3.3 生成时钟——分频、倍频那些事

生成时钟(Generated Clock)是 create_generated_clock 命令的活儿。它用来描述从主时钟派生出来的时钟,比如分频时钟、门控时钟。

举个例子,一个简单的二分频:

create_generated_clock -name clk_div2 -source [get_ports clk_in] \
    -divide_by 2 [get_pins divider_reg/Q]

这里要注意:-source 指定的是源时钟的端口或引脚,而不是源时钟的名字。我见过有人写成 -source [get_clocks clk_sys],这是错的——工具需要的是物理节点,不是逻辑名字。

再来看一个更复杂的场景:

create_generated_clock -name clk_90 -source [get_pins pll/clk_out] \
    -multiply_by 1 -divide_by 1 -phase 90 [get_pins dll/clk_shifted]

这个生成时钟做了90度相移。我在做DDR接口时经常用到——数据在时钟的上升沿和下降沿都采样,相位关系必须精确。

避坑指南:我曾经在一个项目中,把生成时钟的源指定错了,导致工具认为两个时钟是异步的,所有跨时钟域路径都没检查。结果流片回来,芯片在高温下频繁出错。后来花了整整两周才定位到问题——就是那个 -source 写错了。

3.4 输入输出延迟约束——把芯片放到系统中看

芯片不是孤立存在的。它要跟外部器件通信——比如FPGA、DDR颗粒、ADC芯片。这时候就需要输入输出延迟约束。

3.4.1 输入延迟

输入延迟描述的是:数据在时钟沿之后多久到达芯片引脚

set_input_delay -clock clk_sys -max 2.5 [get_ports data_in]
set_input_delay -clock clk_sys -min 1.0 [get_ports data_in]

-max-min 分别对应最坏情况和最好情况。为什么要有两个值?因为外部器件的输出延迟会有变化——温度、电压、工艺偏差都会影响。

我一般这样估算输入延迟:

  • 最大延迟 = 外部器件最大输出延迟 + 板级走线最大延迟
  • 最小延迟 = 外部器件最小输出延迟 + 板级走线最小延迟

3.4.2 输出延迟

输出延迟描述的是:芯片内部数据需要在时钟沿之前多久准备好

set_output_delay -clock clk_sys -max 3.0 [get_ports data_out]
set_output_delay -clock clk_sys -min 0.5 [get_ports data_out]

这里有个容易混淆的地方:-max 对应的是建立时间要求,-min 对应的是保持时间要求。说白了,-max 越大,留给芯片内部的时间就越少,时序越难满足。

实战经验:我在做MIPI接口时,输出延迟约束特别严格——因为MIPI的D-PHY要求数据在时钟沿前后各0.5ns内稳定。当时我反复调整了三次约束值,才找到最优解。记住:约束不是越紧越好,太紧会导致工具过度优化,面积和功耗都会增加。

3.5 知识体系总览

说了这么多,咱们用一张图来梳理一下本章的核心逻辑:

时序约束知识体系 SDC时序约束 基本语法 创建时钟 生成时钟 输入输出延迟 命令+对象+参数 周期、占空比、相位 分频、倍频、相移 max/min、建立/保持 注释 派生 外部接口

这张图把咱们今天讲的内容串起来了。你看,SDC约束的核心就是四个部分:基本语法是工具,创建时钟和生成时钟是基础,输入输出延迟是应用。缺一个都不行。

3.6 本章小结

嗯,今天的内容就这些。总结一下重点:

  • SDC语法:记住 命令 对象 参数 的结构,注释用 #
  • 创建时钟:用 create_clock,指定物理节点和周期
  • 生成时钟:用 create_generated_clock,注意 -source 是物理节点
  • IO延迟:用 set_input_delayset_output_delay,区分 -max-min

最后说一句:约束文件写完后,一定要跑一遍 report_timing 看看结果。我见过太多人写完约束就扔那儿不管了,结果综合出来的电路根本不能用。记住,约束是活的,需要反复迭代优化。

小技巧:刚开始写约束时,可以先用 report_clock 检查时钟是否创建正确。这一步能避免很多低级错误。


公众号:蓝海资料掘金营,微信deep3321