4、输入输出延迟约束:set_input_delay/set_output_delay 原理、最大/最小延迟约束、参考时钟的选择、多周期路径与伪路径
好,咱们今天聊一个实战性极强的话题——输入输出延迟约束。
说实话,我见过太多工程师在这上面栽跟头。板子调不通,时序报红,十有八九是 IO 延迟没设对。我自己刚入行那会儿,也吃过这个亏。有一次项目赶进度,我偷懒没仔细量 PCB 走线延迟,结果样机回来,数据老是抓错。折腾了两周,最后发现就是 set_input_delay 少算了 0.5ns。
所以这一节,咱们把 IO 延迟约束彻底讲透。
4.1 为什么要做 IO 延迟约束?
FPGA 不是孤立工作的。它要和外面的芯片打交道——DDR 颗粒、ADC、DAC、PHY 芯片、CPU……
你想想看,数据从外部芯片发出,经过 PCB 走线,到达 FPGA 的引脚,再进入内部寄存器。这一路上,信号是有延迟的。如果不告诉工具这些延迟是多少,工具怎么知道内部时钟该在什么时候采样?
说白了,IO 延迟约束就是告诉综合工具:外面的世界是什么样的。
核心概念:
- set_input_delay:定义数据在时钟沿之后多久到达 FPGA 引脚
- set_output_delay:定义数据从 FPGA 引脚输出后,外部器件需要多久才能稳定采样
4.2 set_input_delay 原理与实战
先看输入延迟。公式其实很简单:
set_input_delay -clock [get_clocks clk_sys] -max 2.5 [get_ports data_in]
这条命令的意思是:数据 data_in 相对于时钟 clk_sys 的上升沿,最晚可能延迟 2.5ns 到达 FPGA 引脚。
那这个 2.5ns 怎么来的?我一般这么算:
- 外部芯片的 Tco(时钟到数据输出延迟)——查数据手册
- PCB 走线延迟——大约 150~200ps/inch,具体看板材
- 板级抖动和串扰裕量——我习惯留 0.2~0.5ns
举个例子。假设外部芯片的 Tco_max = 1.8ns,PCB 走线 3 英寸,大约 0.6ns,再留 0.3ns 裕量。那 -max 就是 2.7ns。
我的习惯:-min 值通常设 0.5~1ns。为什么?因为数据不可能比时钟还早到太多。如果外部芯片的 Tco_min 很小,走线又短,数据可能提前到达。但提前太多,上一拍的数据还没被采走,就乱了。
4.3 set_output_delay 原理与实战
输出延迟是反过来的。它描述的是:FPGA 把数据送出去之后,外部器件需要多长时间才能稳定地采到这个数据。
set_output_delay -clock [get_clocks clk_sys] -max 3.0 [get_ports data_out]
这个 3.0ns 怎么算?
- 外部芯片的 Tsu(建立时间要求)——比如 1.5ns
- PCB 走线延迟——比如 0.8ns
- 时钟偏斜——FPGA 内部时钟到输出寄存器的延迟,加上 PCB 上时钟到外部芯片的延迟差
我遇到过最坑的一次,就是没算时钟偏斜。结果 FPGA 内部时序全绿,板子就是跑不起来。后来用示波器一抓,发现 FPGA 输出的数据和外部芯片的时钟沿之间,差了将近 2ns。
注意:set_output_delay 的 -max 对应的是外部芯片的建立时间要求,-min 对应的是保持时间要求。别搞反了。我见过有人把这两个值写反,然后 STA 报告一片红,还以为是工具 bug。
4.4 最大/最小延迟约束
刚才提到了 -max 和 -min。这两个参数到底在约束什么?
| 参数 | 含义 | 对应检查 | 典型场景 |
|---|---|---|---|
| -max | 最大路径延迟 | 建立时间 (setup) | 最差工艺角、高温、低压 |
| -min | 最小路径延迟 | 保持时间 (hold) | 最好工艺角、低温、高压 |
说白了,-max 管的是「数据别跑太慢,让下一拍采不到」;-min 管的是「数据别跑太快,把上一拍冲掉」。
我个人习惯,-max 和 -min 都设。很多新手只设 -max,觉得保持时间没问题。但你要知道,在 fast-fast 工艺角下,延迟可能比典型值小 30%~40%。保持时间违例是真实存在的。
4.5 参考时钟的选择
这个坑特别多。set_input_delay 和 set_output_delay 都需要指定一个参考时钟。
选时钟的原则:
- 用源同步时钟——如果外部芯片给你发了随路时钟,就用那个时钟做参考
- 用系统时钟——如果是同步系统,所有芯片共用同一个时钟源,那就用系统时钟
- 注意时钟域——跨时钟域的数据,IO 延迟约束要分别对应各自的时钟
我见过最离谱的,是一个工程师把 100MHz 的时钟写成了 200MHz。结果 set_input_delay 的数值没变,但工具按 200MHz 的周期去算时序,所有路径都报红。他查了两天,最后发现是时钟频率写错了。
避坑指南:
- 参考时钟的周期一定要和实际一致
- 如果是 DDR 接口,要用 -clock_fall 或 -add_delay 分别约束上升沿和下降沿
- 虚拟时钟(-clock [get_clocks virt_clk])用于没有物理时钟线的情况,比如异步接口
4.6 多周期路径与伪路径
有些路径不需要在每个时钟周期都完成数据传递。这时候就要用多周期路径约束。
set_multicycle_path -setup 2 -from [get_ports data_in] -to [get_regs data_reg]
这条命令告诉工具:从 data_in 到 data_reg 这条路径,允许用 2 个时钟周期来完成。
什么时候用?
- 慢速外设——比如一个 ADC 每 3 个时钟周期才更新一次数据
- 使能信号控制的路径——数据只在使能有效时才被采样
- 跨时钟域的同步器——两级或三级触发器同步,天然需要多个周期
伪路径就更直接了:
set_false_path -from [get_ports rst_n] -to [get_regs *]
这条命令说:这条路径不用检查时序,它不影响功能。
伪路径的典型场景:
- 复位信号——异步复位,不需要时序检查
- 测试模式信号——JTAG、SCAN 等测试逻辑
- 静态配置信号——只在初始化时变化,运行时不变
重要提醒:伪路径不要乱用。我曾经接手过一个项目,前工程师把一堆路径标成了伪路径,结果芯片在某些条件下会随机出错。后来发现,那些路径根本不是伪路径,只是他懒得分析时序报告。
伪路径的原则:只有你 100% 确定这条路径不影响功能时,才能标伪路径。
4.7 知识结构图
下面这张图总结了 IO 延迟约束的核心逻辑:
4.8 实战避坑清单
最后,我把自己这些年踩过的坑整理成一份清单,你写约束的时候可以对照着检查:
避坑清单:
- 时钟频率别写错——检查 get_clocks 的周期定义
- -max 和 -min 都要设——不要只关心建立时间
- 参考时钟用对——源同步用随路时钟,系统同步用系统时钟
- 多周期路径要同时设 setup 和 hold——只设 setup 不设 hold,hold 检查还是按单周期算
- 伪路径要谨慎——不确定就别标,宁可多分析时序报告
- 仿真验证——约束写完后,跑一下后仿,看看 IO 时序对不对
嗯,这一节的内容就到这儿。IO 延迟约束说难不难,说简单也不简单。关键是把原理搞清楚,然后多动手、多验证。你只要把上面这些点都吃透了,IO 约束这块基本就不会出大问题。