输入延迟约束:set_input_delay——约束输入路径的建立与保持

大家好,我是老吴。做时序约束这几年,我见过不少新手在输入延迟这块翻车。说白了,芯片工作就像接力赛——外部器件把数据递到你的芯片引脚,你得在规定时间内接住。这个“规定时间”怎么定义?就是靠 set_input_delay。

我个人习惯把它理解为一个“时间账本”:把外部器件的输出延迟、PCB走线延迟全部算到一起,告诉工具芯片外部已经花了多少时间。这样工具才能算清楚内部还有多少时间余量。

先看基本语法和参数

最简单的写法是这样的:

set_input_delay -clock clk [list 2.0] [get_ports {data_in}]

这条命令的意思:数据 data_in 相对于时钟 clk 的上升沿,到达端口时已经过去了 2.0 ns。嗯,这里要注意——这个延迟是相对于时钟沿的。

实际项目中我们很少只设一个值。因为既要做建立时间分析,也要做保持时间分析:

  • -max:用于建立时间分析(最晚到达路径)
  • -min:用于保持时间分析(最早到达路径)

代码如下:

set_input_delay -clock clk -max 3.5 [get_ports {addr_in}]
set_input_delay -clock clk -min 1.2 [get_ports {addr_in}]

这里 -max 和 -min 是分别指定的。为什么要分开?因为外部器件在不同工艺角下,输出延迟可能差很多。我记得之前做DDR接口时,-min只有0.8ns,-max到了4.2ns,差距大得惊人。

时钟关系:-clock 和 -clock_fall

默认情况下,set_input_delay 使用时钟的上升沿作为参考。如果你要用下降沿采样,加参数 -clock_fall:

set_input_delay -clock clk -clock_fall 1.8 [get_ports {data_in}]

在实际设计中,源同步时钟很常见——时钟和数据一起从外部芯片过来。这时候约束更复杂,你还需要同时约束时钟路径和数据路径的skew。这点后面我们再细说。

加不加 -add_delay 是个坑

当同一个端口被多个时钟驱动时(比如双沿采样),需要有两条 set_input_delay 命令。但工具默认第二条会覆盖第一条。这时候必须加上 -add_delay:

set_input_delay -clock clk_rise -max 2.0 [get_ports {dq}]
set_input_delay -clock clk_fall -max 1.5 [get_ports {dq}] -add_delay

不加 -add_delay?第一条就白写了。我曾经在一个项目里debug了半天,就是漏了这个参数,结果保持时间违规全报错了。

输入延迟约束里的建立时间和保持时间

我们来看核心:set_input_delay 怎么影响内部的建立和保持?

一张表格说清楚:

分析类型 工具计算公式 set_input_delay 取值
内部建立时间检查 时钟周期 - input_delay(max) - 内部组合逻辑延迟 >= 建立时间要求 取 -max 值(最晚到达)
内部保持时间检查 input_delay(min) + 内部组合逻辑延迟 >= 保持时间要求 取 -min 值(最早到达)

你想想看,如果 -max 设得太大,工具就会认为数据到芯片内部已经很晚了,留给内部走线的余量就少,容易报建立时间违规。如果 -min 设得太小,工具认为数据到达太早,又容易报保持时间违规。

个人经验: 一个常见的错误是直接拿外部芯片datasheet里的“最大输出延迟”作为 -max,但忽略了PCB走线延迟。走线延迟有时能到0.5ns甚至更多。我建议把PCB延迟也加上,比如- max = Tco_max + Tpcb_delay。曾经有个DDR3项目,因为少算了0.3ns的PCB走线延迟,仿真怎么都不收敛,害我加班到凌晨三点。

输入延迟的具体计算:一个完整例子

假设上一级芯片(U1)输出数据到我们芯片(U2):

  • U1 的时钟频率 200MHz(周期5ns)
  • U1 的 Tco_max = 2.0ns, Tco_min = 0.5ns
  • PCB走线延迟(时钟 + 数据)约 0.3ns

那么我设置的输入延迟为:

set_input_delay -clock clk -max [expr 2.0 + 0.3] [get_ports {data}]
set_input_delay -clock clk -min [expr 0.5 + 0.3] [get_ports {data}]

结果是 -max = 2.3ns, -min = 0.8ns。

工具检查内部建立时间时,会用 5ns(周期)减去2.3ns,剩2.7ns给内部走线。内部保持时间检查时,用0.8ns作为基准,看看数据会不会变得太快。

注意: 如果你的时钟是内部产生的(比如PLL分频),那么输入延迟约束里的时钟必须是同一个源。我曾经见过有人用不同名字的时钟去约束,结果是工具认为时钟不同步,分析完全乱套。

关于负值输入延迟

有些场景下 input_delay 可以是负数。比如外部时钟比数据晚到,或者外部芯片采用“中心对齐”输出。负值输入延迟表示数据在时钟沿之前就已经到达。工具依然会用同样的公式计算,只是你的松弛度可能变成负数。遇到这种情况别慌,检查一下外部时序关系是不是正确理解了。

SVG图:输入延迟约束的时序账本

set_input_delay 时序分析概念图 输入延迟约束的时序账本 外部器件 Tco + PCB延迟 = 输入延迟 芯片引脚 set_input_delay -max / -min 内部寄存器 建立/保持检查 剩余时间 建立时间: 周期 - input_delay_max - 内部延迟 ≥ 建立要求 保持时间: input_delay_min + 内部延迟 ≥ 保持要求

避坑指南:设置 input_delay 时常见的几个坑

  1. 忘记加 -clock 参数——工具不知道参考哪个时钟,会报错。
  2. -max 和 -min 设反了——工具检查保持时间时用了更大的值,导致内部保持时间余量被高估,流片出来可能触发亚稳态。
  3. 忽略输入路径的时钟 latency——如果外部时钟经过PLL或长走线才到芯片引脚,你的输入延迟计算也要考虑时钟偏斜。我建议用 set_clock_latency 单独设置。
  4. 多时钟域不加 -add_delay——前面说过了,这是一个非常隐蔽的bug。

总结一句话:set_input_delay 就是把外部已经花掉的时间告诉工具。设得太松,内部可能不满足时序;设得太紧,过约束导致面积和功耗浪费。多读外部器件的datasheet,多算PCB走线,这份“时间账本”才能算对。