Appearance
控制器:硬布线与微程序
概念
控制器(control unit, CU)是 CPU 的"发号施令者":它从 IR 里取出操作码、结合时钟与标志位,在正确的节拍上发出正确的控制信号——也就是第 30 篇那些 XXout / XXin / ALUop / MemR / MemW。
一句话区分它与数据通路:数据通路是"手",负责搬数据;控制器是"脑",负责喊口令。 手不会自己动,脑也不会自己搬——两者合起来才是指令执行。
本章的核心问题是:这些控制信号是怎么产生出来的? 两条答案:硬布线(用门电路直接算出来)与微程序(把信号编码存进控存,读出来用)。
原理
一、控制器的组成与输入输出
┌──────────────────── 输入 ────────────────────┐
│ IR 的操作码 时钟/节拍 标志位 复位 中断请求 │
└───────┬───────────┬─────────┬──────┬───────┬─────┘
▼ ▼ ▼ ▼ ▼
┌───────────────────────────────────────────┐
│ 指令译码器 + 时序系统(节拍发生器) │
│ 控制信号产生部件(门电路 或 控存 CM) │
└───────────────────┬───────────────────────┘
▼ 输出
┌──────────────────────┴──────────────────────┐
│ PCout MDRout Yin Zout ... Xin ALUop MemR/W │
└─────────────────────────────────────────────┘| 组成部分 | 作用 |
|---|---|
| PC | 提供下一条指令地址;遇转移时由控制器改写入 |
| IR | 存放当前指令,其操作码字段送译码器 |
| 指令译码器 | 把 |
| 时序系统 | 产生时钟、节拍电位与节拍脉冲,决定"现在是第几拍" |
| 控制信号产生部件 | 硬布线:门电路网络;微程序:控制存储器 CM |
注意 PC 与 IR 的归属:它们既算在数据通路里(数据会流经它们),也算控制器的一部分(它们的控制端由控制器驱动)。408 里两种说法都出现过,按题目的上下文判断即可,别纠结。
二、硬布线控制器(组合逻辑控制器)
做法:把控制信号写成"操作码 + 节拍 + 状态"的布尔函数,用门电路(或 PLA)直接实现。
例:PCout 这个信号在"取指周期的 T0 拍"为 1,写成逻辑式大致是:
而 ALUop=ADD 则是:
(即"第 T5 拍且当前指令是 ADD"。)
| 优点 | 缺点 |
|---|---|
| 速度最快:信号经过几级门就出来,不需要读控存 | 设计复杂:几百条指令 × 十几个节拍 × 上百个信号,逻辑式海量 |
| 易于优化关键路径(可以手工插缓冲、并行化) | 修改困难:改一条指令要改线路(甚至重做芯片) |
| 不需要控存,省面积 | 不规整,难以用自动化工具生成与验证 |
适用场合:RISC(指令少、寻址方式少、节拍固定),也正是第 21 篇 RISC 五原则里的第 ⑤ 条。
三、微程序控制器
核心思想(Wilkes, 1951):把控制信号"存起来",用的时候读出来——就像程序设计一样。
三级术语,必须分清:
| 术语 | 定义 | 类比 |
|---|---|---|
| 微命令(micro-command) | 最小的一根控制信号,如 PCout、Yin | 一条语句 |
| 微指令(micro-instruction) | 一个节拍内同时发出的一组微命令 + 下地址信息 | 一个函数体(一拍干的所有事) |
| 微程序(microprogram) | 完成一条机器指令所需的一串微指令 | 一个函数 |
| 微操作 | 微命令作用下数据通路里实际发生的搬运( | 语句的效果 |
层级关系:微命令 ⊂ 微指令 ⊂ 微程序 ⊆ 机器指令。
存放位置:微程序存在 控制存储器 CM(control memory)里。CM 通常用 ROM(只读,掉电不丢)或 WCS(可写控制存储器,用于开发期)。CM 在 CPU 内部,速度必须与 CPU 同量级,所以容量小而快。
四、微指令的两个字段
┌──────────────────────────────┬──────────────────────────────┐
│ 操作控制字段 │ 顺序控制字段 │
│ (发哪些微命令) │ (下一条微指令在哪) │
│ 直接编码 / 字段直接编码 / 字段间接编码 │ 下地址字段 + 判别测试字段 │
└──────────────────────────────┴──────────────────────────────┘顺序控制字段的两种下地址形成方式:
| 方式 | 规则 | 特点 |
|---|---|---|
| 计数器法 | 微指令顺序执行,下地址 = 当前地址 + 1(微程序计数器 µPC) | 微指令字短(不用存下地址),但不能直接跳转,遇分支要靠判别测试改 µPC |
| 断定法(下地址字段法) | 每条微指令里直接写明下一条微指令的地址 | 灵活,可任意跳转;代价是每条微指令都要带一个地址字段 |
| 混合(最常见) | 顺序执行靠 µPC+1,需要分支时由下地址字段 + 判别测试指定 | 折中:既短又灵活 |
判别测试字段:
五、微命令的三种编码方式(本节最常考)
一条微指令里的操作控制字段有几十个微命令要表示,怎么编?
| 编码方式 | 做法 | 位数 | 速度 | 并行能力 |
|---|---|---|---|---|
| 直接编码(水平编码) | 每个微命令占 1 位,1 = 发,0 = 不发 | 最快(无需译码) | 全部可并行 | |
| 字段直接编码 | 把互斥的微命令分成若干组,组内用二进制编码,另留一个"全 0"表示本组不发 | 各组 | 需组内译码,略慢 | 组内互斥、组间可并行 |
| 字段间接编码 | 字段编码后还要再查一次才能确定微命令(两级译码) | 更少 | 最慢 | 受限 |
"互斥"是什么意思:同一节拍里绝不会同时出现的微命令才可以放进同一组。例如 PCout 与 MDRout 同时出现会导致总线冲突(第 30 篇的铁律),所以它们互斥,可以放一组。
为什么"组内要用
代价对比:字段直接编码能大幅压缩字长,但牺牲了并行性——同组互斥命令当然只能发一个,所以如果实际需要同时发的命令被误分到同组,功能就错了。分组时要保证"互斥"这个前提成立。
六、微指令的格式类型
| 类型 | 特征 | 微指令字长 | 微程序条数 | 并行度 |
|---|---|---|---|---|
| 水平型 | 一条微指令能并行发多个微命令(甚至控制多个部件同时动作) | 长 | 短(节拍少) | 高 |
| 垂直型 | 一条微指令只发一个(或很少几个)微命令,类似机器指令 | 短 | 长 | 低 |
| 混合型 | 分组并行,是现代主流 | 中 | 中 | 中 |
一句话记忆:水平型 = 字长换条数;垂直型 = 条数换字长。 两者是同一组 trade-off 的两端。
七、微程序控制器的工作过程
① 取指公共微程序(所有机器指令共用,入口地址固定)
PC -> MAR -> 读内存 -> MDR -> IR -> 译码
② 由操作码映射到"该指令的微程序入口地址"
(入口地址表 / 操作码直接拼接低若干位)
③ 执行该机器的微程序:逐条取微指令 -> 发出微命令 -> 形成下地址
④ 微程序结束 -> 回到取指,取下一条机器指令"取指微程序是公共的"这一点很关键:20 条机器指令不需要 20 段取指微程序,只需要 1 段公共取指微程序 + 20 段执行微程序。这就是下面例 2 里"
示例
例 1:微命令编码位数(经典必考)
某机有 40 个微命令,根据互斥关系分成 5 组:组 1 有 7 个、组 2 有 8 个、组 3 有 14 个、组 4 有 3 个、组 5 有 8 个微命令。分别求直接编码与字段直接编码所需的操作控制字段位数,并算节省比例。
完整计算过程:
(1)直接编码
(2)字段直接编码
第一步,逐组算位数(每组都要
| 组 | 微命令数 | 编码需求 | 位数 |
|---|---|---|---|
| 组 1 | 7 | 7 + 1 = 8 种 | |
| 组 2 | 8 | 8 + 1 = 9 种 | |
| 组 3 | 14 | 14 + 1 = 15 种 | |
| 组 4 | 3 | 3 + 1 = 4 种 | |
| 组 5 | 8 | 8 + 1 = 9 种 |
第二步,求和:
(3)节省比例
结论:字段直接编码把操作控制字段从 40 位压到 17 位,省掉 57.5%,代价是组内需要译码、组内微命令不能并行。
⚠️ 两个高频错误:
- 忘了
:组 2 有 8 个微命令, ——错了,因为还缺"不发"这 1 种,必须是 。这一位之差是本考点最经典的失分点。 - 把
算成 3: ,必须进位到 4。
例 2:微程序条数与控存(CM)容量
某微程序控制器:机器指令 20 条,取指公共微程序 2 条微指令,每条机器指令的执行微程序平均 4 条微指令。操作控制字段 17 位(用例 1 的编码),判别测试字段 2 位,下地址字段按需要设置。求:微指令总数、下地址字段位数、微指令字长、CM 容量(按"够用"取容量)。
完整计算过程:
第一步,微指令总数(取指微程序是公共的,只算一次):
第二步,下地址字段位数:地址要能指向 CM 的每一个单元,所以取不小于 82 的 2 的幂:
注意别答成 6 位:
第三步,微指令字长:
第四步,CM 容量:控存按 2 的幂配置(地址译码方便),取
对照:若改用直接编码
| 编码方式 | 操作控制字段 | 微指令字长 | CM 容量 | 相对 |
|---|---|---|---|---|
| 字段直接编码 | 17 位 | 26 位 | 3328 位 | 1.00× |
| 直接编码 | 40 位 | 49 位 | 6272 位 | 1.88× |
结论:字段直接编码让控存省了约 47%(
同时注意
例 3:硬布线与微程序的速度差异
接着例 2。硬布线控制器产生控制信号需要经过 4 级门,每级 1 ns;微程序控制器读一次控存需要 12 ns(控存访问时间),微指令执行一拍。求两者的节拍长度与最高主频。
完整计算过程:
第一步,硬布线的节拍长度 = 信号产生延迟(组合逻辑级数 × 单级延迟):
第二步,微程序的节拍长度受控存访问时间限制(因为要先读出微指令才能发信号):
第三步,对比:
| 控制器 | 节拍长度 | 最高主频 | 相对速度 |
|---|---|---|---|
| 硬布线 | 4 ns | 250 MHz | 3.00× |
| 微程序 | 12 ns | 83.3 MHz | 1.00× |
结论:每拍控制信号的产生延迟直接决定节拍长度,节拍长度决定主频。 微程序因为多了一道"读控存",主频天然低于硬布线约 3 倍。
⚠️ 一个必须澄清的常见误解:
"微程序比硬布线慢,因为一条机器指令要执行好几条微指令。"
这个说法是错的。 一条微指令就是一个节拍——它发的是"这一拍需要的全部微命令",与硬布线在同一拍发的那批信号完全等价。所以两者的节拍数是一样的,差别只在**"这批信号怎么产生"**:
- 硬布线:门电路现算(快)。
- 微程序:从 CM 读出来(多一次存储访问,慢)。
"微程序要执行多条微指令"对应的是"机器指令要多个节拍",这是所有控制器共有的,不是微程序的额外开销。答题时把这个道理说清楚,是一个很好的加分点。
例 4:硬布线与微程序的全面对照
| 对比项 | 硬布线控制器 | 微程序控制器 |
|---|---|---|
| 控制信号产生 | 组合逻辑(门电路)直接产生 | 从控存 CM 中读出微指令 |
| 核心存储 | 无(可选一个 PLA / ROM 做指令译码) | 必须有 CM(ROM 或 WCS) |
| 速度 | 快(少一次存储访问) | 慢(受 CM 访问时间限制) |
| 设计 / 修改 | 难:改指令就要改线路 | 易:改微程序即可,指令可"扩展" |
| 规整性 | 不规整、难自动化生成 | 规整:微指令格式统一,可用工具生成 |
| 硬件面积 | 门电路多但无 CM | 门电路少但 CM 占面积 |
| 每条指令的微操作 | 全部逻辑式都要展开 | 不用展开,按需读微指令 |
| 适合的指令系统 | RISC(指令少、规整) | CISC(指令多、复杂、易变) |
| 典型代表 | MIPS、ARM(早期) | x86(历史实现)、PDP-11、VAX |
历史脉络:CISC 时代微程序流行(指令复杂、需要频繁修改),RISC 兴起后硬布线回归(指令少而规整,用微程序反而是浪费)——这与第 21 篇"RISC 用硬布线"那一条完全对应。
现代的现实:x86 的做法是"前端微程序 / 微码 + 后端硬布线"的混合:复杂少见指令走微码 ROM,常见指令用硬布线的快速通路(fast path)译成微操作。"微程序 vs 硬布线"在今天的 CPU 里是并存的,不是二选一。
例 5:Python——微命令编码与控存容量的完整换算
import math
def field_direct_bits(groups):
"""字段直接编码: 每组 ceil(log2(n+1)) 位; groups 是各组互斥微命令的个数"""
detail = [(n, math.ceil(math.log2(n + 1))) for n in groups]
return sum(b for _, b in detail), detail
def eval_design(groups, n_instr, per_instr, pub, test_bits):
n_cmd = sum(groups)
bits_f, detail = field_direct_bits(groups)
n_micro = n_instr * per_instr + pub
addr_b = math.ceil(math.log2(n_micro))
cm_cells = 2 ** addr_b
w_f = bits_f + test_bits + addr_b # 字段直接编码的字长
w_d = n_cmd + test_bits + addr_b # 直接编码的字长
return dict(n_cmd=n_cmd, bits_f=bits_f, detail=detail, n_micro=n_micro,
addr_b=addr_b, cm_cells=cm_cells, w_f=w_f, w_d=w_d,
cm_f=cm_cells * w_f, cm_d=cm_cells * w_d)
groups = [7, 8, 14, 3, 8]
r = eval_design(groups, n_instr=20, per_instr=4, pub=2, test_bits=2)
print('=== 例 1: 微命令编码 ===')
print('微命令分 %d 组: %s -> 共 %d 个微命令' % (len(groups), groups, r['n_cmd']))
for n, b in r['detail']:
print(' 组内 %2d 个 -> ceil(log2(%2d+1)) = %d 位 (全 0 表示本组不发)' % (n, n, b))
print('直接编码 %d 位 ; 字段直接编码 %d 位 ; 省 %.1f%%'
% (r['n_cmd'], r['bits_f'], (r['n_cmd'] - r['bits_f']) / r['n_cmd'] * 100))
print('\n=== 例 2: 微程序条数 与 CM 容量 ===')
print('微指令总数 = %d x %d + %d = %d 条 (取指微程序公共, 只算一次)'
% (20, 4, 2, r['n_micro']))
print('下地址字段: 2^%d = %d < %d <= %d = 2^%d -> 需 %d 位'
% (r['addr_b'] - 1, 2 ** (r['addr_b'] - 1), r['n_micro'], r['cm_cells'],
r['addr_b'], r['addr_b']))
print('字段直接: 字长 = %d + 2 + %d = %d 位 -> CM = %d x %d = %d 位 = %d B'
% (r['bits_f'], r['addr_b'], r['w_f'], r['cm_cells'], r['w_f'],
r['cm_f'], r['cm_f'] // 8))
print('直接编码: 字长 = %d + 2 + %d = %d 位 -> CM = %d x %d = %d 位 = %d B'
% (r['n_cmd'], r['addr_b'], r['w_d'], r['cm_cells'], r['w_d'],
r['cm_d'], r['cm_d'] // 8))
print('控存利用率 = %d / %d = %.1f%% (按 2 的幂配容量的浪费)'
% (r['n_micro'], r['cm_cells'], r['n_micro'] / r['cm_cells'] * 100))
print('字段直接编码相对直接编码 省 %.1f%% 控存位'
% ((1 - r['cm_f'] / r['cm_d']) * 100))
print('\n=== 例 3: 节拍长度与主频 ===')
for name, t_ns in [('硬布线', 4), ('微程序', 12)]:
print(' %s: 节拍 %d ns -> f_max = %.1f MHz' % (name, t_ns, 1000 / t_ns))
print(' 微程序/硬布线 节拍比 = %.2f' % (12 / 4))
print('\n=== 敏感度: 机器指令由 20 条增到 30 条 ===')
r0 = eval_design(groups, n_instr=20, per_instr=4, pub=2, test_bits=2)
r2 = eval_design(groups, n_instr=30, per_instr=4, pub=2, test_bits=2)
for tag, r in [('20 条指令', r0), ('30 条指令', r2)]:
print(' %s -> 微指令 %3d 条, 下地址 %d 位, CM %3d 单元, 利用率 %5.1f%%'
% (tag, r['n_micro'], r['addr_b'], r['cm_cells'],
r['n_micro'] / r['cm_cells'] * 100))
print(' 说明: 微指令总数越贴近 2 的幂, 空单元比例越低 (122/128 好于 82/128)')
print(' 反之若刚好超过 2 的幂(如 130 条 -> 需 256 单元), 利用率会骤降到 50.8%')
python 本站为静态站,不提供在线运行;可复制到本地用 gcc / python 执行
输出对照:例 1 得到 40 位 → 17 位、省 57.5%;例 2 得到 82 条微指令、下地址 7 位、字长 26 位、CM 3328 位 = 416 B,直接编码时为 49 位 / 6272 位(省 46.9%)、控存利用率 64.1%;例 3 得到 250 MHz 与 83.3 MHz、比值 3.00。全部与手算一致。
考点
考点
1. 必背结论
- 控制器 = PC + IR + 指令译码器 + 时序系统 + 控制信号产生部件;输入是操作码/节拍/标志位,输出是控制信号。
- 微命令 ⊂ 微指令 ⊂ 微程序:1 条机器指令对应 1 段微程序,1 段微程序 = 若干条微指令,1 条微指令 = 若干微命令。
- 一条微指令 = 一个节拍(发出该拍所需的全部微命令);这与硬布线在同一拍发的信号完全等价。
- 微指令 = 操作控制字段 + 顺序控制字段(下地址 + 判别测试)。
- 字段直接编码每组位数
——那个 是"本组不发"。 - 下地址字段位数
。微 指 令 条 数 - 水平型:字长长、微程序短、并行度高;垂直型:字长短、微程序长、并行度低。
- 下地址形成:计数器法(顺序 +1)/断定法(微指令里写下地址)/混合。
- 取指微程序是公共的,只需 1 段。
- 硬布线快但难改、适合 RISC;微程序易改易扩展、适合 CISC。
2. 高频陷阱
- 字段直接编码忘了
。 时是 位,不是 3 位。这是本考点第一大失分点。 - 下地址字段位数"刚好跨 2 的幂":82 条微指令需要 7 位(
),不是 6 位。 - 微指令条数 ≠ 机器指令条数。一条机器指令要一段微程序(多条微指令);微指令条数由"节拍划分 + 微程序结构"决定。
- 取指微程序不要重复计算:20 条机器指令只共用 1 段取指微程序(例 2 里的
),不是 。 - "微程序比较慢"的正确理由是"读控存多一次存储访问",不是"要执行多条微指令"——后者是所有控制器共同的节拍开销。答错了会被判为概念不清。
- 水平型不是"垂直型的高配版":水平型字长换条数、垂直型条数换字长,是 trade-off 两端,不是优劣关系。
- CISC 用微程序、RISC 用硬布线是"倾向"不是"铁律";现代 x86 是两者混用。答题加"通常"更稳。
- 控存按 2 的幂配容量会有浪费(例 2 利用率只有 64.1%),这是地址译码简化的代价,不是设计错误。
- CM 在 CPU 内部,速度必须与 CPU 匹配,所以容量小;这与主存、Cache 是三个不同层级的存储(别和第 10 篇的存储层次搞混:CM 不属于"Cache—主存—辅存"这条程序员可见的层次)。
3. 解题模板("微程序设计题")
① 数微命令 -> 分组(互斥的放一组)
② 操作控制字段位数:
直接编码 = 微命令总数
字段直接 = Σ ceil(log2(每组个数 + 1))
③ 微指令条数 = (机器指令数 x 每条平均微指令数) + 公共取指微程序条数
④ 下地址字段 = ceil(log2(微指令条数))
⑤ 微指令字长 = 操作控制 + 判别测试 + 下地址
⑥ CM 容量 = 2^下地址位数 x 微指令字长
⑦ 若问速度: 硬布线受"门级延迟"限, 微程序受"控存访问时间"限4. 与相邻章节的接口
- 第 30 篇(数据通路):本篇发出的信号就是那篇里的
XXout/XXin/ALUop。两篇合起来才是一条完整指令的执行过程。 - 第 21 篇(CISC/RISC):RISC 五原则第 ⑤ 条"用硬布线"在这里被展开;对照表里"适合的指令系统"一行直接对应。
- 第 32 篇(流水线):流水线需要每个节拍发出的信号不同,控制器要按"第几级流水"切换信号源;硬布线控制器在流水线 CPU 里几乎是唯一选择。
- 第 33 篇(异常与中断):中断请求是控制器的输入之一,微程序控制器要有一条"中断响应微程序",入口地址由"判别测试 + 中断源编码"共同决定。
- 第 11 篇(主存储器):CM 也是存储器,但与主存/辅存不是同一条层次线——CM 对程序员完全不可见。
小结
- 控制器 = 把(操作码,节拍,标志位)变成一组控制信号;它是"脑",第 30 篇的数据通路是"手"。
- 硬布线用门电路直接算信号:快、难改、适合 RISC。微程序把信号存进 CM 读出来:易改、易扩展、适合 CISC。
- 三级术语:微命令 ⊂ 微指令 ⊂ 微程序;一条微指令 = 一个节拍。
- 字段直接编码每组位数
,那个 表示"本组不发",丢了就全错。例 1 里 40 位压到 17 位。 - 下地址字段
;例 2 的 82 条要 7 位(不是 6 位)。微 指 令 条 数 - CM 容量 =
微指令字长;例 2 是 3328 位 = 416 B。下 地 址 位 数 - 微程序慢的原因是"读控存多一次存储访问"(例 3 里 12 ns vs 4 ns,主频差 3 倍),不是"要执行多条微指令"。
- 取指微程序是公共的,只需 1 段——这是算微指令总数时最容易多算的地方。
评论(0)
当前浏览器不允许本地存储,评论无法保存。
还没有评论,来说两句。