输入延迟约束:set_input_delay 的核心原理
做时序分析,说白了就是跟时间赛跑。而输入延迟约束,就是给这场赛跑定个起跑线。
我刚开始做芯片设计那会儿,总觉得输入延迟约束是个可有可无的东西。直到有一次,一块板子调了整整两周,死活跑不上预期频率。最后发现,问题出在输入路径上——我根本没告诉工具,数据从片外进来到底花了多长时间。从那以后,我再也不敢轻视 set_input_delay 了。
什么是输入延迟?
输入延迟,指的是数据从片外(比如另一个芯片、FPGA、或者传感器)发出,到到达我们芯片内部寄存器的 D 端,这段时间的总和。
你想想看,时钟从 PLL 出来,经过时钟树,到达寄存器 CK 端,这中间有延迟。数据从片外进来,经过 PCB 走线、IO pad、组合逻辑,到达寄存器 D 端,这中间也有延迟。我们要做的,就是告诉工具:数据相对于时钟,到底早到了还是晚到了。
核心公式:
数据到达时间 = 片外时钟延迟 + PCB 走线延迟 + 芯片 IO 延迟 + 内部组合逻辑延迟
而 set_input_delay,就是用来描述这个「数据到达时间」与「时钟沿」之间的相对关系。
set_input_delay 的语法与参数
先看一个典型的约束写法:
# 设置输入延迟,相对于时钟 clk 的上升沿
set_input_delay -clock clk -max 2.5 [get_ports data_in]
set_input_delay -clock clk -min 1.0 [get_ports data_in]
# 如果数据在时钟下降沿被采样
set_input_delay -clock clk -clock_fall -max 2.5 [get_ports data_in]
# 如果数据是源同步时钟(DDR 场景)
set_input_delay -clock clk -max 2.5 -source_latency_included [get_ports data_in]
这里有几个关键参数,我一个个说:
| 参数 | 含义 | 我的经验 |
|---|---|---|
| -clock | 指定参考时钟 | 这个时钟必须是已经创建好的,别写错了名字 |
| -max | 最大输入延迟(建立时间分析用) | 通常对应最差工艺角、最慢温度 |
| -min | 最小输入延迟(保持时间分析用) | 通常对应最好工艺角、最快温度 |
| -clock_fall | 数据相对于时钟下降沿 | DDR 接口常用,别漏了 |
| -add_delay | 对同一个端口添加多组延迟 | 多时钟域场景必备 |
最大输入延迟 vs 最小输入延迟
这两个概念,我见过太多人搞混了。其实很简单:
- -max:数据最晚到达的时间。工具用它来检查建立时间(setup)。
- -min:数据最早到达的时间。工具用它来检查保持时间(hold)。
为什么会这样?你想想看:
- 建立时间检查,关心的是数据能不能在时钟沿之前稳定下来。所以我们要告诉工具「数据最晚可能什么时候到」。
- 保持时间检查,关心的是数据在时钟沿之后还能不能保持住。所以我们要告诉工具「数据最早可能什么时候到」。
我曾经踩过的坑:
有一次我只设了 -max,没设 -min。结果保持时间违例了,工具完全没报出来。因为没设 -min 时,工具默认最小延迟是 0,但实际上我的数据可能早到 0.5ns。那一次差点导致流片失败,还好在 post-layout 仿真里抓出来了。
教训:-max 和 -min 必须成对出现,缺一不可。
参考时钟的选择
参考时钟,就是用来跟输入数据做对齐的那个时钟。选对了,万事大吉;选错了,满盘皆输。
我个人的习惯是,先问自己三个问题:
- 数据是由哪个时钟驱动的?——如果是片外芯片送过来的数据,那个芯片用什么时钟?
- 数据在哪个时钟沿被采样?——上升沿?下降沿?还是双边沿?
- 时钟和数据之间的相位关系是什么?——中心对齐?边沿对齐?还是不确定?
常见的场景有这几种:
| 场景 | 参考时钟 | 典型延迟值 | 注意事项 |
|---|---|---|---|
| 片外芯片用同一个时钟源 | 系统时钟(同源) | 2~5ns | 需要考虑 PCB 走线延迟 |
| 片外芯片有自己的 PLL | 片外芯片的输出时钟 | 1~3ns | 需要做跨时钟域分析 |
| 源同步接口(DDR) | 随路时钟(strobe) | 0.5~1.5ns | 注意 -clock_fall 和 -source_latency_included |
| 异步输入(无关联时钟) | 虚拟时钟(virtual clock) | 根据接口协议估算 | 需要做同步处理,否则约束没意义 |
一个小技巧:
如果你不确定参考时钟选哪个,可以创建一个虚拟时钟(create_clock -name virtual_clk -period 10 -waveform {0 5}),然后用这个虚拟时钟来做输入延迟约束。这样可以把片外时序和片内时序完全解耦,分析起来更清晰。
输入延迟的典型计算流程
嗯,这里我画了一张图,把整个流程串起来:
实际项目中的计算示例
拿一个实际项目来说。假设我们有一个 DDR 接口,片外芯片的 datasheet 上写着:
- 时钟频率:200MHz(周期 5ns)
- 数据相对于时钟的建立时间:0.8ns
- 数据相对于时钟的保持时间:0.8ns
- PCB 走线延迟:0.3ns(估算)
- 芯片 IO 延迟:0.5ns(来自 IO library)
那么输入延迟怎么算?
# 最大输入延迟(建立时间分析)
# 数据最晚到达 = 时钟周期 - 片外建立时间 + PCB延迟 + IO延迟
set_input_delay -clock clk_ddr -max [expr 5.0 - 0.8 + 0.3 + 0.5] [get_ports ddr_dq]
# 最小输入延迟(保持时间分析)
# 数据最早到达 = 片外保持时间 + PCB延迟 + IO延迟
set_input_delay -clock clk_ddr -min [expr 0.8 + 0.3 + 0.5] [get_ports ddr_dq]
算出来:
- -max = 5.0 - 0.8 + 0.3 + 0.5 = 5.0ns
- -min = 0.8 + 0.3 + 0.5 = 1.6ns
注意:这个计算里,我用了时钟周期减去片外建立时间。为什么?因为片外芯片的建立时间要求,意味着数据必须在时钟沿之前 0.8ns 就稳定。所以数据最晚到达时间,就是时钟沿往前推 0.8ns。再加上 PCB 和 IO 的延迟,就是我们要设的 -max 值。
避坑指南
最后,分享几个我这些年踩过的坑:
- 坑一:忘记考虑时钟抖动(jitter)。输入延迟约束里,时钟抖动通常已经包含在片外芯片的时序参数里了,但如果你用的是自己的 PLL,记得把抖动加进去。
- 坑二:多时钟域场景下,对同一个端口设了多组输入延迟,但忘了加 -add_delay。这样后面的约束会覆盖前面的,导致分析不完整。
- 坑三:源同步接口里,随路时钟和数据之间的 skew 没算进去。我曾经因为这个,导致一个 DDR3 接口在高温下频繁出错。
- 坑四:虚拟时钟的周期设得太宽松。虚拟时钟虽然不真实存在,但它的周期会影响工具对输入路径的约束力度。我建议虚拟时钟的周期设成实际数据速率的 2 倍,这样工具会更严格地约束输入路径。
嗯,输入延迟约束就讲到这里。记住一句话:输入延迟约束,本质上是在告诉工具「数据相对于时钟,到底有多大的不确定性」。你把这个不确定性描述得越准确,工具就能帮你把时序做得越可靠。