这一篇在干嘛?
第 1 篇学会了”怎么写命令”,这一篇解决”为什么这么写”。上半场讲静态时序分析(STA)的原理:Slack 公式是怎么推出来的,工具在四个 PVT 角点下怎么算。下半场把手册第 5 章的典型场景过一遍:各种同步接口的 input/output delay 公式、异步 FIFO 的标准约束模板、四种多周期路径的推导。看完你就能对着时序报告定位问题。同系列:第 1 篇:基本概念与 SDC 命令详解。
基本时序概念与四类检查
先背下这组术语,它们是时序报告里的”官方语言”:
| 术语 | 简写 | 含义 |
|---|---|---|
| 建立时间 | Tsu | 数据必须在时钟沿之前稳定的最小时间 |
| 保持时间 | Th | 数据必须在时钟沿之后稳定的最小时间 |
| 恢复时间 | Recovery | 异步信号变为非有效状态与下一个时钟沿之间的最短时间 |
| 移除时间 | Removal | 有效时钟边沿与异步信号释放之间的时间 |
| 源时钟/发送时钟 | Launch clock | 发起数据的时钟 |
| 目的时钟/捕获时钟 | Capture clock | 捕获数据的时钟 |
| 发送沿 | Launch edge | 源时钟发送数据的时钟沿 |
| 捕获沿 | Capture edge | 目的时钟捕获数据的时钟沿 |
| 到达时间 | Arrival time | 数据从发送到捕获之前经过的延时 |
| 要求时间 | Required time | 数据发送后能被安全捕获的最差时间点 |
| 时序裕量 | Slack | 检查后算出的余量,≥ 0 才满足要求 |
对应的四类检查:建立检查(Setup check)、保持检查(Hold check)针对同步数据路径;恢复检查(Recovery check)、移除检查(Removal check)针对异步复位信号。恢复检查类似建立检查,移除检查类似保持检查——分析通过后才能保证异步复位的释放时序正常,防止复位亚稳态。一般工具在异步复位信号被同步到当前时钟域之后才执行这两类检查。
四类时序路径与路径组成

设计中的时序路径只有四种,对着上图理解:
- 输入端口 → 内部寄存器(蓝色):板级时钟在外部发送数据,经板上延迟(即输入延迟 Input delay)到达 FPGA 输入端口,再经内部延迟后被寄存器 B 捕获;
- 寄存器 → 寄存器(紫色):时钟经端口和 BUFG 驱动寄存器 B 发送数据,内部延迟后由寄存器 C 捕获——这是最经典的 reg2reg 路径;
- 内部寄存器 → 输出端口(红色):寄存器 C 发送,经内部延迟到输出端口,再经外部延迟(即输出延迟 Output delay)由板级寄存器 D 捕获;
- 输入端口 → 输出端口(绿色):数据在 FPGA 内部不经过锁存直接输出(纯组合直通),参考时钟可能是虚拟时钟也可能是内部时钟。
每条路径都由三段组成:源时钟路径(源点 → 发送寄存器时钟管脚)、数据路径(起点 → 终点之间传输数据的部分)、目的时钟路径(源点 → 捕获寄存器时钟管脚)。
数据传输关系:数据由源时钟的发送沿发出、目的时钟的捕获沿捕获。典型情况下发送沿发生在 0ns,捕获沿在 1 个周期之后。

Slack 是怎么算出来的
STA 的目的在于验证 FPGA 加载位流后时序行为的可预测性。由于工艺材料和环境变化的影响,工具会对四种延迟场景做分析:Slow Corner 最大延迟、Slow Corner 最小延迟、Fast Corner 最大延迟、Fast Corner 最小延迟。看最恶劣情况的取法:
- 建立/恢复检查看最大延迟:Slow Corner 下源时钟路径、数据路径都参考 Slow max,但目的时钟路径参考 Slow min(捕获沿来得越早,建立越紧);Fast Corner 同理用 Fast max / Fast min;
- 保持/移除检查看最小延迟:Slow Corner 下源时钟路径、数据路径参考 Slow min,目的时钟路径参考 Slow max;Fast Corner 同理。
建立检查

数据到达时间(蓝色路径)由三段组成——发送时钟沿延迟 Tlaunch、发送寄存器的 Tclk-Q、数据路径延迟 Tdp:
Arrival time = Tlaunch + Tclk-Q + Tdp (公式 4-1)
要求时间(绿色路径)由捕获时钟沿延迟 Tcapture、捕获寄存器 Tsu、时钟周期 Tcycle 组成(捕获沿比发送沿晚一个周期):
Required time = Tcapture - Tsu + Tcycle (公式 4-2)
建立裕量 = 要求时间 - 到达时间(数据必须早到):
Setup Slack = Required time - Arrival time (公式 4-3)
保持检查

到达时间与建立检查相同。但要求时间只由捕获时钟沿延迟 Tcapture 和捕获寄存器的 Th 组成(数据必须在新数据到来后仍保持 Th 这么久):
Required time = Tcapture + Th (公式 4-5)
Hold Slack = Arrival time - Required time (公式 4-6)
注意方向反了:hold 要求到达时间不小于要求时间,而 setup 要求到达时间不大于要求时间。
时钟不确定性与时序悲观
时钟不确定性(Timing Uncertainty)是在设计中加入误差裕量的方式,主要考虑芯片中时钟传播时间的可能变化。它不覆盖默认抖动计算,而是在不更改时钟定义和关系的情况下”过度约束”某些时钟。工具的做法:建立检查时从要求时间中减去不确定性值(要求数据提前到达,补偿最差时钟偏移下的延迟启动/过早捕获);保持检查时给要求时间加上不确定性值(要求数据延后撤离,补偿过晚捕获)。
时序悲观(Timing Pessimism)则相反,是工具”算多了”要往回找的地方。源时钟和目的时钟在公共路径上(比如共用同一段时钟树)被分别用不同延迟分析,但一条导线不可能同时存在两个不同延迟。于是引入 CRPR(Clock Reconvergence Pessimism Removal)消除差异:
CRPR = common clock path (max delay - min delay) (公式 4-7)
建立/恢复检查时,目标路径延迟加上 CRPR;保持/移除检查时减去 CRPR。
时钟约束的六种典型场景
RTL 设计时时钟方案贯穿始终:哪些管脚做时钟输入输出、用哪些硬核时钟单元,约束阶段都要逐一覆盖。
场景 1:I/O 管脚时钟(最常见):
create_clock -name clk_a -period 8.000 -waveform {0.000 4.000} [get_ports {clk_a}]
create_clock -name clk_b -period 20.000 -waveform {0.000 10.000} [get_ports {clk_b}]场景 2:高速串行收发器(SerDes)单元——源点是内部硬核 pin:
create_clock -name serdes_phy0_clk -period 8.000 -waveform {0.000 4.000} \
[get_pins {serdes_phy0.tx_outclk}]
create_clock -name serdes_phy1_clk -period 4.000 -waveform {0.000 2.000} \
[get_pins {serdes_phy1.tx_outclk}]场景 3:PCIE 单元——同样是 get_pins 定位硬核时钟管脚:
create_clock -name pcie_phy0_clk -period 8.000 -waveform {0.000 4.000} \
[get_pins {pcie_phy0.core_clk}]
create_clock -name pcie_phy1_clk -period 4.000 -waveform {0.000 2.000} \
[get_pins {pcie_phy1.core_clk}]场景 4:PLL 单元(几乎所有设计都会用到)——两步走:先约束参考时钟,再约束输出时钟:
# 第一步:参考时钟
create_clock -name clk_in -period 10.000 -waveform {0.000 20.000} [get_ports {clk_in}]
# 第二步:PLL 各输出,用 create_generated_clock 描述倍频与相位
create_generated_clock -name {clkout0} -source [get_ports {clk_in}] \
-master_clock {clk_in} -multiply_by 5.000 -phase 45 [get_pins {u_pll.clkout[0]}]
create_generated_clock -name {clkout1} -source [get_ports {clk_in}] \
-master_clock {clk_in} -multiply_by 4.000 -phase 90 [get_pins {u_pll.clkout[1]}]
create_generated_clock -name {clkout2} -source [get_ports {clk_in}] \
-master_clock {clk_in} -multiply_by 2.000 -phase 0 [get_pins {u_pll.clkout[2]}]偷懒写法:derive_pll_clocks 一条命令为所有 PLL 时钟输出自动生成约束。
场景 5:时钟组约束。假定 clk_1~clk_4 四个时钟,约束写法与分析结果对照(“分析”= 工具检查这两列时钟之间的路径):
| 约束 | 效果 |
|---|---|
set_clock_groups -group [get_clocks {clk_1}] | clk_1 与其他所有时钟之间不分析 |
set_clock_groups -group {clk_1} -group {clk_2} | clk_1 与 clk_2 之间不分析,其余照常 |
set_clock_groups -group [get_clocks {clk_1 clk_2}] | {clk_1,clk_2} 整组与 {clk_3,clk_4} 之间不分析 |
set_clock_groups -group [get_clocks {clk_1 clk_2 clk_3}] | 前三个时钟与 clk_4 之间不分析 |
set_clock_groups -group {clk_1 clk_3} -group {clk_2 clk_4} | 两个组之间不分析,组内照常 |
记住规则:组与组之间不分析,同一组内的时钟之间照常分析。
场景 6:时钟选择单元(时钟 MUX)——同一设计要动态跑多个频率时。方法是:两个输入时钟各约束一个生成时钟(共用 MUX 输出点),第二个要加 -add,再用 -exclusive 声明互斥:
create_clock -name clk_a -period 8.000 -waveform {0.000 4.000} [get_ports {clk_a}]
create_clock -name clk_b -period 20.000 -waveform {0.000 10.000} [get_ports {clk_b}]
create_generated_clock -name clk_a_mux -source [get_ports clk_a] -master_clock {clk_a} \
[get_pins {clk_mux.mux_out}]
create_generated_clock -name clk_b_mux -source [get_ports clk_b] -master_clock {clk_b} \
[get_pins {clk_mux.mux_out}] -add
set_clock_groups -exclusive -group [get_clocks {clk_a_mux}] -group [get_clocks {clk_b_mux}]IO 延迟约束:系统同步与源同步
器件间传数据有两种同步方式:

- 系统同步:两个器件共用同一路板载时钟,同时用于发送和接收。此时 FPGA 内找不到对方器件的时钟,要用虚拟时钟作参考;
- 源同步:发送器件随数据一起送一路时钟到接收器件(高速接口几乎都是这种,如 RGMII)。
输入延迟公式速查
上游器件寄存器 Tclk-Q + 板上走线延迟构成数据的相对延迟。系统同步(上升/下降沿采样):
max_delay = clock_to_out_delay_max + board_trace_delay_max
min_delay = clock_to_out_delay_min + board_trace_delay_min
双沿采样时按 rise/fall 分两组,下降沿那组加 -clock_fall ... -add_delay。
源同步 - 边沿对齐(时钟沿与数据转换沿对齐,用 skew 描述,经过 PLL 或直接采样两种接收方式):
- 上升沿采样(时钟沿对齐数据结束):
max_delay = skew_are,min_delay = -skew_bre(skew_bre/are = 上升沿之前/之后的数据无效时间); - 下降沿采样:把上式换成 skew_bfe/skew_afe,加
-clock_fall; - 直接采样(时钟沿对齐数据起始)时要再加一个周期项:
max_delay = input_clock_period + skew_are,min_delay = input_clock_period - skew_bre; - 双沿采样:rise/fall 各取
period/2 ± skew,四条约束。
源同步 - 中心对齐(时钟沿对齐数据有效窗口中心,同一沿既启动又捕获;用 dv_bre/dv_are = 上升沿之前/之后的数据有效时间):
- 上升沿采样:
max_delay = input_clock_period - dv_bre,min_delay = dv_are; - 下降沿采样:换成 dv_bfe/dv_afe,加
-clock_fall; - 双沿采样:rise 用
period/2 - dv_bfe和dv_are,fall 用period/2 - dv_bre和dv_afe。
以系统同步双沿输入为例,完整四条:
set_input_delay -clock input_clock -max rise_max_delay [get_ports input_data]
set_input_delay -clock input_clock -min rise_min_delay [get_ports input_data]
set_input_delay -clock input_clock -max fall_max_delay -clock_fall [get_ports input_data] -add_delay
set_input_delay -clock input_clock -min fall_min_delay -clock_fall [get_ports input_data] -add_delay输出延迟公式速查
输出方向的数据延迟取决于板上延迟和目标器件寄存器的建立/保持要求:
max_delay = board_trace_delay_max + Tsu (对下游建立)
min_delay = board_trace_delay_min - Th (对下游保持)
双沿采样按 Tsu_r/Th_r(上升沿)与 Tsu_f/Th_f(下降沿)分四条约束写。
源同步输出的关键概念:输出时钟一般经 FPGA 内部 ODDR 送出,业界称之为 forwarded clock。两种计算方式:
- 基于目标器件建立保持要求:与系统同步公式相同(max = 走线 max + Tsu,min = 走线 min - Th),时钟用 fw_clock;
- 基于 forwarded clock 与数据的偏差(skew_bre/skew_are 等):上升沿采样时
max_delay = fw_clock_period - skew_are,min_delay = skew_bre;双沿采样时按 rise/fall 各取period/2参与运算,下降沿组加-clock_fall -add_delay。
常见坑:
- 系统同步的 input delay 参考的是虚拟时钟(外部时钟不在 FPGA 里),源同步的 input/output delay 参考的是随数据进来的真实源同步时钟,两者别混;
- 边沿对齐用”无效时间”(skew),中心对齐用”有效时间”(dv),公式符号完全不同,套错模板整组约束就错了;
- min_delay 可以为负值(数据比时钟沿还早到),是正常现象不是笔误。
时序例外约束实战
伪路径
加 false path 能减少分析时间、提高结果质量(QOR):工具不再分析或优化这些路径,避免在伪路径上浪费修复精力而冷落真正的功能路径。
# 示例一:删除复位端口到所有寄存器的路径
set_false_path -from [get_port reset] -to [all_registers]
# 示例二:两个异步时钟之间(要写双向两条)
set_false_path -from [get_clocks async_clk1] -to [get_clocks async_clk2]
set_false_path -from [get_clocks async_clk2] -to [get_clocks async_clk1]异步 FIFO 约束(重点案例)

异步 FIFO 是跨时钟域约束的典型案例。架构要点:写指针 wr_ptr 在 fifo_wrclk 域经 b2g(binary to gray)存入 wr_ptr_b2g 寄存器,跨域后被 fifo_rdclk 域的 wr_b2g_ff0/wr_b2g_ff1 两级触发器打两拍,再经 g2b 译码为 wr_ptr_rdclk_next;读指针方向对称。
标准约束模板分三层:
# 第 1 层:定义读写时钟
create_clock -name fifo_wrclk -period 6.4 -waveform {0 3.2} [get_ports {fifo_wrclk}]
create_clock -name fifo_rdclk -period 6.2 -waveform {0 3.1} [get_ports {fifo_rdclk}]
# 第 2 层:读写时钟之间整体放松(-datapath_only 100,消除跨域相位分析)
set_max_delay -from [get_clocks {fifo_wrclk}] -to [get_clocks {fifo_rdclk}] -datapath_only 100
set_max_delay -from [get_clocks {fifo_rdclk}] -to [get_clocks {fifo_wrclk}] -datapath_only 100
# 第 3 层:格雷码打两拍部分收紧(保证延迟不超过对侧时钟周期 T_min)
set_max_delay -from [get_regs {*wr_ptr_b2g[*]}] -to [get_regs {*wr_b2g_ff0[*]}] -datapath_only 6.2
set_max_delay -from [get_regs {*rd_ptr_b2g[*]}] -to [get_regs {*rd_b2g_ff0[*]}] -datapath_only 6.2思路非常值得体会:跨域整体”放松”(100ns 上限即可),但格雷码同步那几根线要”收紧”到至少小于对侧时钟周期(这里 rdclk 周期 6.2ns),保证指针采样的一致性。
Max/Min delay 例外
纯组合 IO 路径的约束(进出 FPGA 都不经过时序元件才能这么写):
set_max_delay 40 -from [all_inputs] -to [all_outputs]
set_min_delay 10 -from [all_inputs] -to [all_outputs]寄存器打两拍跨时钟域的通用模板(REG1 在 clk1 域,REG2 在 clk2 域,延迟限 1ns):
set_max_delay -from [get_regs REG1] -to [get_regs REG2] -datapath_only 1多周期路径四种场景
工具默认分析规则是 setup 1 cycle、hold 0 cycle:

场景 A:单时钟域(RegA→RegB,数据每 3 个周期更新一次,RegB 带时钟使能):

set_multicycle_path 3 -setup -from [get_regs REGA] -to [get_regs REGB]
set_multicycle_path 2 -hold -end -from [get_regs REGA] -to [get_regs REGB]第一条把 setup 沿移到第 3 个周期;hold 沿自动跟着变成第 2 个周期,导致数据被要求保持 2 个周期——由于有时钟使能没必要,所以第二条 hold 2 -end 把保持要求放松回 0。总结成口诀:setup N → hold N-1。
场景 B:有固定相位差的同频时钟(Clock B 比 Clock A 延迟 1ns)。默认分析会把 Clock A/B 之间的逻辑过度约束(setup 要求只剩 1ns、hold 却宽松到 -9ns)。正相移时补一条:
set_multicycle_path 2 -setup -from [get_clocks Clock_A] -to [get_clocks Clock_B]setup 捕获沿向前移一个周期即可,hold 不用动。注意负相移时通常不需要多周期约束;相移过大时必须调整启动/捕获沿,否则会出问题。
场景 C:快时钟 → 慢时钟(Slow 周期是 Fast 的 3 倍)。setup 要用 -start 相对发送时钟计数:
set_multicycle_path N -setup -start -from [get_clocks Fast_Clock] -to [get_clocks Slow_Clock]
set_multicycle_path N-1 -hold -from [get_clocks Fast_Clock] -to [get_clocks Slow_Clock]第一条生效后发送沿向后移(本例 N=3 移到 0ns),hold 沿受影响,第二条把保持的发送沿向后移 2 个周期;捕获沿始终不动。
场景 D:慢时钟 → 快时钟(Slow 周期是 Fast 的 2 倍,RegB 有时钟使能):
set_multicycle_path 2 -setup -from [get_clocks Slow_Clock] -to [get_clocks Fast_Clock]
set_multicycle_path N-1 -hold -end -from [get_clocks Slow_Clock] -to [get_clocks Fast_Clock]setup 捕获沿向前移一个周期;随后补 N-1 -hold -end 把保持的捕获沿向后移 1 个周期以放松保持要求。RegB 的时钟使能保证了没有传输亚稳态风险。
四种场景的规律:默认 setup 用 -end、hold 用 -start;-setup 修完往往要补一条 hold;-start/-end 的选择取决于你想移动的是发送沿还是捕获沿。
通关标准:
- 能默写 Setup Slack = (Tcapture - Tsu + Tcycle) - (Tlaunch + Tclk-Q + Tdp),并说出 hold 公式方向相反的原因。
- 拿到一份系统同步/源同步接口的时序参数表(Tsu/Th 或 skew),能独立算出 input/output delay 的 min/max 并写出四条约束。
- 能完整写出异步 FIFO 的三层约束模板,并解释为什么整体放松、格雷码段收紧。
自测 1:建立检查时为什么目的时钟路径要用 min 延迟?
建立检查考察”数据最晚到 vs 捕获沿最早来”。数据到达用 max 延迟算最坏情况;而捕获沿如果因时钟路径延迟小而提前到达,留给数据的时间反而更少,所以目的时钟路径要用 min 延迟才是最恶劣组合。保持检查同理反过来:数据最早离开(min)vs 捕获沿最晚到达(max)。
自测 2:CRPR 是什么?为什么建立检查要加、保持检查要减?
CRPR(时钟重汇聚悲观移除)= 公共时钟路径的 (max delay - min delay)。源/目的时钟共享同一段时钟树,但工具分析时给它们分别取了不同延迟,同一根线不可能有两个延迟,这部分是”假悲观”。建立检查中到达时间偏悲观,把 CRPR 加到目标路径上放松要求;保持检查则相反减去 CRPR。
自测 3:系统同步和源同步接口,input delay 的参考时钟分别选什么?
系统同步:外部与 FPGA 共用板载时钟,但该时钟在 FPGA 内可能没有网表对象,FPGA 侧要用虚拟时钟(create_clock 不带 Target)作 launch clk 参考,capture clk 是 FPGA 内部采样时钟。源同步:时钟随数据一起从同一外部器件送入 FPGA,input delay 直接参考这路真实的源同步输入时钟(先 create_clock 定义它)。
自测 4:异步 FIFO 约束里,-datapath_only 100 和 -datapath_only 6.2 分别是干什么的?
-datapath_only 100 约束在两个时钟域整体之间:消除跨时钟域的相位/抖动分析(hold 自动按 false path 处理),给所有跨域路径一个宽松的 100ns 上限,防止工具报一堆无意义的 violation;-datapath_only 6.2 收紧在格雷码指针打两拍那几根线上:保证数据在 6.2ns(对侧时钟周期)内到达第一级同步器,使两级触发器采样到一致的指针值。
自测 5:快时钟到慢时钟的多周期约束为什么 setup 要用 -start 而不是默认的 -end?
默认 -end 表示 setup 的 N 倍数相对捕获时钟计数。快→慢方向上,我们想表达的是”数据用快时钟的 N 个周期发出后才被慢时钟采”,即发送沿要沿发送(快)时钟向后移 N 个周期,这正是 -start 的语义(相对 launch clock 计数)。用 -end 会移动慢时钟的捕获沿,得不到正确的建立关系。