IO 约束 XDC 模板:input/output delay 分场景写法与推导
更新时间:2026-11-04。综合能过、时序报告也出了,签核却挂在端口路径上——十有八九是 IO 约束写错了。input delay 相对谁、min/max 两套数字怎么来、纯输出口为什么要造一个「虚拟时钟」,是 FPGA/ASIC 入门者抄约束时最常翻车的三处。本文给一份可以直接复制的 IO 约束 XDC 模板(XDC 在时序命令上与 SDC 同源,OpenSTA 可直接读取),每个数字都从板级时序预算推出,再用 OpenSTA 真跑一遍验证。底层命令语义可回看 IO 约束 XDC 模板的 SDC 基础,签核口径见 SDC 签核二十二项清单。
本文要回答的问题
set_input_delay/set_output_delay里的-clock到底填哪个时钟?- 模板里的 0.5、1.2 这些数字是拍脑袋的吗,怎么从板级参数推出来?
- 系统同步、边沿对齐(DDR)、源同步三种板级关系,模板分别怎么改?
- 纯输出口没有时钟引脚可引用,output delay 相对谁写?
- 端口名写错时工具为什么不一定报错,漏约束怎么发现?
一、名称与使用场景
这套模板管的是芯片端口与板级器件之间的时序边界:芯片内部路径靠 create_clock 就能自动检查,但数据从外部器件的触发器出发、经过封装引脚再到本芯片第一级触发器,这一段不在网表里,必须用 set_input_delay 显式告诉工具;输出侧同理,用 set_output_delay 把板级走线和对端器件的建立时间扣掉。不写这两条,所有端口路径在报告里都是无约束状态,等于时序签核漏了一大圈。
板级同步关系决定模板的具体形态,先分清三种场景再抄:
读这张卡抓住一条主线:先认同步关系,再选模板分支。系统同步是基础形态(本文实验就用它),边沿对齐和源同步只改 -clock 的指向与附加选项;纯输出口是唯一需要「无端口时钟」的分支。至于数据在双沿之间怎么打拍,可结合 SDR 与 DDR 输入输出寄存器 对照理解。
二、完整模板与数字推导
先把 delay 的预算关系画出来。下图是系统同步输入路径:外部器件在 t=0 沿发出数据,经过它自己的时钟到输出延迟 Tco、再经过板级走线,到达本芯片输入端口;工具把这两段之和记成 input delay,内部检查时从周期里一并扣掉。
怎么读这张图:input delay 不是芯片内部的延迟,而是「数据比发射沿晚多久到达我的引脚」,所以它的组成只有芯片外部那两段。本例假设板上是 100 MHz 晶振(周期 10 ns),外部器件数据手册给出 Tco 最小 0.3 ns、最大 0.8 ns,PCB 走线 0.2~0.4 ns,于是:
set_input_delay -max= Tco_max 0.8 + 走线_max 0.4 = 1.2 ns(喂给 setup 检查)set_input_delay -min= Tco_min 0.3 + 走线_min 0.2 = 0.5 ns(喂给 hold 检查)
输出侧反向:本芯片发出的数据要在对端器件建立时间前稳定,预算里要扣掉板级走线和对端建立/保持窗口。假设走线 0.2~0.7 ns、对端建立 0.3 ns、保持 0.2 ns,则 output delay max = 0.7+0.3 = 1.0 ns,min = 0.2+0.2 = 0.4 ns。把这些数字落进模板:
# 1) 主时钟:100 MHz 晶振进入 clk 端口,周期 10 ns
create_clock -name clk -period 10.0 [get_ports clk]
# 2) 虚拟时钟:对端捕获时钟没进入本芯片,只建时钟不挂端口
create_clock -name vclk -period 10.0
# 3) 系统同步输入:min/max 分别喂 hold / setup
set_input_delay -clock clk -min 0.5 [get_ports {din[*]}]
set_input_delay -clock clk -max 1.2 [get_ports {din[*]}]
# 4) 同源寄存器输出
set_output_delay -clock clk -min 0.4 [get_ports {dout[*]}]
set_output_delay -clock clk -max 1.0 [get_ports {dout[*]}]
# 5) 纯输出口 led:对端用板外时钟捕获,引用虚拟时钟
set_output_delay -clock vclk -min 0.4 [get_ports led]
set_output_delay -clock vclk -max 1.0 [get_ports led]
# 6) 组合直达输出 combo:相对发射它的时钟 clk 写
set_output_delay -clock clk -min 0.4 [get_ports combo]
set_output_delay -clock clk -max 1.0 [get_ports combo]为什么 min 和 max 要写两套:setup 关心数据最晚到,吃的是 max 那套;hold 关心下一沿的数据最早也不能冲掉本次捕获,吃的是 min 那套,少写任何一套都会有一类检查在裸奔。读报告时还会看到 output external delay 前面带负号——这不是算错了,而是输出预算要从捕获周期里往回扣,工具内部用负号表达「扣减」语义;input delay 在到达时间一侧永远是正数累加。
三个易错点先点明:输入 delay 相对发射它的时钟,输出 delay 相对捕获它的时钟,方向恰好相反;总线端口要用 {din[*]} 花括号包住,方括号才不会被 Tcl 当命令替换;combo 这种输入直穿到输出的组合口,output delay 仍要写,否则这段路径只受一半约束。另外复位、测试模式这类不要求同步时序的端口不要顺手加 input delay,改用 false path 或 case analysis 表达,否则等于人为造出一条永远收不紧的伪路径。
三、边沿对齐、源同步与虚拟时钟的改法
边沿对齐 / DDR。 另一时钟沿到达的数据要加 -clock_fall。关键坑:同一端口写第二条 set_input_delay 时必须带 -add_delay,否则默认是覆盖语义,第一条约束直接消失:
set_input_delay -clock clk -max 1.2 [get_ports din]
set_input_delay -clock clk -clock_fall -max 0.8 -add_delay [get_ports din]源同步。 时钟随数据一起从对端走过来(如高速 SerDes 之外的并行总线),先在时钟输入端口建随路时钟,delay 相对随路时钟写;DDR 双沿采样时常配 0.5 个周期的多周期约束:
create_generated_clock -name sclk -source [get_ports clk] \
-divide_by 1 [get_ports sclk_in]
set_input_delay -clock sclk -max 0.6 [get_ports {sdin[*]}]纯输出口与虚拟时钟。 如果输出端口的捕获触发器在另一颗芯片上、它的时钟根本不经过本芯片任何引脚,就没有端口能挂 create_clock。做法是建一个不带端口对象的时钟(即虚拟时钟 vclk),output delay 引用它。好处是对端时钟频率、不确定度以后独立调整,不会污染本芯片主时钟的统计。
单位。 XDC/SDC 里裸数字没有单位,跟随时序库的 time_unit(多数 PDK 与 Vivado 默认 ns,本实验库头声明 time_unit : "1ns")。皮法、纳秒混用是第二常见的低级错,抄模板前先确认库头。
不确定度怎么加、和模板是什么关系,见 XDC 模板之外的时钟不确定度:set_clock_uncertainty 是在上面模板算出的余量上再打折,不替代 input/output delay。
四、修改示范:加不确定度、翻转率与负载
基础模板只描述了「理想波形 + 板级预算」。真签核还要补上三项环境量,写法是在模板尾部追加而不是改动前面的 delay:
# 时钟不确定度:PLL jitter + 余量预留,setup/hold 同时收紧 0.2 ns
set_clock_uncertainty 0.2 [get_clocks clk]
# 输入端翻转率:外部器件驱动能力未知时给一个保守 slew
set_input_transition 0.3 [all_inputs]
# 输出负载:板级电容 50 fF = 0.05 pF
set_load 0.05 [all_outputs]这三项的作用在报告里直接可见:uncertainty 会出现在 required time 一栏(setup 减、hold 加),input transition 和 load 则通过延迟表影响 cell delay。生产项目通常从输入端口集合里排除时钟引脚再设翻转率(remove_from_collection [all_inputs] [get_ports clk]),避免与时钟树设置重复。
五、真实跑一遍:OpenSTA 读取与报错复现
模板不是摆样子:用一个 5 个 DFF 的最小 RTL,经 Yosys 综合到只有 BUF/NOT/AND2/OR2/DFF 的教学库,再让 OpenSTA 读入 XDC 报时序。正确约束下,din → combo 这条输入到输出的组合路径上,能看到模板里的 1.2 ns 被计入、0.2 ns 不确定度被扣减,最终 slack MET:
Startpoint: din[0] (input port clocked by clk)
Endpoint: combo (output port clocked by clk)
Path Group: clk
Path Type: max
0.000 0.000 clock clk (rise edge)
1.200 1.200 v input external delay
0.040 1.240 ^ _07_/Y (NOT)
1.240 data arrival time
10.000 10.000 clock clk (rise edge)
-0.200 9.800 clock uncertainty
-1.000 8.800 output external delay
8.800 data required time
7.560 slack (MET)再故意把端口名写成不存在的 din0。这里有个反直觉的真实行为:OpenSTA 不中止、只给 Warning,set_input_delay 作用在空集合上等于没写,后续命令照常执行,于是输入约束静默丢失。
读这份实录抓两处:开头两行 port 'din0' not found 是仅有的告警;再看下面 combo 路径的起点已经从 din[0] (input port clocked by clk) 退化成 din[0] (input port),input external delay 变成 0.000——约束没了,但工具不会判你失败。这正是签核清单要求「逐端口核对报告起点是否带 clocked by」的原因。把名字改回 {din[*]} 重跑,起点恢复 clocked by、1.2 ns 回到报告里,与第五节开头一致。
代码获取:完整工程(教学库、RTL、两套 XDC、OpenSTA 脚本与 Makefile)在公开仓 experiments/tpl-io-constraint-xdc,克隆后执行 make run 即可在 geek-eda:0.4-flow 镜像里复现综合与三轮 STA。库里的延迟数值是教学假设的常数表,产线请替换为代工厂 PDK 库。
实验环境
- 机器/系统:Mac mini M4 / macOS(Darwin 25),实验在 docker 容器 geek-eda:0.4-flow 内运行
- 工具版本:OpenSTA 2.0.17(
sta -version)、Yosys 0.33(yosys -V) - 日期:2026-11-04
- 完整命令:
docker run --rm -v <workdir>:/work -w /work geek-eda:0.4-flow sh run_all.sh(依次执行yosys synth.ys、sta run_sta.tcl、sta run_sta_bad.tcl、sta run_sta.tcl)
这一篇在知识体系里的位置
本篇是「RTL 综合之后如何把端口边界约束完整」的模板件:底层命令语义见 SDC 约束基础,不确定度的工程含义见 时钟不确定度详解,全片签核口径对照 SDC 签核二十二项清单,双沿接口的硬件背景见 SDR 与 DDR IO 寄存器。
总结
IO 约束 XDC 模板的骨架是三步:先 create_clock(纯输出口再加一个不挂端口的虚拟时钟),再按「输入相对发射沿、输出相对捕获沿」写 set_input_delay/set_output_delay 的 min/max 两套值,数字一律由 Tco、板级走线、对端建立保持窗口相加推出;边沿对齐加 -clock_fall -add_delay,源同步先建随路时钟。端口名写错时工具只发 Warning、约束静默丢失,必须靠报告里起点是否带 clocked by 来识别;模板经 OpenSTA 2.0.17 实读验证,复制后改时钟周期与板级预算两组数即可用。