保持时间(Hold Time)在数字电路中的定义

好了,咱们直接进入正题。我在给新来的同事讲时序时,发现大家对 setup time 通常理解得不错,但一提 hold time,就有点晕乎。说白了,hold time 就是数据在时钟沿采完之后,还得老老实实多待一会儿,不能马上跑掉。

为什么会有这个要求?因为时钟沿过来那一下子,锁存器内部的传输门不是瞬间关断的。你想想看,如果时钟沿刚过,数据输入端的信号就变了,那锁存器可能锁到的是新数据,而不是你想要的旧数据。嗯,这就是 hold 违例的根子。

Hold Time 的正式定义

教科书上的定义是这样写的:

保持时间(Hold Time,符号 \( t_{hold} \) 或 \( t_H \))是指在时钟有效沿到达之后,数据输入信号必须保持稳定不变的最小时间长度。

我个人的理解方式更直接。你在考场写卷子,考试结束铃响的那一瞬间(时钟沿),老师开始收卷。但你没写完,想再多写两笔。如果老师允许你铃响后还能写两秒,那这两秒就是 hold time。只不过在芯片里,我们希望铃响后数据立刻停笔,但锁存器有惯性,所以它要求你“铃响后继续稳定”一小段时间。

核心要点: Hold Time 是时钟沿之后的约束,而 Setup Time 是时钟沿之前的约束。两者都是对数据输入信号稳定窗口的限制。

为什么锁存器需要 Hold Time?

这个问题我在项目中跟不少同事争论过。其实归根结底是锁存器内部结构决定的。以最简单的 D 触发器为例,它内部由主锁存器和从锁存器构成,或者用传输门加反相器实现。

我当时在做一个 DDR PHY 的项目,遇到过一个奇怪的 hold 违例。后来追到电路级才发现,是因为时钟关断速度不够快,导致主锁存器在时钟沿后还处于透明状态,把新数据也放过去了。这就是 hold time 不够的典型表现。

如果你是做标准单元库的,你会更清楚:库里的时序参数(liberty file)直接定义了每个 flop 的 \( t_{hold} \) 值。这些值是经过 SPICE 仿真提取出来的,不是拍脑袋想的。

一个实用技巧: 我在做时序收敛时,如果遇到 hold 违例,第一反应不是去调 data path,而是先检查 clock path 有没有过长。因为 clock skew 大了,hold 会变差。这是最容易被忽略的。

Hold Time 与 Setup Time 的本质区别

很多初学者会把 setup 和 hold 搞混。其实它们的本质区别可以用一句话说明:

  • Setup Time:数据必须在时钟沿之前准备好。它跟时钟周期有关。频率越高,留给 setup 的时间越少。
  • Hold Time:数据必须在时钟沿之后保持稳定。它跟时钟周期无关。频率再低,hold 的要求也不会变。

我在给客户做时序培训时,经常拿这个图(下面我画的)来对比:

建立时间(Setup)与保持时间(Hold)对比框架 clk(时钟上升沿) 数据跳变 Setup 窗口 数据必须在这之前稳定 Hold 窗口 数据必须在这之后保持 Core Path Delay → hold 更长 Hold Check 的 Time Unit Setup 组 · 与时钟周期相关 · 高速设计时更紧张 Hold 组 · 与时钟周期无关 · 低频下同样关键 注:Self-Refresh 场景下,hold 违例通常由低电压、低温、工艺角(FF)引起,而非频率 因此修复 hold 需要增加 buffer 而非降频

我的一点体会: 做 Self-Refresh 这类低功耗设计时,hold 违例比 setup 更让人头疼。因为 setup 还能降频或降电压来救,但 hold 不行。你降频对 hold 没影响,降电压反而让 cell delay 变大,hold 更差。所以 hold 通常是后端 APR 阶段最后才收敛的。

Hold Time 的 Slack 计算

在静态时序分析中,hold 的 slack 是这样算的:

Hold Slack = Data_Arrival_Time - Data_Required_Time

其中:
Data_Required_Time = Launch_Clock_Edge + t_hold
Data_Arrival_Time  = Capture_Clock_Edge + Clock_Skew + Path_Delay

这个公式你可能觉得复杂。我教你一个记忆方法:

  • 你要确保数据来得不够快 — 如果数据来得太快,在时钟沿还没到的时候就先到了捕获触发器,那就会抢跑,产生 hold 违例。
  • 所以 hold 实际上是检查“数据最早可能的到达时间”是否晚于“允许的最早到达时间”。

避坑指南——我曾经犯过的错: 我一两年之前做一个 MCU 项目,Self-Refresh 模式下 hold 违例总是修不完。我一开始认为是 clock skew 太大,拼命修时钟树。后来才发现,最根本的原因是某个 low-power cell 在休眠模式下漏电太大,导致数据路径上的 buffer 输出变慢。这不是 hold 本身的问题,是工艺-电压-温度组合导致的。所以我建议你,遇到怪异的 hold 违例,先查 PVT 条件。

Hold Time 的 Practical 含义

在实际工程中,hold 违例的修复手段不多,归纳下来就这几招:

修复手段 原理 局限性
插入 buffer 增加数据路径延迟 增加面积和功耗
调整 clock skew 让捕获时钟更晚到 可能破坏其他路径的 setup
换用高阈值电压单元 利用工艺角,使 cell delay 变大 低频路径上可行,高频路径慎用
改变连线结构 增加线延迟 对深亚微米工艺效果有限

我个人建议,在 Self-Refresh 场景中优先考虑插入 buffer。为什么?因为 Self-Refresh 模式通常工作在低频甚至极低频,功耗约束比高频模式宽松,buffer 增加的动态功耗占比很小。而且你插入 buffer 不会影响 setup,因为 setup 跟时钟周期有关,低频下 setup 通常不紧张。

总结一下: 保持时间(Hold Time)是你数据在时钟沿之后必须“定住”的时间窗口。它跟频率无关,跟工艺、电压、温度强相关。在 Self-Refresh 这种 low-power 场景下,hold 违例往往在 FF corner(快工艺、高电压、低温)最严重。别问我怎么知道的——debug 过一次你就忘不了。

最后说一句

我看到不少教程喜欢把 hold 时间画在波形图上,标注“数据必须保持的区域”。这当然没错,但我觉得更重要的是你能想到“它的物理来源是什么”。是锁存器内部的脉冲宽度、传输门关断速度、寄生电容。你理解了这个,就不会被奇怪的 hold 违例搞懵。