☰
FPGA时序约束实战:set_input_delay从原理到RGMII接口应用
2026/10/7 5:29:45 网站建设 项目流程

1. 时序约束里最容易被忽视的那条命令

搞FPGA的人都有一个共识:功能仿真过了不代表板子能跑,板子能跑不代表稳定,稳定了不代表换个批次芯片还能稳定。这中间隔着的,就是时序约束。而在所有时序约束命令里,set_input_delay大概是被误用最多、被忽视最严重的一条。

很多人写约束的习惯是:时钟用create_clock定义好,跨时钟域用set_false_path或set_clock_groups隔开,输出用set_output_delay随便给个值,输入嘛……set_input_delay给个 2ns 或者干脆不写,反正综合能过、实现能过、bitstream 能生成,板子跑起来也没报错,那就行了。

这种心态在低速设计里确实能蒙混过关。但一旦你碰到 RGMII、DDR、高速ADC/DAC接口、MIPI这类源同步或系统同步接口,set_input_delay写错或者不写,轻则数据偶尔出错、重则整个链路完全跑不通,而且这种问题在实验室常温下可能看不出来,一到高低温测试或者批量生产就集中爆发。

这篇文章面向的是已经写过一些时序约束、但对set_input_delay始终停留在“照着模板抄”阶段的FPGA开发者。我会从这条命令到底在描述什么物理事实讲起,把max和min的推导过程一步步拆开,然后落到RGMII、源同步ADC、SPI这类真实接口上,给出可以直接复用的约束写法。中间会穿插我在实际项目中踩过的坑和排查思路,尽量让你看完之后不再需要去网上搜“set_input_delay 怎么填”。

2. set_input_delay到底在约束什么

2.1 从物理链路说起:数据到达FPGA引脚的时刻

要理解set_input_delay,得先把目光从FPGA内部移到板级PCB上。一个典型的输入接口是这样的:外部器件(比如PHY芯片、ADC、另一个FPGA)在它的时钟沿把数据推出来,数据经过PCB走线、可能经过连接器、到达FPGA的输入引脚,然后进入FPGA内部的IOB(Input Output Block),再经过布线资源到达触发器的D端。

set_input_delay描述的就是数据到达FPGA引脚时,相对于某个参考时钟沿的延迟。注意关键词:相对于参考时钟沿、到达引脚。它不是描述FPGA内部从引脚到触发器的延迟——那是工具自己会算的;它描述的是FPGA外部的延迟,是工具无法自动获取、必须由你告诉它的信息。

这个“外部延迟”由几部分组成:外部器件的Tco(时钟到输出有效的时间)、PCB走线延迟、连接器延迟、以及任何外部缓冲器的延迟。这些加起来,就是数据相对于时钟沿到达FPGA引脚的时间偏移。

2.2 为什么工具不能自己算出来

有人会问:Vivado不是有时序分析引擎吗,为什么不能自动算?原因很简单:Vivado只知道FPGA内部的事情。它知道从引脚到触发器的布线延迟是多少、知道IOB的建立保持时间是多少,但它完全不知道PCB上走了多长的线、外部芯片的输出延迟是多少。这些信息只存在于原理图、PCB布线报告和外部器件的数据手册里,必须由设计者手动提取并告诉工具。

所以set_input_delay的本质是:把FPGA外部的时序信息翻译成工具能理解的语言,让工具在分析内部建立/保持关系时,把外部延迟也考虑进去。如果你不写这条约束,工具默认外部延迟为零,也就是假设数据在时钟沿的同一时刻到达引脚——这在物理上几乎不可能,所以分析结果自然不可信。

2.3 参考时钟的选择:不是随便找个时钟就行

set_input_delay必须绑定一个参考时钟,这个时钟通常是create_clock创建的。参考时钟的选择有一个原则:它应该是外部器件输出数据时所依据的那个时钟。

在系统同步接口中,这个时钟通常是FPGA输出的、送给外部器件的那个时钟。比如FPGA产生一个100MHz时钟送给ADC作为采样时钟,ADC在这个时钟的沿上输出数据,那么参考时钟就是FPGA内部这个100MHz时钟在输出引脚上的版本。

在源同步接口中,外部器件会随数据一起发送一个随路时钟(比如RGMII的RX_CLK、DDR的DQS),这个随路时钟才是参考时钟。这时候你需要在FPGA的时钟输入引脚上创建时钟,然后用它作为set_input_delay的参考。

选错参考时钟是新手最常见的错误之一。比如RGMII接口,有人拿FPGA内部的GTX_CLK去约束RXD,但RXD是PHY根据RX_CLK发出的,跟GTX_CLK没有固定的相位关系,这样约束出来的结果完全没有意义。

3. max和min的推导:把数据手册和PCB参数翻译成约束值

3.1 建立分析对应max,保持分析对应min

set_input_delay有两个关键参数:-max和-min。它们分别对应两种分析场景:

  • -max:用于建立时间(setup)分析。它描述的是数据到达引脚最晚的情况。数据来得越晚,越容易违反建立时间。
  • -min:用于保持时间(hold)分析。它描述的是数据到达引脚最早的情况。数据来得越早,越容易违反保持时间。

这两个值不是随便给的,它们来自外部器件的时序参数和PCB走线的延迟范围。下面我分别推导。

3.2 max值的计算:最晚到达路径

数据最晚到达引脚的时间,等于外部器件在最坏情况下输出数据的延迟,加上PCB走线的最大延迟。

以外部器件的Tco为例。数据手册通常会给出Tco的最大值(Tco_max)和最小值(Tco_min)。Tco_max对应数据最晚出来的情况,Tco_min对应最早出来的情况。

那么:

input_delay_max = Tco_max + PCB_trace_delay_max input_delay_min = Tco_min + PCB_trace_delay_min

PCB走线延迟怎么算?一般用经验值:表层微带线约 140~180 ps/inch,内层带状线约 160~200 ps/inch。具体取决于板材的介电常数。如果你有PCB布线报告,可以直接拿到走线长度,乘以单位延迟即可。如果没有,可以按最长走线和最短走线的差值来估算。

这里有一个容易忽略的点:PCB走线延迟的max和min不是走线长度的max和min,而是同一根走线在不同工艺角、不同温度下的延迟变化。不过在大多数设计中,这个变化量相对Tco的变化量来说比较小,可以简化处理。但如果你做的是车规级或者高可靠性设计,这个变化量必须考虑进去。

3.3 一个具体的计算例子

假设你用的是某款以太网PHY,RGMII接口,数据手册给出:

  • Tco_max = 2.5 ns
  • Tco_min = 1.5 ns
  • PCB走线长度 = 2 inch,表层微带线,单位延迟约 150 ps/inch

那么:

PCB_delay = 2 inch × 150 ps/inch = 300 ps = 0.3 ns input_delay_max = 2.5 + 0.3 = 2.8 ns input_delay_min = 1.5 + 0.3 = 1.8 ns

如果走线长度有偏差,比如最长2.2 inch、最短1.8 inch,那么:

PCB_delay_max = 2.2 × 0.15 = 0.33 ns PCB_delay_min = 1.8 × 0.15 = 0.27 ns input_delay_max = 2.5 + 0.33 = 2.83 ns input_delay_min = 1.5 + 0.27 = 1.77 ns

这就是最终填进set_input_delay的值。

3.4 源同步接口的特殊处理

源同步接口和系统同步接口的区别在于:时钟是随数据一起传输的。这时候set_input_delay的参考时钟是随路时钟,而随路时钟本身也有延迟。

以RGMII为例,PHY输出的RX_CLK和RXD是一起出来的。RX_CLK到达FPGA引脚的延迟,和RXD到达FPGA引脚的延迟,可能不一样。set_input_delay描述的是数据相对于时钟的延迟,所以需要计算的是数据延迟减去时钟延迟。

如果数据走线和时钟走线等长,那么两者延迟相同,set_input_delay就退化成Tco的范围。如果不等长,就需要把走线延迟差也算进去。

这也是为什么RGMII的PCB布线要求数据线和时钟线等长——等长之后,set_input_delay的计算就简单很多,而且时序余量更大。

4. 真实接口的约束实战

4.1 RGMII接口的input delay约束

RGMII是千兆以太网常用的接口,速率125MHz,DDR双边沿采样,实际数据率1000Mbps。它的时序约束是很多人头疼的问题。

RGMII有两种模式:延时模式(delay mode)和非延时模式(non-delay mode)。延时模式下,PHY会在内部对RX_CLK做延迟,使得时钟沿落在数据的中间;非延时模式下,时钟和数据基本对齐,需要FPGA内部做延迟。

以延时模式为例,PHY数据手册通常会给出RX_CLK和RXD之间的偏斜(skew)范围。假设:

  • RX_CLK相对于RXD的偏斜:典型值 1.5 ns,范围 1.0~2.0 ns
  • PCB走线等长,延迟差忽略

那么:

input_delay_max = 2.0 ns input_delay_min = 1.0 ns

约束写法:

create_clock -name rx_clk -period 8.0 [get_ports rx_clk] set_input_delay -clock rx_clk -max 2.0 [get_ports {rxd[*]}] set_input_delay -clock rx_clk -min 1.0 [get_ports {rxd[*]}] # DDR双边沿,需要分别约束上升沿和下降沿 set_input_delay -clock rx_clk -max 2.0 [get_ports {rxd[*]}] -clock_fall -add_delay set_input_delay -clock rx_clk -min 1.0 [get_ports {rxd[*]}] -clock_fall -add_delay

注意-clock_fall和-add_delay的使用。DDR接口在时钟的上升沿和下降沿都采样数据,所以需要对两个沿分别约束。-add_delay表示在已有约束的基础上追加,而不是覆盖。

提示:RGMII的约束值一定要以PHY数据手册为准。不同厂商的PHY,偏斜范围可能差很多。我见过有人直接抄网上的约束值,结果换了一款PHY就完全跑不通。

4.2 源同步ADC接口的约束

高速ADC通常采用源同步接口,ADC输出数据的同时输出一个随路时钟(DCO或DCLK)。以一款125MSPS的ADC为例:

  • 数据手册给出:Tco_max = 2.8 ns,Tco_min = 1.8 ns(相对于DCO)
  • DCO和数据走线等长

约束写法:

create_clock -name adc_dco -period 8.0 [get_ports adc_dco] set_input_delay -clock adc_dco -max 2.8 [get_ports {adc_data[*]}] set_input_delay -clock adc_dco -min 1.8 [get_ports {adc_data[*]}]

如果是DDR接口,同样需要加-clock_fall和-add_delay。

这里有一个细节:ADC的DCO可能是差分信号,进入FPGA后经过IBUFDS转换成单端时钟。create_clock应该创建在IBUFDS的输出上,而不是输入引脚上。因为时序分析是从IBUFDS输出开始的,输入引脚到IBUFDS输出之间的延迟由工具自动计算。

4.3 SPI接口的约束:低速接口也不能随便写

SPI通常被认为是低速接口,很多人不写约束也能跑。但如果你用的是高速SPI(比如50MHz以上),或者MCU的SPI时序比较紧,不写约束就可能出问题。

SPI是系统同步接口,FPGA作为从机时,SCK由主机提供,MOSI和SSB也由主机发出。这时候:

  • 参考时钟:SCK
  • 数据:MOSI、SS
  • 约束值:主机的Tco_max和Tco_min,加上PCB走线延迟

假设主机Tco_max = 5 ns,Tco_min = 2 ns,PCB延迟0.3 ns:

create_clock -name spi_sck -period 20.0 [get_ports spi_sck] set_input_delay -clock spi_sck -max 5.3 [get_ports {spi_mosi spi_ss}] set_input_delay -clock spi_sck -min 2.3 [get_ports {spi_mosi spi_ss}]

SPI的采样沿取决于CPOL和CPHA的设置。如果是在SCK的上升沿采样,那么参考时钟就是SCK的上升沿;如果是在下降沿采样,就需要用-clock_fall。

5. 常见问题与排查技巧

5.1 约束写了但时序报告仍然报错

这是最常见的问题。约束写了,但report_timing仍然显示setup或hold违反。可能的原因有:

问题现象可能原因排查方法
setup违反input_delay_max给大了检查Tco_max和PCB延迟是否算错
hold违反input_delay_min给小了检查Tco_min是否漏算了PCB延迟
约束不生效参考时钟没创建用report_clocks确认时钟存在
约束不生效端口名写错用get_ports确认端口存在
DDR约束不生效漏了-clock_fall检查是否对两个沿都做了约束

5.2 参考时钟选错导致约束无效

我见过一个案例:设计里有一个RGMII接口,开发者用FPGA内部的GTX_CLK作为set_input_delay的参考时钟。综合和实现都过了,时序报告也很漂亮,但板子跑起来丢包严重。

原因很简单:RXD是PHY根据RX_CLK发出的,跟GTX_CLK没有固定的相位关系。用GTX_CLK去约束RXD,相当于假设RXD和GTX_CLK对齐,这个假设在物理上不成立。工具按照错误的假设去分析,结果自然不可信。

正确的做法是用RX_CLK作为参考时钟。如果RX_CLK没有引到FPGA的时钟引脚,那就需要在RXD的某个引脚上创建时钟,或者用set_input_delay的-clock参数指定一个虚拟时钟。

5.3 虚拟时钟的使用场景

有时候外部器件的时钟并没有引到FPGA的时钟引脚上,比如某些ADC只输出数据不输出时钟,时钟是FPGA提供的。这时候就需要创建一个虚拟时钟(virtual clock)。

虚拟时钟的创建方式:

create_clock -name virt_clk -period 8.0

注意没有[get_ports ...],因为它不对应任何物理引脚。然后set_input_delay就可以用这个虚拟时钟作为参考:

set_input_delay -clock virt_clk -max 2.8 [get_ports {adc_data[*]}] set_input_delay -clock virt_clk -min 1.8 [get_ports {adc_data[*]}]

虚拟时钟的周期应该和外部器件实际使用的时钟周期一致。如果外部器件的时钟是FPGA提供的,那么虚拟时钟的周期就是FPGA输出时钟的周期。

5.4 多die FPGA的input delay约束

多die FPGA(比如某些大容量的器件)的时序约束有一个特殊之处:跨die的布线延迟比较大,而且工具可能不会自动把跨die延迟算进去。这时候set_input_delay的值可能需要额外加上跨die的延迟预算。

具体加多少,取决于器件的die间互连延迟。一般来说,工具会在时序报告中显示跨die路径的延迟,你可以根据报告来调整约束值。如果工具没有自动处理,就需要手动在set_input_delay中加上这部分延迟。

5.5 约束值的微调策略

理论计算出来的set_input_delay值是一个起点,实际项目中往往需要微调。我的经验是:

  • 如果时序余量很大(比如setup余量超过1ns),可以适当收紧约束值,给PCB走线偏差留更多余量。
  • 如果时序余量很小(比如小于0.2ns),需要检查是否所有延迟都算进去了,特别是连接器延迟和外部缓冲器延迟。
  • 如果hold违反,优先检查input_delay_min是否给小了。hold违反通常是因为数据来得太早,而input_delay_min描述的就是数据最早到达的时间。

注意:不要为了让时序报告好看而随意放宽约束值。约束值必须反映真实的物理情况,否则板子跑起来会出问题。时序报告过了但板子不工作,比时序报告不过更可怕。

6. 从约束到板级验证的闭环

6.1 约束写完只是开始

set_input_delay写完、综合实现通过、bitstream生成,这只是完成了第一步。真正的验证在板子上。

我的习惯是:在板级验证阶段,用ILA(Integrated Logic Analyzer)抓取输入数据,观察数据是否稳定。具体做法是:在输入数据进入FPGA后的第一个触发器上挂ILA,设置触发条件为数据变化,然后观察采样到的数据是否正确。

如果ILA抓到的数据偶尔出错,但时序报告显示余量充足,那就要怀疑约束值是否准确。这时候可以尝试调整set_input_delay的值,重新综合实现,再看ILA的结果。

6.2 高低温测试中的时序余量

实验室常温下跑通不代表高低温下也能跑通。温度变化会影响PCB走线延迟和外部器件的Tco。如果时序余量本来就不大,高低温下就可能出问题。

我的做法是:在时序报告中关注最差路径的余量,如果setup余量小于0.5ns,就要考虑在高低温测试中重点观察这个接口。如果条件允许,可以在高低温箱中跑长时间压力测试,统计误码率。

6.3 批量生产中的一致性

实验室样机跑通不代表批量生产也能跑通。不同批次的PCB、不同批次的PHY芯片,延迟特性可能有差异。如果set_input_delay的值卡得很紧,批量生产时可能出现部分板子跑不通的情况。

应对策略是:在约束值中留足够的余量,同时在PCB布线时严格控制走线长度和阻抗。如果接口速率很高,可以考虑在FPGA内部加入IDELAY或IDELAYCTRL,动态调整输入延迟,补偿批次差异。

7. 几个容易踩的坑和我的处理习惯

第一个坑是把Tco和PCB延迟的单位搞混。数据手册的Tco通常以ns为单位,PCB延迟可能以ps为单位,计算时一定要统一。我见过有人把300ps当成300ns加进去,结果约束值大了1000倍,时序报告一片红。

第二个坑是忽略时钟走线延迟。在源同步接口中,如果时钟走线和数据走线不等长,set_input_delay的值需要减去时钟走线延迟。很多人只算数据走线延迟,忘了时钟也有延迟,导致约束值偏大。

第三个坑是DDR接口漏了下降沿约束。DDR接口在时钟的上升沿和下降沿都采样数据,如果只约束了上升沿,下降沿的时序就不受约束,工具可能不会报错,但板子跑起来会出错。

第四个坑是参考时钟的周期写错。create_clock的周期必须和实际时钟周期一致。如果外部时钟是125MHz,周期是8ns,写成10ns就会导致时序分析基于错误的周期,结果完全不可信。

我自己的处理习惯是:每次写完set_input_delay,都会用report_timing检查一遍,确认约束生效、参考时钟正确、max和min都参与了分析。然后会在约束文件里加注释,写明每个值的来源(数据手册页码、PCB走线长度),方便以后复查。

8. 约束文件的组织与维护

8.1 按接口分文件管理

一个大型FPGA项目可能有几十个接口,如果把所有约束写在一个文件里,维护起来非常痛苦。我的做法是按接口分文件,比如rgmii_constraints.xdc、adc_constraints.xdc、ddr_constraints.xdc,然后在主约束文件中用source命令包含进来。

这样做的另一个好处是:不同项目之间可以复用约束文件。比如RGMII接口的约束,在这个项目里调好了,下个项目如果用的是同一款PHY,直接复制过去就行。

8.2 约束文件的版本管理

约束文件一定要纳入版本管理。我见过有人改了约束没记录,后来板子出问题,排查了半天才发现是约束被改过。每次修改约束,都要在提交信息里写清楚改了什么、为什么改。

如果条件允许,可以在约束文件中加一个修改记录表,记录每次修改的日期、修改人、修改内容和原因。这样即使过了很久,也能追溯约束值的来源。

8.3 约束的自动化检查

对于接口比较多的项目,可以写一个简单的脚本,自动检查约束文件中的常见问题,比如:

  • 是否有端口没有约束
  • 是否有参考时钟没有创建
  • 是否有DDR接口漏了下降沿约束
  • 约束值的单位是否统一

这个脚本可以用Tcl写,在Vivado中运行。虽然不能替代人工检查,但可以过滤掉大部分低级错误。

9. 从set_input_delay延伸出去的几个话题

9.1 set_output_delay的对称性

set_input_delay和set_output_delay在概念上是对称的。set_input_delay描述外部器件到FPGA的延迟,set_output_delay描述FPGA到外部器件的延迟。理解了set_input_delay的推导方法,set_output_delay也就触类旁通了。

区别在于:set_output_delay的参考时钟通常是FPGA输出的时钟,而set_input_delay的参考时钟可能是外部器件提供的随路时钟。另外,set_output_delay的max和min对应的是外部器件的建立和保持要求,而不是Tco。

9.2 时序约束与PCB设计的协同

set_input_delay的值依赖于PCB走线延迟,所以时序约束和PCB设计是紧密相关的。在PCB设计阶段,就应该和硬件工程师沟通好走线长度要求。比如RGMII接口要求数据线和时钟线等长,偏差控制在多少mil以内,这些都需要在PCB设计规则中体现。

如果PCB已经布好了,才发现走线长度不满足要求,那就只能在FPGA内部做补偿,比如用IDELAY调整输入延迟。但IDELAY的调整范围有限,如果偏差太大,可能还是解决不了问题。

9.3 时序约束的文档化

时序约束不是写完就完了,还需要文档化。我通常会在项目文档中专门开一节,记录每个接口的约束值、推导过程、数据手册来源、PCB走线长度。这样即使项目交接给别人,也能快速理解约束值的含义。

文档化的另一个好处是:当接口出问题时,可以快速定位是约束值不对,还是PCB走线有问题,还是外部器件本身的问题。

10. 我个人的几条经验总结

第一条:不要抄网上的约束模板。每个项目的PHY型号、PCB走线、时钟频率都可能不同,抄来的约束值大概率不适用。约束值必须根据数据手册和PCB参数自己算。

第二条:约束值宁紧勿松。约束值给得松,时序报告好看,但板子可能出问题。约束值给得紧,时序报告可能不好看,但板子跑起来更稳。当然,也不能紧到工具无法收敛,需要在两者之间找平衡。

第三条:板级验证是最终标准。时序报告过了不代表板子能跑,板子能跑不代表批量能跑。最终的标准是板级验证和批量测试的结果。

第四条:约束文件要像代码一样管理。版本管理、注释、文档化,一个都不能少。约束文件是设计的一部分,不是临时文件。

第五条:多和硬件工程师沟通。set_input_delay的值依赖于PCB走线延迟和外部器件参数,这些信息只有硬件工程师最清楚。多沟通可以避免很多不必要的返工。

最后再分享一个小技巧:如果你不确定set_input_delay的值是否合理,可以先给一个比较宽松的范围,跑一遍时序分析,看看工具报出来的余量是多少。然后根据余量反推约束值是否合理。如果余量大得离谱,说明约束值可能给松了;如果余量很小甚至为负,说明约束值可能给紧了。这个方法虽然不精确,但可以快速判断约束值的方向是否正确。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询