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 知识体系总览
下面这张图把本章的核心概念串起来了。你可以看到,从时钟周期约束出发,经过建立/保持时间检查,再到输入输出延迟,构成了完整的时序约束基础框架。
2.5 三种约束的对比与总结
为了方便你对照记忆,我把三种约束的核心差异整理成了一张表:
| 约束类型 | 检查对象 | 关键参数 | 常见违例原因 |
|---|---|---|---|
| 建立时间 | 发射→捕获路径 | Tsu、Tperiod、Tcomb | 组合逻辑太长、时钟频率过高 |
| 保持时间 | 发射→捕获路径 | Th、Tclk2q、Tcomb_min | 路径太短、时钟偏斜过大 |
| 输入延迟 | 外部器件→内部寄存器 | input_delay max/min | 外部时序估计不准、未给min值 |
| 输出延迟 | 内部寄存器→外部器件 | output_delay max/min | 方向理解错误、外部setup/hold要求不匹配 |
一句话总结:建立时间管“够不够快”,保持时间管“会不会冲过头”,IO延迟管“芯片跟外面怎么配合”。三个都对了,时序约束的基础才算打牢。