ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

FPGA时序约束实战:set_input_delay从原理到RGMII应用

FPGA时序约束实战:set_input_delay从原理到RGMII应用 1. 为什么 set_input_delay 值得单独拎出来讲做 FPGA 或者 ASIC 设计的人绕不开时序约束这个话题。很多人刚开始学 Vivado 或者 Quartus 的时候最常干的一件事就是点一下 “Run Implementation”然后看时序报告里那一堆红色的 Slack心里一紧但又不知道从哪下手。时钟周期约束大家基本都会写create_clock嘛照着板子晶振频率填就行。但一到set_input_delay和set_output_delay很多人就开始犯迷糊了——这两个约束到底在约束谁值该填多少填错了会怎样我见过不少项目功能仿真全过上板子就是不稳定数据偶尔错一拍查来查去最后发现是输入延迟约束没写对。也见过有人干脆不写set_input_delay让工具自己去猜结果工具默认按 0 处理综合出来的电路在慢速场景下勉强能用一到高速或者温度变化就崩。所以我觉得有必要把set_input_delay这个东西从头到尾捋一遍不讲虚的就讲它在实际项目里怎么用、为什么这么用、用错了怎么排查。这篇文章适合已经写过一点 Verilog 或者 VHDL、用过 Vivado 或 Quartus 做综合实现、但对时序约束还停留在“能跑就行”阶段的开发者。如果你正在做 RGMII 接口、摄像头 MIPI 接收、或者任何跟外部芯片打交道的同步接口那set_input_delay就是你必须要啃下来的一块硬骨头。我会从基本概念讲起然后落到 Vivado 里的具体操作再结合 RGMII 这个典型场景把计算过程一步步拆开最后分享一些我踩过的坑和排查技巧。2. set_input_delay 到底在描述什么2.1 从“数据到达时间”说起要理解set_input_delay得先搞清楚 FPGA 是怎么看待外部输入信号的。假设你有一个外部芯片它跟 FPGA 之间有一组数据线和一根时钟线。外部芯片在时钟的某个边沿把数据发出来数据经过 PCB 走线传到 FPGA 的引脚然后进入 FPGA 内部的输入寄存器。问题来了FPGA 内部的静态时序分析工具STA在计算建立时间和保持时间的时候它需要知道数据相对于时钟到底提前多少或者滞后多少到达引脚。这个“相对于时钟的到达时间信息”就是set_input_delay要告诉工具的东西。你可以把set_input_delay想象成在跟工具说“嘿外面那个芯片发数据的时候数据不是跟时钟完全对齐的它可能提前 2ns 或者滞后 1.5ns 到达我的引脚你算时序的时候把这个因素考虑进去。”如果你不说工具就默认数据跟时钟完全对齐延迟为 0。在低速场景下这可能没问题因为时钟周期足够长0 到 0 的余量也够。但速度一上去这个假设就站不住脚了。2.2 它约束的是外部路径不是内部路径这里有一个非常容易混淆的点set_input_delay约束的不是 FPGA 内部的走线延迟而是外部器件到 FPGA 引脚这一段路径的延迟。FPGA 内部从引脚到第一级寄存器的延迟工具自己会算不需要你操心。你需要告诉工具的是外部世界的数据相对于时钟是什么关系。所以set_input_delay的值跟外部器件的输出特性比如 tco时钟到输出有效的时间、PCB 走线延迟、以及时钟路径的偏斜都有关系。它本质上是一个“外部路径延迟预算”的声明。你声明的值越准确工具优化内部布局布线的时候就越有针对性你声明的值过于乐观工具可能就不给你好好优化上板子就出问题你声明的值过于悲观工具会过度优化浪费面积和功耗甚至可能布不通。2.3 最大延迟和最小延迟为什么要分开设set_input_delay通常要写两次一次用-max一次用-min。为什么因为建立时间检查关心的是数据最晚什么时候到达保持时间检查关心的是数据最早什么时候到达。这两个场景对应的是不同的 PVT 条件工艺、电压、温度。在慢速 corner 下外部器件的输出延迟最大数据到达 FPGA 引脚最晚这时候要满足建立时间。在快速 corner 下外部器件输出延迟最小数据到达最早这时候要满足保持时间。如果你只写一个值工具就没法同时优化建立和保持。实际项目中我一般会先根据外部器件手册和 PCB 走线估算出最大延迟和最小延迟分别填到-max和-min里。如果手册只给了典型值那就得根据经验加一些余量比如典型值加减 20% 到 30%具体看器件和走线长度。3. Vivado 里怎么写 set_input_delay3.1 基本语法和参数含义在 Vivado 的 XDC 约束文件里set_input_delay的基本写法是这样的set_input_delay -clock [get_clocks clk_name] -max 2.5 [get_ports data_in] set_input_delay -clock [get_clocks clk_name] -min 1.0 [get_ports data_in]这里有几个关键参数需要解释一下。-clock指定的是参考时钟这个时钟必须是已经用create_clock定义过的。-max和-min分别对应建立和保持分析。-clock_fall是可选的如果数据是在时钟下降沿采样就需要加上这个参数。还有一个-add_delay参数当你需要为同一个端口指定多个不同参考时钟的延迟时才会用到一般场景下不需要。端口列表可以用通配符比如[get_ports data_in*]可以一次性约束所有以 data_in 开头的端口。但我不建议在正式项目里大量用通配符因为一旦端口命名有变化约束可能就失效了而且工具不会报错你根本不知道。我一般会显式列出所有端口或者用get_ports配合-filter来精确匹配。3.2 参考时钟的选择陷阱参考时钟选哪个这个问题看起来简单实际上很容易出错。假设你的输入接口有一个随路时钟比如 RGMII 的 RX_CLK这个时钟从外部芯片传过来接到 FPGA 的时钟输入引脚。你首先要用create_clock在这个引脚上定义一个时钟然后再用这个时钟作为set_input_delay的参考。但这里有个细节如果你在 FPGA 内部对这个输入时钟做了 MMCM 或者 PLL 倍频那么set_input_delay应该参考的是输入引脚上的原始时钟而不是倍频后的时钟。因为外部数据是跟着原始时钟一起过来的倍频后的时钟是 FPGA 内部生成的跟外部数据没有直接的相位关系。我见过有人把参考时钟写成倍频后的时钟结果时序报告看起来很美上板子数据全错。还有一种情况是源同步接口时钟和数据一起从外部传来但时钟在 FPGA 内部被用来采样数据。这时候set_input_delay的参考时钟就是那个随路时钟而且通常需要配合set_clock_latency或者set_input_jitter来更精确地描述时钟特性。不过对于大多数中低速接口只写set_input_delay就够了。3.3 一个完整的约束示例假设我们有一个 8 位数据总线data_in[7:0]伴随一个时钟rx_clk时钟频率 125MHz周期 8ns。外部器件在时钟上升沿输出数据tco 最大 2ns最小 1ns。PCB 走线延迟最大 0.5ns最小 0.3ns。那么最大输入延迟 tco_max pcb_max 2 0.5 2.5ns最小输入延迟 tco_min pcb_min 1 0.3 1.3ns对应的 XDC 约束就是create_clock -name rx_clk -period 8.000 [get_ports rx_clk_pin] set_input_delay -clock rx_clk -max 2.5 [get_ports {data_in[*]}] set_input_delay -clock rx_clk -min 1.3 [get_ports {data_in[*]}]注意端口列表的写法{data_in[*]}这种带花括号的写法在 Vivado 里是合法的可以匹配所有位。但如果你用的是 Verilog 的命名方式比如data_in[0]、data_in[1]这样分开的端口名那就得用通配符或者逐个列出。4. RGMII 接口的 set_input_delay 实战计算4.1 RGMII 的时序特点RGMII 是千兆以太网里非常常见的一个接口它的时序有点特殊。在 1000Mbps 模式下RGMII 的时钟是 125MHz但数据在时钟的双边沿都传输所以实际数据率是 125M × 2 250Mbps 每根数据线四根数据线加起来就是 1000Mbps。这就意味着数据在时钟上升沿和下降沿都会变化FPGA 需要在两个边沿都正确采样。RGMII 规范里定义了两种模式一种是延迟模式delay mode一种是非延迟模式non-delay mode。在延迟模式下发送端会把时钟或者数据延迟 1.5ns 到 2ns使得数据和时钟之间有一个固定的相位差方便接收端采样。在非延迟模式下数据和时钟基本对齐接收端需要自己想办法把时钟移相 90 度来采样。大多数 FPGA 的 RGMII 实现会用到 IDELAY 或者 IDELAYCTRL 来调整输入延迟这时候set_input_delay的值就需要跟 IDELAY 的设置配合起来。如果你用了 IDELAY那么set_input_delay描述的是经过 IDELAY 之前的外部延迟工具会根据这个约束来优化从引脚到 IDELAY 再到寄存器的路径。4.2 具体计算过程假设我们用的是 RGMII 延迟模式外部 PHY 芯片在时钟上升沿输出数据tco 最大 1.2ns最小 0.8ns。PCB 走线延迟最大 0.4ns最小 0.2ns。RGMII 时钟周期 8ns但因为是双边沿采样建立时间检查的参考边沿是时钟的上升沿和下降沿所以有效窗口是 4ns。对于上升沿采样的数据最大输入延迟 1.2 0.4 1.6ns最小输入延迟 0.8 0.2 1.0ns对于下降沿采样的数据情况稍微复杂一点。因为数据在下降沿也变化所以下降沿的建立时间检查是以下降沿为参考的。如果外部 PHY 在下降沿输出的 tco 跟上升沿差不多那计算方式类似但要注意-clock_fall参数的使用。在 Vivado 里RGMII 的约束通常会写成这样create_clock -name rgmii_rx_clk -period 8.000 [get_ports rgmii_rxc] set_input_delay -clock rgmii_rx_clk -max 1.6 [get_ports {rgmii_rxd[*]}] set_input_delay -clock rgmii_rx_clk -min 1.0 [get_ports {rgmii_rxd[*]}] set_input_delay -clock rgmii_rx_clk -max 1.6 -clock_fall -add_delay [get_ports {rgmii_rxd[*]}] set_input_delay -clock rgmii_rx_clk -min 1.0 -clock_fall -add_delay [get_ports {rgmii_rxd[*]}]注意这里用了-clock_fall和-add_delay因为 RGMII 的数据在上升沿和下降沿都有效需要为两个边沿分别指定延迟。-add_delay的作用是告诉工具“这个约束是追加的不要覆盖之前的”否则后一条会覆盖前一条。4.3 跟 IDELAY 的配合在实际的 RGMII 接收路径里FPGA 通常会用一个 IDELAY 原语来微调输入数据的延迟使得数据眼图对准时钟采样点。这时候set_input_delay的值应该描述的是IDELAY 之前的延迟也就是外部器件 tco 加上 PCB 走线延迟。IDELAY 本身的延迟由工具自动处理不需要你在set_input_delay里体现。但这里有一个坑如果你在 XDC 里对输入端口做了set_input_delay同时又用了 IDELAYVivado 可能会报一些奇怪的时序违例因为工具在计算从引脚到 IDELAY 的路径时会把set_input_delay的值算进去。我的经验是先不加 IDELAY只写set_input_delay跑一遍实现看看时序报告。如果建立时间或者保持时间有少量违例再考虑加 IDELAY 来微调。不要一上来就把 IDELAY 和set_input_delay一起堆上去那样出了问题很难定位。5. 常见问题与排查技巧实录5.1 时序报告里的输入延迟相关违例怎么看打开 Vivado 的时序报告找到 “Input Delay” 或者 “Setup” 相关的路径。如果看到某个输入端口到第一级寄存器的路径有负 Slack先确认几件事set_input_delay的值是不是设得太乐观了参考时钟是不是选对了有没有漏掉某些端口我遇到过一个典型情况一个 8 位数据总线其中 7 位都约束了唯独第 3 位忘了写。结果工具对第 3 位按默认 0 延迟处理综合出来的电路在那一根线上时序特别差上板子就是这一位偶尔出错。这种问题在时序报告里其实能看出来因为那一根线的路径延迟会明显跟其他位不一样。所以约束写完一定要检查一遍看看有没有漏网之鱼。5.2 约束写了但工具不认怎么办有时候你明明写了set_input_delay但时序报告里就是没有体现。这种情况通常有几个原因一是参考时钟没有正确定义工具找不到-clock指定的时钟二是端口名写错了get_ports返回空集合工具默默忽略三是约束文件没有被正确加载比如 XDC 文件没有加到工程里或者加载顺序有问题。排查方法很简单在 Vivado 的 Tcl Console 里手动执行get_ports和get_clocks命令看看返回的是什么。如果返回空那就是名字写错了。另外可以用report_property命令查看某个端口的属性确认set_input_delay是否真的生效了。我一般会在约束文件里加一些puts语句把关键信息打印出来这样综合的时候能在日志里看到约束有没有被正确解析。5.3 常见问题速查表问题现象可能原因排查方法时序报告无输入延迟信息参考时钟未定义或端口名错误用get_clocks和get_ports验证建立时间违例严重-max值设得过大或过小对照外部器件手册重新计算保持时间违例-min值设得过大检查最小延迟计算是否包含 PCB 走线上板数据偶尔出错约束值过于乐观未留余量在计算值基础上加 10%-20% 余量双边沿采样约束不生效缺少-clock_fall或-add_delay检查约束语法确认两个边沿都写了换了器件后约束失效端口名或时钟名变化重新生成约束不要直接复制5.4 几个我踩过的坑第一个坑是参考时钟选错。有一次我做了一个带 MMCM 的输入接口外部时钟 50MHzMMCM 倍频到 200MHz 给内部逻辑用。我顺手就把set_input_delay的参考时钟写成了 200MHz 的那个。结果工具按 5ns 周期去算建立时间而实际外部数据是按 20ns 周期来的约束完全对不上。后来改成参考 50MHz 的输入时钟时序报告才正常。第二个坑是忘了加-add_delay。RGMII 双边沿采样的时候我先写了上升沿的约束又写了下降沿的约束但没加-add_delay。结果后一条把前一条覆盖了工具只看到了下降沿的约束上升沿的路径完全没约束。上板子跑千兆的时候接收数据一半对一半错查了好久才发现是约束被覆盖了。第三个坑是PCB 走线延迟估算太随意。有一次我按经验估了个 0.2ns 的走线延迟结果板子回来实测发现走线绕得比较长实际延迟接近 0.8ns。虽然最后通过调整 IDELAY 救回来了但过程很折腾。后来我养成了一个习惯PCB 走线延迟尽量让硬件同事给一个估算值或者用阻抗和长度粗略算一下不要拍脑袋。6. 从约束到实现让工具真正帮你干活6.1 约束写完只是开始很多人以为 XDC 文件写完、综合实现跑完、时序报告全绿就万事大吉了。但实际上时序报告全绿只代表工具在它理解的约束范围内认为没问题。如果约束本身写错了工具算出来的“没问题”就是假的。我见过太多项目时序报告干干净净上板子就是不稳定最后发现是约束跟实际硬件对不上。所以约束写完只是第一步接下来要做的是交叉验证。怎么验证如果有条件用示波器或者逻辑分析仪量一下实际信号的眼图看看数据和时钟的相位关系跟你约束里写的是不是一致。如果没有条件那就至少要在不同温度下多跑一段时间看看有没有偶发错误。FPGA 的时序是跟温度强相关的常温下没问题不代表高温下没问题。6.2 时序收敛的迭代思路如果时序报告里有输入延迟相关的违例不要急着去改 RTL 或者加流水线。先回头看看约束是不是写得太紧了。set_input_delay的值如果比实际需求大很多工具会拼命去优化那条路径可能把布局布得乱七八糟反而影响其他路径。正确的做法是先按实际计算值写约束跑一遍实现看违例程度。如果违例很小比如 -0.1ns 以内可以尝试稍微放宽约束或者加一点 IDELAY。如果违例很大比如 -1ns 以上那可能是约束值本身就不合理需要重新核对计算过程。另外Vivado 的report_timing_summary里有一个 “Input Delay” 的专门章节会列出所有输入端口到第一级寄存器的时序情况。我一般会重点看那几个 Slack 最差的路径确认它们的约束值是不是跟预期一致。如果某条路径的约束值明显跟其他路径不一样那多半是约束写错了。6.3 关于 set_input_delay 的几个经验法则第一宁可稍微悲观一点不要过于乐观。约束值比实际需求大 10% 到 20%工具会多留一些余量上板子更稳。但也不要大太多否则工具会过度优化浪费资源。第二所有输入端口都要约束不要有遗漏。哪怕某个端口你暂时不用也给它写一个合理的约束避免工具按默认值处理。第三约束文件要版本管理。每次改约束都要记录改了什么、为什么改。我见过有人改约束改到最后自己都忘了哪个版本是对的只能从头再来。第四换器件或者换板子的时候约束一定要重新审查。不同器件的引脚延迟不一样不同板子的走线也不一样直接复制约束很容易出问题。7. 写在最后的一些个人体会set_input_delay这个东西说难不难说简单也不简单。它的核心思想就是告诉工具外部数据相对于时钟的到达时间但实际项目里影响这个时间的因素太多了外部器件的 PVT 特性、PCB 走线的长度和阻抗、时钟树的偏斜、甚至连接器的寄生参数。你不可能把所有因素都精确建模但你可以通过合理的估算和留余量让工具帮你把内部路径优化到位。我个人的习惯是每做一个新接口先把外部器件的时序手册翻出来找到 tco 或者 tdv 这些参数然后跟硬件同事确认一下 PCB 走线的大致延迟再动手写约束。写完约束后先跑一遍综合看看有没有明显的语法错误或者端口匹配问题。然后跑实现看时序报告重点关注输入路径的 Slack。如果一切正常再上板子实测。实测的时候不要只看功能对不对还要看长时间运行有没有偶发错误因为时序问题往往就是偶发的。还有一个建议是多看看 Vivado 自带的那些参考设计里的 XDC 文件。Xilinx 的官方例程里RGMII、DDR、MIPI 这些接口的约束都写得很规范可以参考它们的写法和计算思路。但不要直接抄因为你的硬件跟例程的硬件不一样约束值肯定要重新算。抄的是思路和格式不是数值。最后说一个我最近才想明白的事时序约束不是为了应付工具而是为了让你自己搞清楚这个接口到底是怎么工作的。当你能够把set_input_delay的值一步步算出来并且能解释每一步为什么这么算的时候你对这个接口的理解就已经超过大多数人了。工具只是帮你验证你的理解对不对真正的功夫还是在约束之外。
返回列表