4、输出延迟约束:set_output_delay命令、输出路径的时序模型、外部器件时序参数

输出延迟约束,说白了就是告诉工具:你的数据从FPGA出去以后,外面那个芯片到底能等多长时间。我刚开始做RISC-V核心的时候,总觉得只要把内部时序调好就行了,结果板子一跑,外挂的SRAM老是读错数据。后来才明白——输出约束没做对,核心频率再高也是白搭。

4.1 输出路径的时序模型

先看一个最简单的场景。你的RISC-V核心输出地址和数据,送给片外的Flash或者SRAM。这时候时序路径长什么样?

FPGA内部:时钟从PLL出来,经过时钟树,到达输出寄存器的CK端。数据从Q端输出,经过内部走线,到达输出引脚。

FPGA外部:数据从引脚出发,经过PCB走线,到达外部器件的输入引脚。外部器件内部还有一段建立时间和保持时间的要求。

嗯,这里要注意——我们做输出约束,本质上是在约束FPGA内部那一段路径。外部那段路径的延迟,我们通过set_output_delay来告诉工具。

核心公式(记牢这个):

输出延迟 = PCB走线延迟 + 外部器件建立时间

或者反过来:输出延迟 = 时钟周期 - (PCB走线延迟 + 外部器件保持时间)

我习惯把输出延迟想象成「外部器件给FPGA出的考题」。外部器件说:数据必须在时钟沿之前Tsu时间稳定,在时钟沿之后Thd时间保持不变。那FPGA就得保证自己的输出满足这个要求。

4.2 set_output_delay命令详解

Vivado里用set_output_delay来约束。语法其实不复杂,但参数多了容易搞混。

# 最基本的写法
set_output_delay -clock [get_clocks clk] -max 5.0 [get_ports data_out]
set_output_delay -clock [get_clocks clk] -min 2.0 [get_ports data_out]

# 带参考时钟沿的写法
set_output_delay -clock [get_clocks clk] -max 5.0 -clock_fall [get_ports data_out]

# 带-add_delay的写法(多时钟域场景)
set_output_delay -clock [get_clocks clk1] -max 4.0 -add_delay [get_ports data_out]
set_output_delay -clock [get_clocks clk2] -max 3.0 -add_delay [get_ports data_out]

参数解释一下:

  • -clock:指定参考时钟。输出路径的时序分析基于这个时钟。
  • -max:最大输出延迟。对应外部器件的建立时间要求。
  • -min:最小输出延迟。对应外部器件保持时间要求。
  • -clock_fall:用时钟下降沿作为参考。默认是上升沿。
  • -add_delay:允许多个约束叠加。多时钟域场景必备。

我的小习惯:

写约束的时候,我习惯把-max和-min成对写。只写-max不写-min,保持时间分析可能漏掉。曾经有一次我就是只写了-max,结果保持时间违例了,查了两天才发现是-min没写。

4.3 外部器件时序参数解读

拿到一个外部器件的datasheet,怎么看时序参数?我以最常见的SRAM为例。

参数符号 参数名称 典型值(ns) 说明
tSU 建立时间 3.0 数据必须在时钟沿前稳定
tHD 保持时间 1.0 数据必须在时钟沿后保持
tCO 时钟到输出 5.0 时钟沿后数据才有效
tPD 传播延迟 2.0 PCB走线延迟

怎么算输出延迟?举个例子:

假设时钟周期10ns,外部SRAM的tSU=3ns,PCB走线延迟tPD=1ns。

那set_output_delay -max应该设多少?

答案是:3 + 1 = 4ns。

为什么?因为FPGA的数据从引脚出来,经过1ns的PCB走线,到达SRAM的输入引脚。SRAM要求数据在时钟沿前3ns稳定。所以FPGA必须在时钟沿前4ns就把数据送到引脚上。

注意:

我曾经犯过一个错误——把PCB走线延迟算反了。以为走线延迟是给FPGA多争取时间,结果约束设小了,板子跑起来数据全是乱的。记住:走线延迟是消耗时间的,不是给你加时间的。

4.4 实战:RISC-V核心的地址输出约束

拿RISC-V核心的地址总线举例。地址总线通常连接到外部存储器,频率高、位宽大,约束必须做细。

# 假设时钟周期10ns,外部SRAM的tSU=2ns,tHD=1.5ns,PCB走线延迟1ns
# 最大输出延迟 = 2 + 1 = 3ns
# 最小输出延迟 = 1.5 - 1 = 0.5ns(注意这里是减)

set_output_delay -clock [get_clocks clk_core] -max 3.0 [get_ports {addr[*]}]
set_output_delay -clock [get_clocks clk_core] -min 0.5 [get_ports {addr[*]}]

为什么最小输出延迟是减?因为保持时间要求数据在时钟沿后还要稳定一段时间。PCB走线延迟让数据晚到,反而帮了保持时间的忙。所以实际约束值可以小一点。

嗯,这里有个坑——如果PCB走线延迟大于外部器件的保持时间,最小输出延迟会变成负数。Vivado允许负数,但你要确认物理上是否真的能实现。

4.5 输出延迟约束的常见误区

  1. 只约束-max不约束-min:保持时间分析会漏掉,可能导致数据采样错误。
  2. 把PCB走线延迟算反:走线延迟是加在-max上,减在-min上。方向搞反了约束就废了。
  3. 忽略时钟抖动:实际时钟有jitter,约束时最好留0.1-0.2ns的余量。
  4. 多bit总线没加skew:地址总线多bit之间可能有skew,建议按最差情况约束。

避坑指南:

我曾经在一个RISC-V项目里,地址总线约束只写了-max,没写-min。综合实现后时序报告全绿,但板子跑起来就是随机出错。后来用示波器抓波形,发现地址信号在时钟沿附近有毛刺,保持时间不够。加上-min约束后重新实现,问题解决。

4.6 知识体系图

下面这张图帮你理清输出延迟约束的完整逻辑:

输出延迟约束知识体系 set_output_delay 输出路径时序模型 FPGA内部 + PCB + 外部器件 命令参数详解 -max / -min / -clock_fall / -add_delay 外部器件时序参数 tSU / tHD / tCO / tPD 内部路径延迟 PCB走线延迟 建立时间约束 保持时间约束 多时钟域处理 建立时间tSU 保持时间tHD 传播延迟tPD 核心公式:输出延迟 = PCB延迟 + 外部器件时序要求 实战要点:成对约束、留余量、多bit考虑skew 常见误区:只写-max、PCB延迟算反、忽略时钟抖动

这张图把输出延迟约束拆成了三块:时序模型、命令参数、外部器件参数。你想想看,只要把这三块搞明白,任何输出接口的约束都能手到擒来。

我个人习惯是先把外部器件的datasheet翻出来,找到tSU和tHD,再估算PCB走线延迟,最后套公式算出-max和-min。这样一步步来,基本不会出错。


公众号:蓝海资料掘金营,微信deep3321