第二讲:时序路径与约束——建立时间与保持时间
各位同学,今天我们来聊聊时序分析里最基础、也最容易被忽视的内容——建立时间和保持时间。我见过不少工程师,跑STA时看到violation就慌,其实说白了,很多问题都是对这两个概念理解不透彻造成的。
2.1 建立时间与保持时间:芯片的“心跳”规则
先问大家一个问题:为什么寄存器需要满足建立时间和保持时间?
你可以把寄存器想象成一个“采样器”。时钟上升沿来临时,它要抓取D端的数据。但这个抓取动作不是瞬间完成的——它需要数据在时钟沿之前稳定一段时间,这叫建立时间(Setup Time);也需要在时钟沿之后继续稳定一段时间,这叫保持时间(Hold Time)。
核心定义:
- 建立时间(Tsu):数据在时钟有效沿之前必须保持稳定的最短时间。
- 保持时间(Th):数据在时钟有效沿之后必须保持稳定的最短时间。
我刚开始做设计时,总觉得保持时间不重要。心想:“数据都采进去了,后面稳不稳有什么关系?”结果有一次在28nm项目里,一个计数器在高速模式下频繁出错,查了三天才发现是保持时间违例。从那以后,我再也不敢小看保持时间了。
我的经验:建立时间决定了你的芯片能跑多快(频率上限),保持时间决定了你的芯片能不能稳定工作(功能正确性)。两者缺一不可。
2.2 时序路径的四大分类
在STA中,我们把所有时序路径归纳为四大类。你想想看,一个芯片里无非就是输入引脚、寄存器、输出引脚这三类节点,它们之间的组合就构成了所有路径。
| 路径类型 | 起点 | 终点 | 典型场景 |
|---|---|---|---|
| 输入到寄存器 | 输入引脚 | 寄存器D端 | 片外数据进入芯片第一级寄存器 |
| 寄存器到寄存器 | 寄存器Q端 | 寄存器D端 | 核心逻辑路径,最常分析 |
| 寄存器到输出 | 寄存器Q端 | 输出引脚 | 芯片数据输出到片外 |
| 输入到输出 | 输入引脚 | 输出引脚 | 纯组合逻辑路径 |
2.2.1 输入到寄存器路径
这条路径的起点是芯片的输入引脚,终点是内部寄存器的D端。说白了,就是外部数据进入芯片后,经过一些组合逻辑,最终被第一级寄存器采样的过程。
约束这条路径时,我们需要告诉工具:外部数据相对于时钟的到达时间是多少?这就是输入延迟(Input Delay)的概念。
// 典型的输入延迟约束示例
set_input_delay -clock clk -max 2.5 [get_ports data_in]
set_input_delay -clock clk -min 0.8 [get_ports data_in]
我个人习惯把输入延迟分成两部分:片外延迟(数据从外部器件到芯片引脚的时间)和片内延迟(从引脚到第一级寄存器的组合逻辑延迟)。分析时,建立时间看最大路径,保持时间看最小路径。
2.2.2 寄存器到寄存器路径
这是最核心的路径类型,也是我们花时间最多的地方。起点是前一级寄存器的Q端,终点是后一级寄存器的D端,中间经过组合逻辑。
建立时间的检查公式很简单:
T_clk_q + T_comb + T_setup <= T_period + T_skew
其中:
- T_clk_q:寄存器从时钟沿到Q端输出的延迟
- T_comb:组合逻辑延迟
- T_setup:后级寄存器的建立时间要求
- T_period:时钟周期
- T_skew:时钟偏斜(后级时钟减去前级时钟)
注意:保持时间检查与时钟周期无关!它只关心数据能不能在时钟沿之后保持稳定。公式是:
T_clk_q + T_comb >= T_hold + T_skew
我曾经在一个项目中,为了优化建立时间,把组合逻辑推到了寄存器后面,结果保持时间全红了。嗯,这就是典型的“拆东墙补西墙”。
2.2.3 寄存器到输出路径
这条路径从内部寄存器的Q端出发,经过输出缓冲逻辑,最终到达芯片的输出引脚。约束时用输出延迟(Output Delay)来描述外部器件对数据到达时间的要求。
// 输出延迟约束示例
set_output_delay -clock clk -max 4.0 [get_ports data_out]
set_output_delay -clock clk -min 1.2 [get_ports data_out]
这里有个容易混淆的点:输出延迟是相对于时钟沿的。最大输出延迟对应建立时间检查(数据要早于外部器件的建立时间要求),最小输出延迟对应保持时间检查。
2.2.4 输入到输出路径
这条路径没有寄存器参与,纯粹是组合逻辑从输入引脚到输出引脚。说实话,在同步设计中,我建议尽量避免这种路径。为什么?因为它的时序完全不可控,完全依赖组合逻辑的延迟。
如果实在无法避免,可以用虚拟时钟(Virtual Clock)来约束:
create_clock -name vclk -period 10
set_input_delay -clock vclk -max 2.0 [get_ports in]
set_output_delay -clock vclk -max 3.0 [get_ports out]
避坑指南:我曾经在一个ADC接口设计中,输入到输出路径的延迟占了整个周期的60%,导致系统频率上不去。后来加了一级流水线寄存器,把路径拆成了“输入到寄存器”和“寄存器到输出”两条,频率直接翻倍。所以,能用寄存器的地方,千万别省。
2.3 四大路径的约束要点总结
好了,我们来捋一捋。这四大路径的约束,说白了就是回答四个问题:
- 输入到寄存器:外部数据什么时候来?——用input delay约束
- 寄存器到寄存器:内部逻辑能不能在一个周期内算完?——靠综合和STA分析
- 寄存器到输出:外部器件什么时候要数据?——用output delay约束
- 输入到输出:纯组合路径,尽量别用,用了要加虚拟时钟
我个人习惯在做完综合后,先跑一遍STA,看看这四类路径的slack分布。如果寄存器到寄存器的路径slack很紧张,我会优先优化它——因为它影响的是核心逻辑,改起来也最直接。
下一讲我们会深入讨论如何编写完整的时序约束文件。到时候我会拿一个实际项目中的SDC文件来拆解,大家记得跟上。