Appearance
时序分析:建立时间、保持时间、亚稳态
概念
到这里,L1 的逻辑部分已经完整:从门到 ALU,从触发器到状态机。理论上,一台 CPU 的全部电路都能设计出来了。
但那个电路能不能真的跑起来,是另一个问题。
例子:两个触发器用一根很长的组合逻辑连起来。逻辑上完全正确——但组合逻辑太慢,数据还没传到位,下一个时钟沿就到了,捕获触发器采到了一个中间值。
时序分析(timing analysis)要回答的就是三个问题:
| 问题 | 判据 |
|---|---|
| 最快能跑多快? | 最高时钟频率 |
| 会不会偶尔采错? | 保持时间约束(与频率无关!) |
| 采错了会怎样? | 亚稳态——输出卡在 0 和 1 之间 |
整章的骨架只有两个不等式:
先记住这两行,剩下的全是理解它们。
本层(L1 电路)在这里回答了上一层(L2 组成原理)最工程化的那个问题:L2 讲流水线时反复出现的"流水线为什么有频率上限""为什么插入流水线寄存器能提速",答案就在这一篇。这一篇也是 L1 的收口——它把"电路"和"能跑的电路"这两件事分开。
原理
一、四个参数
从时钟沿到输出:
| 参数 | 名称 | 含义 |
|---|---|---|
| 时钟到输出延迟(clock-to-Q) | 时钟沿到 | |
| 组合逻辑延迟 | 数据从 | |
| 建立时间 | 时钟沿之前, | |
| 保持时间 | 时钟沿之后, |
再加两个"坏东西":
| 参数 | 含义 |
|---|---|
| 同一时钟沿到达不同触发器的时间差(时钟树不平衡) | |
| 时钟周期本身的随机波动(PLL 噪声) |
关键物理事实(13 篇讲过):
二、标准时序通路:发射 → 组合 → 捕获
同步电路里所有的时序检查都在"两个触发器之间":
发射触发器 捕获触发器
┌───────────┐ ┌───────────┐
│ │ 组合逻辑 │ │
│ FF1 Q ├──→[ t_comb ]──┬───→│ D FF2 │
│ ↑ │ │ │ ↑ │
└─────┼─────┘ │ └─────┼─────┘
clk │ │ clk │
┌────────┴─────────────────────┴──────────┴──────┐
│ 时钟网络(有偏斜 t_skew) │
└────────────────────────────────────────────────┘
时间轴:
t=0 数据离开 FF1 (t_co)
↓ ↓
├──────────────┼───────────┼──────────────┤
时钟沿 Q 稳定 D 到达 FF2 采样
← t_su →定义:
正偏斜 = 捕获端的时钟更晚到(这一约定必须记牢,后面全部结论都基于它)。
三、建立时间检查(决定 )
问题:数据必须在捕获沿之前
数据到达时刻(以发射沿为 0):
捕获沿到来的时刻:捕获触发器晚
建立条件:数据到达 ≤ 捕获沿 −
读这个式子:
| 想提高 | 怎么办 |
|---|---|
| 换更快的器件 | |
| 缩短关键路径的组合逻辑( | 最常做:插入流水线寄存器、逻辑重定时 |
| 增大正偏斜( | 危险——会恶化保持时间(见第五节) |
式子里的减号就是 L2 里那句"流水线靠增加寄存器切短组合逻辑来提高频率"的定量依据。
一变小, 就能变小,频率就上去了。
四、保持时间检查(与频率无关!)
问题:同一个时钟沿,数据不能来得太快——否则会把捕获触发器里"正要被采的旧值"冲掉。
关键点:这次是同一个周期的事(发射沿和捕获沿是同一个沿),所以周期
必须用
★ 最重要的反直觉结论:
降频救不了保持违例。 因为保持检查只看"同一个沿前后"的关系,周期
被完全消掉了。 这条结论的工程含义:保持违例必须靠改电路(插延时单元、缩短时钟偏斜)解决,靠降频是治不好的。 这也是"为什么流片后才发现 hold 违例极其昂贵"的原因。
两种违例的定位口诀:
| 建立违例(setup) | 保持违例(hold) | |
|---|---|---|
| 关心的路径 | 最长路径( | 最短路径( |
| 与 | 有关(降频可救) | 无关(降频不救) |
| 修复手段 | 缩短组合逻辑、加流水线 | 给路径加延迟、减小时钟偏斜 |
| 直观感觉 | "数据来得太慢" | "数据来得太快" ⚠️ |
"给路径加延迟来修 hold"是最反直觉的一条——为了让数据慢点来,你在路径上插缓冲器。这与"修 setup 要加快数据"正好相反。
五、时钟偏斜:对两种检查的影响相反
代入两个不等式:
| 检查 | 不等式 | 正偏斜(捕获沿更晚)的效果 |
|---|---|---|
| Setup | 更容易满足(多了 | |
| Hold | 更容易违例(要求多撑 |
所以时钟偏斜不是"越小越好"这么简单:
| 偏斜 | 后果 |
|---|---|
| 正偏斜太大 | hold 违例(低频也挂) |
| 负偏斜太大 | setup 违例( |
| 偏斜为 0 | 最安全;工程上目标是把偏斜控制在很小的窗口内(用平衡的时钟树) |
工程现实:芯片里时钟要送到几十万个触发器,走线长度不可能完全一致 → 偏斜必然存在。所以时钟树综合(CTS)是后端设计里专门的一个阶段,目标就是把偏斜压到几十皮秒以内。
六、亚稳态:为什么会"卡在中间"
13 篇讲过:双稳态电路有两个稳定点(存 0 / 存 1),中间有一个不稳定平衡点。
违反
- 捕获触发器内部的两个门同时被推,状态既不像 0 也不像 1
- 它会停留在这个中间态一段时间,然后随机地倒向 0 或 1
- 如果下游电路在这段时间里采样 → 拿到随机值
三个危险特性:
| 特性 | 后果 |
|---|---|
| 输出电平既非 0 也非 1 | 下游组合逻辑可能同时导通上下管(短路的静态功耗) |
| 输出电平不确定 | 不同分支采到不同值 → 同一份数据算出不同结果 |
| 传播 | 亚稳态会沿着组合逻辑扩散,污染一大片电路 |
MTBF(平均无故障时间)公式:
| 符号 | 含义 |
|---|---|
| 留给亚稳态恢复的时间(≈ 时序裕量 slack) | |
| 器件的亚稳态时间常数(工艺决定,典型 10~100 ps) | |
| 亚稳态窗口宽度常数(工艺决定) | |
| 时钟频率 | |
| 数据变化频率(异步事件到达率) |
这个公式最重要的特征是
指数级增长——所以工程上"多给 1~2 ns 的裕量"能把 MTBF 从"几秒"提到"几千年"(见例 5)。
标准解法:跨时钟域用两级同步器
异步输入 ──→┌─────┐ ┌─────┐ ┌──────────┐
(CDC) │ FF1 │──→│ FF2 │──→│ 同步后逻辑 │
└──┬──┘ └──┬──┘ └──────────┘
│ │
clk ──────────┴─────────┘
↑
可能亚稳态 → 但有一整个周期的时间恢复
到 FF2 采样时,FF1 已经稳定(概率上)核心思想:承认第一级可能亚稳态,但给它一整个时钟周期去恢复,第二级采样时它已经稳定。两级不够就三级——每加一级,
注意:多级同步器只能降低"传播亚稳态的概率",不能消除它。所以 CDC(跨时钟域)设计有一整套规则(格雷码指针、握手协议、异步 FIFO),这是数字设计里独立的一个专题。
七、流水线:提高吞吐,恶化延迟
这是时序分析最直接的应用。
原始电路:一个时钟周期内要穿过很长的组合逻辑:
┌─────┐ ┌──────────────────────────┐ ┌─────┐
│ FF1 ├──→│ t_comb = 7.5 ns ├──→│ FF2 │
└─────┘ └──────────────────────────┘ └─────┘
T = t_co(0.5) + 7.5 + t_su(0.3) + ... ≈ 8.5 ns → 117.6 MHz插入寄存器切成 3 段:
┌─────┐ ┌────────┐ ┌─────┐ ┌────────┐ ┌─────┐ ┌────────┐ ┌─────┐
│ FF1 ├─→│ 2.5 ns ├─→│ FF2 ├─→│ 2.5 ns ├─→│ FF3 ├─→│ 2.5 ns ├─→│ FF4 │
└─────┘ └────────┘ └─────┘ └────────┘ └─────┘ └────────┘ └─────┘
T = 0.5 + 2.5 + 0.3 + 0.2 = 3.5 ns → 285.7 MHz| 指标 | 原始 | 3 段流水线 | 变化 |
|---|---|---|---|
| 时钟周期 | 8.5 ns | 3.5 ns | ↓ |
| 117.6 MHz | 285.7 MHz | ↑ 2.43× | |
| 吞吐率 | 1/8.5 ns | 1/3.5 ns | ↑ 2.43× |
| 单条数据延迟(latency) | 8.5 ns | ↑ 变差 ⚠️ |
为什么延迟会变差:数据要在更多个寄存器里各停留一拍,而寄存器的时序开销(
这就是 L2 里"流水线深度不是越深越好"的定量解释:每加一级,吞吐率最多提升一点,但寄存器开销线性累加、延迟线性增长、面积与功耗线性上升。所以存在一个最优流水线深度。
八、IO 时序:把边界条件接进来
芯片不是孤岛,输入输出还得和外部器件对时序:
| 方向 | 条件 | 说明 |
|---|---|---|
| 输入 | 外部器件的 | |
| 输出 | 外部器件的 |
这就是"建立/保持时间"在芯片引脚上的版本,也是 SDRAM、Flash、SPI 等接口都要给"时序参数表"的原因。
九、静态时序分析(STA)在做什么
前面所有计算都可以手工做,但芯片里有几百万条路径。静态时序分析(STA)工具做的事就是:
① 从网表提取所有"发射-捕获"通路(不依赖输入激励,所以叫"静态")
② 对每条路径算 t_co + t_comb + t_su 与 T + t_skew 比较 → slack
③ 报告负 slack 的路径(违例)并按严重程度排序
④ 关键路径(最差 slack)决定 f_max| 概念 | 定义 |
|---|---|
| slack(裕量) | 实际时间 − 要求时间;负 slack = 违例 |
| 关键路径 | slack 最小的那条路径,决定 |
| 为什么叫"静态" | 不需要输入激励,覆盖所有路径 ∵ 假设任意输入都会出现 |
STA 是唯一能"穷尽所有路径"的方法——仿真是"跑一遍看看",永远跑不完;STA 是"把每条路径都算一遍"。这是数字设计的工程基石。
示例
例 1:求最高时钟频率(完整计算)
某电路的寄存器到寄存器通路:
, , ,正偏斜 ,时钟抖动 。求 。
第 1 步:套建立不等式。
第 2 步:抖动要另算。
第 3 步:算频率。
第 4 步:检查保持条件。
设
结论:
顺带算一下:如果
| 3.1 ns | 322.6 MHz | |
| 2.9 ns | 344.8 MHz | |
| 2.7 ns | 370.4 MHz |
看出来了吗:正偏斜把频率"免费"提上去了——但代价在下一节的保持时间上。天下没有免费的时序裕量。
例 2:保持时间违例的定位与修复(反直觉!)
同一条通路的数据:
,最短组合延迟 , , 。检查保持条件。
第 1 步:算左边和右边。
第 2 步:判定。
第 3 步:试"降频"——无效!
保持不等式里没有
第 4 步:正确修复——给最短路径"加延迟"。
在最短路径上插入缓冲器(两级反相器),把
刚好卡在边界上,工程上不能接受(温度、电压波动会让它随时变成违例)。再多插一级,把
修复方案对照表:
| 方案 | 是否有效 | 理由 |
|---|---|---|
| 降频 | ✗ 无效 | 不等式里没有 |
| 缩短时钟偏斜 | ✓ 有效 | 右边变小 |
| 给最短路径插缓冲器 | ✓ 有效 | 左边变大(反直觉的"加延迟") |
| 换更大 | ✗ | 方向反了( |
| 换更大 | ✓ 有效 | 左边变大(但通常没得选) |
工程现实:hold 违例一般在后端布局后才暴露(因为布线延迟才知道),修起来要"插 buffer、调时钟树",代价极高。所以设计阶段就要避免过短的寄存器到寄存器路径(比如两个触发器之间只隔一个门)。
例 3:时钟偏斜的"两面性"(定量对照)
用例 1 的参数,系统性地看
从 到 时,setup 裕量和 hold 裕量怎么变。
设:
两个裕量公式:
代入
| setup 裕量(ns) | hold 裕量(ns) | 判定 | |
|---|---|---|---|
| ✗ setup 违例 | |||
| ✓ | |||
| ✓ | |||
| ✓ | |||
| ✓ | |||
| ✗ hold 违例 |
结论:
- 负偏斜 → setup 更容易挂(因为捕获沿来得早,留给数据的时间变少)
- 正偏斜 → hold 更容易挂(因为"必须继续稳定"的时间被延长)
这条"两面性"是时钟树设计的根本矛盾:你不会为了让某一类路径的 setup 好看,就把偏斜调大——那会让另一类路径的 hold 挂掉。唯一的出路是把偏斜整体压小。
顺便回答一个常见疑问:"既然正偏斜能提高
,为什么不故意做正偏斜?" 因为 由所有路径中最差的那条 setup 决定,而 hold 违例由任意一条 hold 违例决定——两者都要满足。故意加偏斜只是"把 setup 的问题挪到 hold 上",总账不会变好。
例 4:流水线的定量账(完整计算)
某组合逻辑总延迟
, , , (忽略抖动)。比较"不流水线"和"切 3 段流水线"。
第 1 步:不流水线。
第 2 步:切 3 段。 每段组合逻辑约
第 3 步:对比。
| 指标 | 不流水线 | 3 段流水线 | 变化 |
|---|---|---|---|
| 时钟周期 | 8.5 ns | 3.5 ns | 0.41× |
| 时钟频率 | 117.6 MHz | 285.7 MHz | 2.43× |
| 吞吐率(每秒处理的运算数) | 2.43× | ||
| 单条延迟 | 8.5 ns | 1.24×(变差) | |
| 寄存器数量 | 2 | 4 | 2× |
| 面积/功耗 | — | 更大 | ↑ |
第 4 步:算"收益衰减"。
理想情况(忽略
如果再切 6 段(每段
相对 3 段只提升 1.56 倍(而寄存器数量翻倍)——收益在快速衰减。
这就是"流水线深度不是越深越好"的定量证据:
| 段数 | 相对增益 | 单条延迟 | 寄存器数 | ||
|---|---|---|---|---|---|
| 1 | 8.5 ns | 117.6 MHz | 1.00× | 8.5 ns | 2 |
| 3 | 3.5 ns | 285.7 MHz | 2.43× | 10.5 ns | 4 |
| 6 | 2.25 ns | 444.4 MHz | 3.78× | 13.5 ns | 7 |
| ∞ | 1.0 ns(极限) | 1000 MHz | 8.5× | ∞ | ∞ |
极限:
这个"1.0 ns 地板"就是 L2 里"流水线不可能无限深"的根本原因。 数学上的完美上界:
。
例 5:亚稳态 MTBF 的指数敏感性
某带两级同步器的跨时钟域电路:
, ,接收时钟 ,异步数据到达率 。求不同时序裕量 下的 MTBF。
第 1 步:算分母(与
注意单位:
第 2 步:代入不同
| MTBF | 换算 | ||
|---|---|---|---|
| 1.0 ns | 0.22 秒就出错一次 | ||
| 1.5 ns | 32.7 s | 半分钟一次 | |
| 2.0 ns | 1.35 小时 | ||
| 2.5 ns | 8.3 天 | ||
| 3.0 ns | 3.4 年 | ||
| 4.0 ns | 约 7.5 万年 |
第 3 步:读这张表。
- 每多给 0.1 ns(= 一个
)的恢复时间,MTBF 就乘以 - 从
ns 到 ns,只多了 3 ns,MTBF 从 0.22 秒变成 7.5 万年(跨 11 个数量级)
第 4 步:怎么把
| 手段 | 效果 |
|---|---|
| 两级同步器(多给一整个周期) | |
| 三级同步器 | 再多一个周期 → 量级再上一档 |
| 降低接收时钟频率 | 周期变长 → |
| 选用更小 |
对比一下"降频"在两个场景下的不同疗效:
- hold 违例:降频无效(不等式里没有
) - 亚稳态:降频有效(
就是一个周期, 越大越好) 这两个结论都出自"懂公式",而不是"背口诀"。
例 6:用程序复算
#include <stdio.h>
/* 时序检查:setup / hold 裕量 与 f_max */
static void timing_check(const char *name,
double tco, double tcomb_max, double tcomb_min,
double tsu, double th, double tskew, double jitter,
double T) {
double setup_slack = T + tskew - (tco + tcomb_max + tsu) - jitter;
double hold_slack = tco + tcomb_min - th - tskew;
double Tmin = tco + tcomb_max + tsu - tskew + jitter;
printf("%-18s setup裕量=%+.3f ns hold裕量=%+.3f ns "
"Tmin=%.3f ns fmax=%.1f MHz %s\n",
name, setup_slack, hold_slack, Tmin, 1000.0 / Tmin,
(setup_slack >= 0 && hold_slack >= 0) ? "OK" : "VIOLATION");
}
int main(void) {
printf("--- 例1 求 fmax ---\n");
timing_check("例1", 0.5, 2.0, 1.6, 0.3, 0.2, 0.2, 0.1, 2.7);
timing_check("skew=-0.2", 0.5, 2.0, 1.6, 0.3, 0.2, -0.2, 0.1, 3.1);
timing_check("skew=0", 0.5, 2.0, 1.6, 0.3, 0.2, 0.0, 0.1, 2.9);
printf("\n--- 例2 hold 违例与修复 ---\n");
timing_check("原始(违例)", 0.3, 2.0, 0.10, 0.3, 0.5, 0.1, 0.0, 3.0);
timing_check("降频10倍", 0.3, 2.0, 0.10, 0.3, 0.5, 0.1, 0.0, 30.0);
timing_check("插buffer+0.25", 0.3, 2.0, 0.35, 0.3, 0.5, 0.1, 0.0, 3.0);
timing_check("插buffer+0.4", 0.3, 2.0, 0.50, 0.3, 0.5, 0.1, 0.0, 3.0);
printf("\n--- 例3 偏斜两面性(T=3.0ns) ---\n");
double skews[6] = {-0.3, -0.1, 0.0, 0.1, 0.25, 2.0};
for (int i = 0; i < 6; i++)
timing_check("skew系列", 0.5, 2.0, 1.6, 0.3, 0.2, skews[i], 0.0, 3.0);
printf("\n--- 例4 流水线 ---\n");
double tco = 0.5, tsu = 0.3, tskew = 0.2, tc = 7.5;
double T0 = tco + tc + tsu + tskew;
int stages[3] = {1, 3, 6};
for (int k = 0; k < 3; k++) {
int n = stages[k];
double T = tco + tc / n + tsu + tskew;
double gain = T0 / T; /* 吞吐增益 */
printf(" %d 段: T=%.3f ns f=%.1f MHz 单条延迟=%.2f ns "
"吞吐增益=%.2fx 效率=%.0f%%\n",
n, T, 1000.0 / T, n * T, gain, 100.0 * gain / n);
}
printf(" 极限: T_min = tco+tsu+tskew = %.2f ns -> f = %.0f MHz\n",
tco + tsu + tskew, 1000.0 / (tco + tsu + tskew));
printf("\n--- 例5 MTBF ---\n");
double tau = 0.1, T0 = 0.1e-9, fclk = 100e6, fdata = 10e6;
double denom = T0 * fclk * fdata;
double trs[6] = {1.0, 1.5, 2.0, 2.5, 3.0, 4.0};
for (int i = 0; i < 6; i++) {
double mtbf = exp(trs[i] / tau) / denom;
printf(" t_r=%.1f ns -> MTBF=%.4g s (%.4g 年)\n",
trs[i], mtbf, mtbf / 3.1536e7);
}
return 0;
}
c 本站为静态站,不提供在线运行;可复制到本地用 gcc / python 执行
预期输出(关键行):
--- 例1 求 fmax ---
例1 setup裕量=+0.000 ns hold裕量=+1.700 ns Tmin=2.700 ns fmax=370.4 MHz OK
skew=-0.2 setup裕量=+0.000 ns hold裕量=+2.100 ns Tmin=3.100 ns fmax=322.6 MHz OK
skew=0 setup裕量=+0.000 ns hold裕量=+1.900 ns Tmin=2.900 ns fmax=344.8 MHz OK
--- 例2 hold 违例与修复 ---
原始(违例) setup裕量=+0.500 ns hold裕量=-0.200 ns Tmin=2.500 ns fmax=400.0 MHz VIOLATION
降频10倍 setup裕量=+27.500 ns hold裕量=-0.200 ns Tmin=2.500 ns fmax=400.0 MHz VIOLATION
插buffer+0.25 setup裕量=+0.500 ns hold裕量=+0.050 ns Tmin=2.500 ns fmax=400.0 MHz OK
插buffer+0.4 setup裕量=+0.500 ns hold裕量=+0.200 ns Tmin=2.500 ns fmax=400.0 MHz OK
--- 例3 偏斜两面性(T=3.0ns) ---
skew系列 setup裕量=-0.100 ns hold裕量=+2.200 ns Tmin=3.300 ns fmax=303.0 MHz VIOLATION
skew系列 setup裕量=+0.100 ns hold裕量=+2.000 ns Tmin=3.100 ns fmax=322.6 MHz OK
skew系列 setup裕量=+0.200 ns hold裕量=+1.900 ns Tmin=3.000 ns fmax=333.3 MHz OK
skew系列 setup裕量=+0.300 ns hold裕量=+1.800 ns Tmin=2.900 ns fmax=344.8 MHz OK
skew系列 setup裕量=+0.450 ns hold裕量=+1.650 ns Tmin=2.750 ns fmax=363.6 MHz OK
skew系列 setup裕量=+2.200 ns hold裕量=-0.100 ns Tmin=1.000 ns fmax=1000.0 MHz VIOLATION
--- 例4 流水线 ---
1 段: T=8.500 ns f=117.6 MHz 单条延迟=8.50 ns 相对1段增益=1.00x
3 段: T=3.500 ns f=285.7 MHz 单条延迟=10.50 ns 相对1段增益=2.43x
6 段: T=2.250 ns f=444.4 MHz 单条延迟=13.50 ns 相对1段增益=3.78x
极限: T_min = tco+tsu+tskew = 1.00 ns -> f = 1000 MHz
--- 例5 MTBF ---
t_r=1.0 ns -> MTBF=0.2203 s (6.99e-09 年)
t_r=2.0 ns -> MTBF=4851 s (0.0001539 年)
t_r=3.0 ns -> MTBF=1.069e+08 s (3.389 年)
t_r=4.0 ns -> MTBF=2.354e+12 s (7.463e+04 年)例 2 的第一行要注意:这条通路 setup 裕量是正的(+0.5 ns),却因为 hold 违例而不可用——"setup 过了不代表能用"。第二行是"降频到 30 ns"(
从 3.0 变 30.0,周期放大 10 倍),hold 裕量一个字没变( ns),依然违例——这就是"降频救不了 hold"的现场证据。
import math
# ============ 时序检查工具 ============
def check(name, tco, tcmax, tcmin, tsu, th, tskew, jitter=0.0, T=None):
Tmin = tco + tcmax + tsu - tskew + jitter
fmax = 1 / Tmin * 1000 # MHz(T 以 ns 计)
if T is None:
T = Tmin
s_setup = T + tskew - (tco + tcmax + tsu) - jitter
s_hold = tco + tcmin - th - tskew
ok = s_setup >= -1e-12 and s_hold >= -1e-12
print(f" {name:<16} T={T:6.2f}ns setup裕量={s_setup:+7.3f} "
f"hold裕量={s_hold:+7.3f} Tmin={Tmin:5.2f}ns fmax={fmax:7.1f}MHz "
f"{'OK' if ok else 'VIOLATION'}")
return Tmin, fmax, s_setup, s_hold
print("=== 例1 求 fmax(tco=0.5 tcomb=2.0 tsu=0.3 skew=+0.2 jitter=0.1) ===")
for sk in (-0.2, 0.0, 0.2):
check(f"skew={sk:+.1f}", 0.5, 2.0, 1.6, 0.3, 0.2, sk, 0.1)
print("\n=== 例2 hold 违例与修复 ===")
check("原始", 0.3, 2.0, 0.10, 0.3, 0.5, 0.1, 0.0, T=3.0)
check("降频10倍", 0.3, 2.0, 0.10, 0.3, 0.5, 0.1, 0.0, T=30.0) # hold 裕量不变!
check("插buffer+0.25", 0.3, 2.0, 0.35, 0.3, 0.5, 0.1, 0.0, T=3.0)
check("插buffer+0.4", 0.3, 2.0, 0.50, 0.3, 0.5, 0.1, 0.0, T=3.0)
print("\n=== 例3 偏斜两面性(T=3.0ns 固定) ===")
print(" skew setup裕量 hold裕量")
for sk in (-0.3, -0.1, 0.0, 0.1, 0.25, 2.0):
s = 3.0 + sk - (0.5 + 2.0 + 0.3)
h = 0.5 + 1.6 - 0.2 - sk
print(f" {sk:+.2f} {s:+.3f} {h:+.3f}"
f" {'setup违例' if s < 0 else ('hold违例' if h < 0 else '')}")
print("\n=== 例4 流水线(tco=0.5 tcomb=7.5 tsu=0.3 skew=0.2) ===")
tco, tc, tsu, tsk = 0.5, 7.5, 0.3, 0.2
T0 = tco + tc + tsu + tsk
print(f" 不流水线: T={T0:.2f}ns f={1000/T0:.1f}MHz 单条延迟={T0:.2f}ns")
for n in (2, 3, 4, 6, 10):
T = tco + tc / n + tsu + tsk
print(f" {n:2d} 段: T={T:5.3f}ns f={1000/T:7.1f}MHz "
f"吞吐增益={T0/T:5.2f}x (理想{n}x, 效率{T0/T/n*100:5.1f}%) "
f"单条延迟={n*T:6.2f}ns")
Tlim = tco + tsu + tsk
print(f" 极限(组合逻辑=0): T={Tlim:.2f}ns -> f={1000/Tlim:.0f}MHz "
f"(寄存器开销地板)")
print("\n=== 例5 亚稳态 MTBF(tau=0.1ns, T0=0.1ns, fclk=100MHz, fdata=10MHz) ===")
tau, T0c, fclk, fdata = 0.1, 0.1e-9, 100e6, 10e6
denom = T0c * fclk * fdata
print(f" 分母 T0*fclk*fdata = {denom:g} s")
for tr in (1.0, 1.5, 2.0, 2.5, 3.0, 4.0):
mtbf = math.exp(tr / tau) / denom
yr = mtbf / 3.1536e7
print(f" t_r={tr:.1f}ns -> e^(t/τ)={math.exp(tr/tau):.4g} "
f"MTBF={mtbf:.4g} s = {yr:.4g} 年")
print(f" 每增加一个 τ=0.1ns,MTBF 乘以 e={math.e:.3f}")
print(f" 验证: MTBF(2.0)/MTBF(1.9) = "
f"{math.exp(2.0/tau)/math.exp(1.9/tau):.3f}")
print("\n=== 附:降频对两种故障的不同疗效 ===")
for T in (1.0, 3.0, 30.0):
h = 0.3 + 0.1 - 0.5 - 0.1 # hold 裕量,与 T 无关
print(f" T={T:5.1f}ns hold裕量={h:+.2f}ns (恒定) ; "
f"MTBF 分母不变但 t_r 增大约 {T:.1f} 倍")
python 本站为静态站,不提供在线运行;可复制到本地用 gcc / python 执行
输出要点:
- 例 1:
从 到 , 从 322.6 → 370.4 MHz(正偏斜"白送"频率)。 - 例 2:原始 hold 裕量
ns;把周期从 3 ns 放大到 30 ns,hold 裕量纹丝不动;把最短路径延迟从 0.1 加到 0.3 ns 后转正。 - 例 3:负偏斜导致 setup 违例,正偏斜 2.0 ns 时反而导致 hold 违例——两面性。
- 例 4:1/3/6 段分别为 117.6 / 285.7 / 444.4 MHz;效率从 100% 衰减到 81% → 63%;频率地板 1 GHz。
- 例 5:
从 1 ns 到 4 ns,MTBF 从 0.22 秒涨到 7.5 万年;每 0.1 ns(一个 )MTBF 乘以 。
考点
考点
1. 必背两大不等式
2. Setup vs Hold 对照表(★ 核心)
| 项 | Setup | Hold |
|---|---|---|
| 关心 | 数据来不来得及 | 数据来得太早 |
| 用哪个 | 最大延迟 | 最小延迟 |
| 含时钟周期 | 含 | 不含 |
| 降频能救? | 能 | 不能 ⚠️ |
| 修复手段 | 缩短组合逻辑、加流水线 | 给路径加延迟、减小偏斜 |
| 决定什么 | 电路能不能用(与频率无关) | |
| 检查关系 | 跨一个周期(发射沿 | 同一个沿(发射沿 |
3. 时钟偏斜的两面性
- 出现在 setup 不等式右边是减号 → 偏斜变大 → 要求变松 →
提高 - 出现在 hold 不等式右边是加号 → 偏斜变大 → 要求变严 → 容易违例
4. 亚稳态
- 对
指数敏感:每多一个 ,MTBF × - 标准解法:两级(或三级)同步器,给亚稳态一整个周期恢复
- 注意:降频对亚稳态有效(
是一周期),但对 hold 违例无效
5. 流水线
每加一级流水线,寄存器开销被重复计入一次 → 收益递减 → 存在最优流水线深度。
6. 高频陷阱
- "降频能解决所有时序问题"——错。hold 违例与频率无关,降频完全无效。
- hold 用最大延迟算——错。hold 关心"数据来得太快",要用最小延迟。
- "setup 裕量是正的就能用"——错。hold 违例照样让电路不可用(例 2 第一行)。
- 把
和 的时间方向记反。 在沿之前, 在沿之后;禁止窗口 = 。 符号搞反。统一约定"$t_{skew} = $ 捕获时钟 − 发射时钟";换约定则所有结论反转。报告时写清约定。- 忘了
。抖动只有正的破坏性(它让有效周期变短),直接加在 上。 - 认为"流水线提高性能"就等于"降低延迟"。吞吐率上去了,单条延迟反而变长。
- 认为流水线可以无限深。地板是
,而且寄存器开销本身也在增加。 - 把"关键路径"理解成"最长的物理连线"。关键路径 = slack 最小的逻辑通路,与物理长度无关(但长走线会带来额外的延迟)。
- 以为"功能仿真过了就没问题"。功能仿真不检查时序——这就是 STA 存在的理由。
7. 与前后章节的接口
circuit/13-flipflop.md给出 / 的物理来源(触发器内部门延迟),本篇给出它们的使用方法。circuit/12-alu.md的先行进位加法器之所以存在,就是因为 ALU 的 太长、卡住 ——整篇 L1 的"提速"努力,最终都要放在这两个不等式里算账。- L2 组成原理的"流水线"章节(
arch)直接使用本篇的例 4:段数、 、吞吐率、延迟的完整计算,以及"流水线冒险"为什么要插气泡。 - L4 操作系统的"中断响应时间" 本质也是时序问题(中断到达 → 流水线排空 → 保存现场)。
小结
- 逻辑正确 ≠ 能跑起来。整章只有两个不等式:建立(决定
)和保持(决定电路能不能用)。 - 建立检查跨一个周期,用最大组合延迟,降频可救:
- 保持检查在同一个沿,用最小组合延迟,周期
完全消失 → 保持违例与频率无关,降频救不了: - 时钟偏斜有两面性:正偏斜帮 setup、害 hold;负偏斜反之。唯一的出路是把偏斜压小(时钟树综合)。
- 亚稳态是"卡在 0 与 1 之间",靠
的指数敏感性理解;两级同步器是标准解法。注意:降频对亚稳态有效,对 hold 无效——同是"降频",两种结论。 - 流水线提高吞吐、恶化单条延迟,且寄存器开销反复计入 → 收益快速衰减,存在最优深度与频率地板
。 - STA(静态时序分析)之所以叫"静态",是因为它不依赖输入激励,把每条路径都算一遍——这是在数百万条路径上唯一可行的办法。
回到主线:L1(电 · 电路与数字逻辑)到此收口。
回头看一下这一层我们走过的路:
门(组合逻辑) ──→ 能算(ALU)
│ │
↓ ↓
有反馈 ──→ 能记(触发器)──→ 能存一个字(寄存器)
│
↓
有状态 + 有输入 ──→ 有行为(状态机)
│
↓
时序分析 ──→ 能跑多快、会不会出错L1 回答了什么问题:"L0 的硅器件,怎么变成能算、能记、能按规则行动的电路。"
我们造出了什么:一个 32 位 ALU、一堆寄存器、一个可以表示任意行为的状态机——以及把它们串起来后能跑多快的账本。
还缺什么:指令。
现在的电路只会"按操作码算一次"。谁来告诉它"读内存、算加法、写回、跳到下一个地址"?谁来组织"取指→译码→执行→写回"这个循环?谁来管理 Cache、中断和 I/O?
把这些零件按"指令"组织起来,就是一台 CPU——这是 L2【机器 · 计算机组成原理】的全部内容。
下一篇:计算机组成原理·概述
评论(0)
当前浏览器不允许本地存储,评论无法保存。
还没有评论,来说两句。