2. 时序约束基础:建立时间与保持时间、时钟周期约束、输入输出延迟约束

时序约束,说白了就是告诉工具你的芯片要跑多快、信号什么时候到、什么时候必须稳定。我刚开始做数字设计那会儿,总觉得时序约束是后端的事,前端只管写代码就行。结果第一次流片回来,芯片在低温下死活跑不到目标频率……嗯,从那以后我再也不敢轻视时序约束了。

这一章我们先把最基础的概念理清楚。只有把建立时间、保持时间、时钟周期这些基本功打扎实了,后面讲Hybrid场景下的复杂约束才能听得明白。

2.1 建立时间与保持时间——芯片的“心跳规则”

每个寄存器都有两个关键的时间参数:建立时间(Setup Time, Tsu)和保持时间(Hold Time, Th)。这两个参数决定了数据在时钟沿附近必须保持稳定的窗口。

核心概念速记

  • 建立时间(Tsu):时钟有效沿到来之前,数据必须提前稳定下来的最短时间。
  • 保持时间(Th):时钟有效沿到来之后,数据必须继续保持稳定的最短时间。

为什么要有这两个时间?因为寄存器内部是靠锁存器工作的,数据需要一段时间才能稳定地“写入”存储节点。如果数据在窗口内变化,寄存器就可能进入亚稳态——输出既不是0也不是1,整个逻辑就乱套了。

我在一个AI加速器项目里遇到过这种情况:综合后时序报告全是绿的,但后仿真的波形里偶尔出现毛刺。查了两天才发现,有个寄存器的保持时间余量只有几个皮秒,工艺角一变化就hold violation了。所以啊,时序分析不能只看setup,hold violation同样致命。

2.2 时钟周期约束——告诉工具你的目标频率

时钟周期约束是最基本的约束,没有之一。工具需要知道时钟的频率、占空比、相位,才能计算每条路径的延迟是否满足要求。

在SDC(Synopsys Design Constraints)里,我们用 create_clock 命令来定义时钟:

# 创建一个100MHz的主时钟,占空比50%
create_clock -name clk -period 10.0 [get_ports clk]

# 创建一个200MHz的虚拟时钟(用于IO约束)
create_clock -name vclk -period 5.0

这里有个细节:-period 的单位是纳秒(ns)。100MHz对应10ns,200MHz对应5ns。我个人习惯在写约束文件时把单位写清楚,或者在文件开头加个注释说明时间单位,避免后续交接时搞混。

时钟周期约束直接影响建立时间的分析。工具会检查:

  • 数据从发射寄存器到捕获寄存器的总延迟,是否小于一个时钟周期减去建立时间。
  • 公式:Tclk2q + Tcomb + Tsu ≤ Tperiod + Tskew

你想想看,如果组合逻辑路径太长,或者时钟偏斜(skew)太大,setup就很容易违例。这时候要么降频,要么优化逻辑深度。

我的小技巧:在项目初期,我会先给一个宽松的时钟周期(比如目标频率的80%),等RTL稳定后再收紧到目标频率。这样能避免早期综合跑太久,同时提前发现关键路径。

2.3 输入输出延迟约束——让工具知道芯片外面的世界

芯片不是孤立存在的。它要跟外面的FPGA、存储器、其他芯片通信。输入输出延迟约束就是告诉工具:外部信号相对于时钟,到底什么时候到、什么时候走。

2.3.1 输入延迟约束

输入延迟(set_input_delay)描述的是:外部信号在时钟沿之后多久到达芯片的输入引脚。

# 假设外部器件在时钟上升沿后2ns输出数据
set_input_delay -clock clk -max 2.0 [get_ports data_in]
set_input_delay -clock clk -min 0.5 [get_ports data_in]

这里 -max 和 -min 分别对应最差和最好情况。工具会用 -max 检查内部寄存器的setup,用 -min 检查hold。

我曾经在一个DDR接口项目里吃过亏:只给了 -max 没给 -min,结果工具默认 -min 为0,导致hold分析过于乐观。流片回来发现数据采样不稳定,最后只能降速运行。所以,input_delay的min值一定要给,哪怕你觉得外部器件不可能那么快。

2.3.2 输出延迟约束

输出延迟(set_output_delay)描述的是:芯片内部数据从时钟沿开始,到外部器件需要它稳定下来的时间窗口。

# 假设外部器件需要在时钟沿前3ns看到稳定数据
set_output_delay -clock clk -max 3.0 [get_ports data_out]
set_output_delay -clock clk -min 1.0 [get_ports data_out]

注意:set_output_delay 的值是从时钟沿往回算的。值越大,留给内部路径的时间越少,约束越紧。

常见误区:很多人把output_delay理解成“数据从时钟沿开始到输出引脚的时间”,其实正好相反。output_delay是外部器件要求的建立/保持时间,不是芯片内部的延迟。搞反了的话,时序分析结果会完全错误。

2.4 知识体系总览

下面这张图把本章的核心概念串起来了。你可以看到,从时钟周期约束出发,经过建立/保持时间检查,再到输入输出延迟,构成了完整的时序约束基础框架。

时序约束基础知识体系 时序约束基础 · 知识体系 时序约束基础 建立/保持时间 时钟周期约束 IO延迟约束 Tsu 建立时间 Th 保持时间 create_clock 时钟偏斜 input_delay output_delay 数据必须在窗口内稳定 Tclk2q + Tcomb + Tsu ≤ Tperiod max/min 都要给 三者共同决定芯片能否在目标频率下稳定工作 缺一不可,互相影响

2.5 三种约束的对比与总结

为了方便你对照记忆,我把三种约束的核心差异整理成了一张表:

约束类型 检查对象 关键参数 常见违例原因
建立时间 发射→捕获路径 Tsu、Tperiod、Tcomb 组合逻辑太长、时钟频率过高
保持时间 发射→捕获路径 Th、Tclk2q、Tcomb_min 路径太短、时钟偏斜过大
输入延迟 外部器件→内部寄存器 input_delay max/min 外部时序估计不准、未给min值
输出延迟 内部寄存器→外部器件 output_delay max/min 方向理解错误、外部setup/hold要求不匹配

一句话总结:建立时间管“够不够快”,保持时间管“会不会冲过头”,IO延迟管“芯片跟外面怎么配合”。三个都对了,时序约束的基础才算打牢。