保持时间(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 的要求也不会变。
我在给客户做时序培训时,经常拿这个图(下面我画的)来对比:
我的一点体会: 做 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 违例搞懵。