Appearance
数据通路:CPU 内部数据怎么流动
概念
数据通路(datapath)指 CPU 内部数据流动的路径——由「能算的部件(ALU、加法器、移位器)+「能存的部件」(寄存器、寄存器堆、存储器)+ 「把它们连起来的选择器」(多路选择器、三态门、总线)共同组成。
为什么它比控制器更基础:控制器只是"发号施令"的那部分(第 31 篇),真正搬数据、做运算、写回结果的是数据通路。指令执行的每一个节拍,数据通路里的数据都在按既定路线搬家——把这条路线写清楚,就是"微操作序列"。
本章要解决一个问题:给一条指令,逐拍写出数据从哪个部件流到哪个部件。 这是 408 大题的标准动作。
原理
一、两类元件,职责分明
| 类别 | 别名 | 特性 | 典型器件 | 在通路里的角色 |
|---|---|---|---|---|
| 组合逻辑元件 | 操作元件 | 输出只由当前输入决定,无记忆 | ALU、加法器、移位器、多路选择器 MUX、译码器、三态门 | 算、选、送 |
| 时序逻辑元件 | 存储元件 | 输出由输入 + 原状态决定,有记忆,受时钟同步 | 触发器、寄存器(74LS374)、寄存器堆、主存 | 存 |
一个关键判据:看这个部件"现在的输出是否只取决于现在的输入"。
- 是 → 操作元件(组合逻辑)。
- 输出还取决于"上一拍存了什么" → 存储元件(时序逻辑)。
寄存器与触发器的关系:一个
二、单总线数据通路的结构
总线(bus)是一组共享的连线。单总线结构指 CPU 内部只有一条数据总线,所有部件都挂在这一条线上:
┌────────────────────────────────────────┐
│ 内部数据总线 │
└───┬───┬───┬───┬───┬───┬───┬───┬───┬────┘
┌──────┐ ┌──────┐ │ │ │ │ │ │ │ │ ┌──────┐
│ PC │──┤ MAR │──┘ │ │ │ │ │ │ │ │ Y │──┐
└──────┘ └──┬───┘ │ │ │ │ │ │ │ └──────┘ │
+1 ↑ │ 地址总线 │ │ │ │ │ │ │ ▼
┌────────┐ ▼ │ │ │ │ │ │ │ ┌────────┐
│ 主存 │ ┌──────┐ │ │ │ │ │ │ └──────▶│ ALU │
│ M │──┤ MDR │────┘ │ │ │ │ │ └───┬────┘
└────────┘ └──────┘ │ │ │ │ │ ▼
│ │ │ │ │ ┌────────┐
┌──────┐│┌──────┐│┌──────┐ │ Z │
│ IR │││ R0 │││ R1 │… └────────┘
└───┬──┘│└──────┘│└──────┘
│ │ │
▼ └────────┘
控制器 CU(译码 + 发控制信号)挂在这条总线上的部件:
| 部件 | 全称 | 作用 | 位数 |
|---|---|---|---|
| PC | program counter,程序计数器 | 存下一条指令的地址 | = 地址位数 |
| MAR | memory address register,存储器地址寄存器 | 存要访问的主存地址 | = 地址位数 |
| MDR | memory data register,存储器数据寄存器 | 存读/写的数据 | = 存储字长(= 机器字长) |
| IR | instruction register,指令寄存器 | 存当前指令 | = 指令字长 |
| Y | —— | ALU 左侧输入的暂存器 | = 机器字长 |
| Z | —— | ALU 结果的暂存器 | = 机器字长 |
| R0 ~ Rn | general registers,通用寄存器 | 程序员可见的运算数据仓库 | = 机器字长 |
三、为什么单总线必须要有 Y 和 Z
这不是"多加了两个寄存器",而是单总线结构的强制要求。
问题 1:ALU 要两个输入,总线一次只送一个。
R0 → ALU 输入 A (第一拍:总线送 R0)
R1 → ALU 输入 B (第二拍:总线送 R1)—— 此时 R0 还在总线上吗?不在了!总线是一条共享线路,同一时刻只能有一个部件在驱动它。所以第一拍送进来的 R0 必须先存起来,否则第二拍就没法用。
解法:加一个暂存器 Y。第一拍 R0 → Y(Y 不挂输出端,只接受输入),第二拍把 R1 送上总线、同时 ALU 做 R1 + Y。
问题 2:ALU 是组合逻辑,"算完就散",结果还没写回寄存器就变了。
解法:加一个结果暂存器 Z。ALU 输出先打进 Z,再由 Z → 总线 → 目标寄存器。
三总线的对比
| 结构 | 一次能传几个数据 | 是否需要 Y/Z | ADD 执行拍数 |
|---|---|---|---|
| 单总线 | 1 个 | 必须要有 Y 和 Z | 3 拍 |
| 双总线 | 2 个 | 需要 Z(可以不需 Y) | 2 拍 |
| 三总线 | 3 个 | 不需要(ALU 直接读两个、写回总线) | 1 拍 |
| 专用通路(RISC 常用) | 并行多路 | 不需要,寄存器堆双端口读出 | 1 拍 |
一句话总结:Y 和 Z 的存在是"总线共享"的代价,不是设计缺陷。 总线越少,硬件越省但拍数越多。
四、控制信号:XXout / XXin 的命名法
数据通路里的每个部件都有两个控制端:
| 后缀 | 含义 | 作用 |
|---|---|---|
XXout | 输出允许 | 打开该部件到总线的三态门,把它送上总线 |
XXin | 输入允许 | 允许该部件在本拍结束时把总线上的数据锁存进来 |
于是微操作可以直接写成控制信号列表:
这行等式就是答题的核心格式:左边是微操作(数据搬家),右边是它需要打开的那些控制信号。
工程对应:XXout 就是挂在总线上的三态门(《电 · 电路与数字逻辑》第 14 篇里的 74LS244),XXin 就是寄存器的时钟使能 + 边沿触发端(74LS374)。
注意"一拍只能有一个 out":若两个 XXout 同时打开,两个部件会同时驱动总线,产生总线冲突(电气上是两个强驱动源对撞)。这是写微操作序列时的铁律,也是最常见的错误。
五、取指周期的微操作序列(所有指令共用)
取指 4 拍(T0~T3):
| 拍 | 微操作 | 控制信号 | 说明 |
|---|---|---|---|
T0 | PCout, MARin | 把指令地址送上地址总线 | |
T1 | MemR, MDRin, PC+1 | 读内存;PC 自增可与读内存并行(PC 有独立的加法器,不占总线) | |
T2 | MDRout, IRin | 指令进入 IR | |
T3 | —— | 译码。IR 的操作码字段直接连到控制器,不占用总线 |
取指周期为什么能 4 拍:T1 里 (PC)+1 → PC 用的是 PC 自己的加一逻辑,不经过总线,所以能和"读内存"同拍完成。凡是不需要总线的动作,都可以与总线动作并行。
六、执行周期的微操作(各类指令)
记一个总规律:凡是"两数运算",都是「送 Y → 算入 Z → 写回」三拍;凡是"访存",都先经过 MAR/MDR。
(1) 寄存器间加法 ADD R0, R1(执行 3 拍)
| 拍 | 微操作 | 控制信号 |
|---|---|---|
T4 | R0out, Yin | |
T5 | R1out, ALUop=ADD, Zin | |
T6 | Zout, R0in |
(2) 取数 LOAD R0, X(执行 3 拍)
| 拍 | 微操作 | 说明 |
|---|---|---|
T4 | 形式地址从 IR 送进 MAR(这里 IR 的地址字段直连总线) | |
T5 | 读主存 | |
T6 | 数据进目标寄存器 |
(3) 存数 STORE R0, X(执行 2 拍)
| 拍 | 微操作 | 说明 |
|---|---|---|
T4 | 地址先到位 | |
T5 | MDRout 配合 MemW;数据要先进 MDR,不能直接写内存 |
(4) 自增 INC R0(执行 3 拍)
| 拍 | 微操作 | 说明 |
|---|---|---|
T4 | 先暂存 | |
T5 | ALU 做"加 1",第二个操作数由常数 1 提供,不占总线 | |
T6 | 写回 |
注意 INC 也要 3 拍——通用寄存器没有 PC 那样的"独立加一逻辑",所以必须走 ALU 的完整三拍。
(5) 条件分支 JZ R0, X(执行 2 拍)
| 拍 | 微操作 | 说明 |
|---|---|---|
T4 | 判断 | 由控制器根据 ALU 标志位决定是否发出 PCin |
T5 | 若不成立, | 分支不成立时只做了一次判断,不写 PC |
七、MAR / MDR 的位数与必要性
| 寄存器 | 位数 | 理由 |
|---|---|---|
| MAR | = 地址位数 | 它必须能装下"主存的每一个地址" |
| MDR | = 存储字长(通常 = 机器字长) | 它必须能装下"一次读写的数据量" |
| IR | = 指令字长 | 要装下完整指令 |
| PC | = 地址位数 | 与 MAR 同宽 |
为什么 CPU 不能直接连内存、非要经过 MAR 和 MDR?
- 时序:内存读写的动作需要"先给地址、后等数据",中间要有一拍到几拍的等待。用寄存器把地址和数据锁住,CPU 就能在这段时间里做别的事。
- 驱动力:总线负载电容大,寄存器能提供足够的驱动能力。
- 宽度解耦:内存可能按字节编址(8 位),而 CPU 一次要读 32 位——MDR 负责把 4 个字节拼装起来。
示例
例 1:写出取指周期的微操作序列,并算出占用拍数
单总线结构 CPU,指令字长 = 存储字长 = 机器字长 = 32 位。写出取指周期的全过程。
完整计算过程:
| 拍 | 微操作 | 控制信号 | 占用总线 |
|---|---|---|---|
T0 | PCout, MARin | 是 | |
T1 | MemR, MDRin, PC+1 | 是(读内存) | |
T2 | MDRout, IRin | 是 | |
T3 | —— | 否(译码不走总线) |
结论:取指周期 4 拍,其中 3 拍占用总线、1 拍(译码)只走内部控制线。
两个必须写对的细节:
T1的(PC)+1 → PC与读内存并行。因为 PC 的加一用的是它自己的加法器,不占用数据总线。若把它单独列成一拍,时间会算多。T3的译码不是数据搬运,而是把 IR 的操作码送进控制器。它不占总线,但占一个节拍(因为后续控制信号要等它出来)。
例 2:ADD R0, R1 的完整微操作序列
接着例 1。写出
ADD R0, R1(含义:)从取指到写回的全部微操作。
完整计算过程:
① 取指周期(T0 ~ T3,见例 1)
T0 PC -> MAR PCout, MARin
T1 M(MAR) -> MDR, (PC)+1 -> PC MemR, MDRin, PC+1
T2 MDR -> IR MDRout, IRin
T3 OP(IR) -> CU (译码,不占总线)
② 执行周期(T4 ~ T6)
T4 R0 -> Y R0out, Yin
T5 R1 + Y -> Z R1out, ALUop=ADD, Zin
T6 Z -> R0 Zout, R0in总计 7 拍(取指 4 + 执行 3)。
逐拍检查"总线冲突":
| 拍 | 驱动总线的部件(out) | 数量 | 是否合法 |
|---|---|---|---|
T0 | PC | 1 | ✓ |
T1 | 主存(经 MDR) | 1 | ✓ |
T2 | MDR | 1 | ✓ |
T4 | R0 | 1 | ✓ |
T5 | R1 | 1 | ✓ |
T6 | Z | 1 | ✓ |
每一拍只有 1 个 out —— 这是检查答案对不对的最快办法。
为什么不写成 2 拍:如果省略 Y,需要在 T4 把 R0 和 R1 同时送上总线送到 ALU 的两个输入端——但单总线一次只能送一个,所以做不到。这就是 Y 存在的全部理由。
例 3:五条指令的执行周期微操作对照
单总线结构,写出下列指令的执行周期(执行周期只列,不含取指 4 拍)。
| 指令 | 含义 | T4 | T5 | T6 | 执行拍数 |
|---|---|---|---|---|---|
ADD R0, R1 | 3 | ||||
SUB R0, R1 | 3 | ||||
LOAD R0, X | 3 | ||||
STORE R0, X | —— | 2 | |||
INC R0 | 3 |
读表要点:
- 所有"两操作数运算"骨架完全相同(送 Y、算入 Z、写回),换的只是 ALUop。这是背一条顶三条的原因。
STORE只要 2 拍:它不需要"读出来再算",只要地址到位、数据送进 MDR 就能写。LOAD与STORE的 T4 完全一样(都是地址进 MAR),差别只在 T5 的方向:一个从内存读到 MDR,一个从寄存器写到 MDR。
例 4:单总线与三总线的时钟数对比
有一段含 5 条指令的程序:
ADD、SUB、LOAD、STORE、INC(顺序如例 3)。分别求单总线与三总线结构下执行这段程序所需的时钟周期数。
完整计算过程:
(1)单总线
第一步,统计每条指令的拍数(取指 4 + 执行):
| 指令 | 取指 | 执行 | 小计 | 累计 |
|---|---|---|---|---|
ADD | 4 | 3 | 7 | 7 |
SUB | 4 | 3 | 7 | 14 |
LOAD | 4 | 3 | 7 | 21 |
STORE | 4 | 2 | 6 | 27 |
INC | 4 | 3 | 7 | 34 |
(2)三总线
三总线结构下,ALU 可以从两条总线同时取两个操作数、结果直接送上第三条总线,所以:
(3)对比
| 结构 | 拍数 | 相对单总线 |
|---|---|---|
| 单总线 | 34 | 1.00× |
| 三总线 | 15 | 快 2.27 倍 |
结论与解读:
- 单总线用"少连线"换来了 2.27 倍的时钟数。这是典型的"面积换速度"。
- 取指占了 20/34 = 58.8%——取指是单总线结构下最大的开销来源。这正是第 32 篇指令流水线的用武之地:把取指与执行重叠起来,就能把这 58.8% 的大部分藏掉。
- 总线数不是越多越好:三总线的连线数量、引脚数、驱动成本都远高于单总线。RISC 用的不是"很多总线",而是"专用通路 + 寄存器堆双端口",效果类似但更规整。
例 5:MAR/MDR 位数与指令字长的关系
某机主存容量
、按字节编址,机器字长 32 位,指令字长 32 位,存储字长 32 位。求 PC、MAR、MDR、IR 的位数。
完整计算过程:
第一步,主存容量
第二步,由地址位数决定 PC 与 MAR:
第三步,MDR 由存储字长决定:
第四步,IR 由指令字长决定:
| 寄存器 | 位数 | 由什么决定 |
|---|---|---|
| PC | 32 | 地址位数 |
| MAR | 32 | 地址位数 |
| MDR | 32 | 存储字长 |
| IR | 32 | 指令字长 |
易错点:若主存按字编址(每 4 字节一个字),地址位数只需
MDR 为什么必须能装下 32 位:假设指令字长 32 位、MDR 只有 8 位,那么取一条指令要经过 4 次"MDR → IR"的分段搬运,取指周期会从 4 拍膨胀到 7 拍。MDR 的宽度决定了"一次能搬多少",也就决定了取指节奏。
例 6:Python——单总线微操作序列模拟与总线冲突检查
# 单总线数据通路: 每拍 = (需要驱动总线的部件, 需要锁存的部件, 需要的控制信号)
# 规则: 同一拍里 "out" 的部件至多 1 个, 否则总线冲突
FETCH = [
('T0', ['PC'], ['MAR'], ['PCout', 'MARin'], '取指: 送地址'),
('T1', ['M'], ['MDR'], ['MemR', 'MDRin', 'PC+1'], '取指: 读内存, PC 自增并行'),
('T2', ['MDR'], ['IR'], ['MDRout', 'IRin'], '取指: 指令入 IR'),
('T3', [], [], [], '取指: 译码(不占总线)'),
]
EXEC = {
'ADD': [('T4', ['R0'], ['Y'], ['R0out', 'Yin'], 'R0 -> Y'),
('T5', ['R1'], ['Z'], ['R1out', 'ALUop=ADD', 'Zin'], 'R1 + Y -> Z'),
('T6', ['Z'], ['R0'],['Zout', 'R0in'], 'Z -> R0')],
'SUB': [('T4', ['R0'], ['Y'], ['R0out', 'Yin'], 'R0 -> Y'),
('T5', ['R1'], ['Z'], ['R1out', 'ALUop=SUB', 'Zin'], 'R1 - Y -> Z'),
('T6', ['Z'], ['R0'],['Zout', 'R0in'], 'Z -> R0')],
'LOAD': [('T4', ['IR'], ['MAR'],['IRout', 'MARin'], 'IR(addr) -> MAR'),
('T5', ['M'], ['MDR'],['MemR', 'MDRin'], 'M(MAR) -> MDR'),
('T6', ['MDR'], ['R0'],['MDRout', 'R0in'], 'MDR -> R0')],
'STORE': [('T4', ['IR'], ['MAR'],['IRout', 'MARin'], 'IR(addr) -> MAR'),
('T5', ['R0'], ['MDR'],['R0out', 'MDRin', 'MemW'], 'R0 -> MDR, 写内存')],
'INC': [('T4', ['R0'], ['Y'], ['R0out', 'Yin'], 'R0 -> Y'),
('T5', [], ['Z'], ['ALUop=INC', 'Zin'], 'Y + 1 -> Z (常数 1 不占总线)'),
('T6', ['Z'], ['R0'],['Zout', 'R0in'], 'Z -> R0')],
}
def run(name, exec_steps, verbose=False):
steps = FETCH + exec_steps
conflicts = []
for t, outs, ins, sig, note in steps:
if len(outs) > 1:
conflicts.append((t, outs))
if verbose:
print(' %s %-26s | %s' % (t, note, ' '.join(sig) or '(译码)'))
return len(steps), conflicts
print('=== 例 2: ADD R0, R1 的完整微操作 ===')
n, _ = run('ADD', EXEC['ADD'], verbose=True)
print(' 总拍数 = %d (取指 4 + 执行 3)' % n)
print('\n=== 例 4: 五条指令的时钟数统计 ===')
tot = 0
for name in ['ADD', 'SUB', 'LOAD', 'STORE', 'INC']:
n, cf = run(name, EXEC[name])
tot += n
print(' %-6s 取指 %d + 执行 %d = %d 拍 (总线冲突: %s)'
% (name, 4, n - 4, n, '无' if not cf else cf))
print(' 单总线合计 %d 拍 ; 三总线 5 x 3 = 15 拍 ; 比值 %.2f' % (tot, tot / 15))
print('\n=== 全表总线冲突检查 ===')
bad = 0
for name in ['ADD', 'SUB', 'LOAD', 'STORE', 'INC']:
_, cf = run(name, EXEC[name])
bad += len(cf)
print(' 5 条指令共 %d 处总线冲突 -> %s' % (bad, '全部合法, 每拍至多 1 个 out' if bad == 0 else '存在问题'))
print('\n=== 例 5: 寄存器位数 ===')
import math
cap_b, word_b = 4 * (1 << 30), 4
print(' 4GB 按字节编址: 地址 %d 位 -> PC = MAR = %d 位' % (math.log2(cap_b), int(math.log2(cap_b))))
print(' 按字编址: 地址 %d 位 -> PC = MAR = %d 位' % (math.log2(cap_b / word_b), int(math.log2(cap_b / word_b))))
print(' 存储字长 %dB -> MDR = %d 位 ; 指令字长 %dB -> IR = %d 位' % (word_b, word_b * 8, word_b, word_b * 8))
python 本站为静态站,不提供在线运行;可复制到本地用 gcc / python 执行
输出对照:ADD R0, R1 共 7 拍(取指 4 + 执行 3),逐拍控制信号与例 2 一致;5 条指令单总线合计 34 拍、与三总线 15 拍之比 2.27,与例 4 手算一致;总线冲突检查为 0 处;寄存器位数输出 按字节编址 32 位、按字编址 30 位,与例 5 一致。
考点
考点
1. 必背结论
- 数据通路 = 操作元件(组合逻辑:ALU/MUX/译码器)+ 存储元件(时序逻辑:寄存器/存储器)+ 互连总线。
- 判据:输出只由当前输入决定 → 操作元件;有记忆、受时钟同步 → 存储元件。
- 单总线一次只能传一个数据 → 所以 ALU 需要 Y(输入暂存)与 Z(结果暂存);三总线不需要。
- 控制信号命名:
XXout= 打开三态门送上总线;XXin= 本拍末锁存总线上的数据。 - 铁律:一拍之内最多只能有一个
XXout,否则总线冲突。 - 取指周期 4 拍:
PC→MAR/M(MAR)→MDR 且 (PC)+1→PC/MDR→IR/OP(IR)→CU(译码不占总线)。 - 两操作数运算的执行固定三拍:
→Y/ALU 算 →Z/→目标寄存器。 - MAR = 地址位数;MDR = 存储字长;IR = 指令字长;PC = 地址位数。
2. 高频陷阱
(PC)+1 → PC不单独占拍:它与"读内存"并行(PC 有独立加法器)。把它拆成两拍会多算一拍。- 译码(
T3)占拍但不占总线:题目问"占用总线的拍数"时是 3,问"取指周期共几拍"时是 4。看清问的是哪个。 STORE只要 2 拍(无运算),LOAD要 3 拍。两者 T4 相同、T5 相反——这个对比是命题点。- 数据不能直接写内存:必须先进 MDR(
R0 → MDR,再由MemW落盘)。少写这一步整题逻辑就断了。 INC也要 3 拍:通用寄存器没有独立加一逻辑,必须走 ALU 完整三拍。只有 PC 有特权。IR(addr) → MAR这一拍的信号名:形式地址字段从 IR 直接送上总线(IRout),不经过 ALU。- "按字节编址"还是"按字编址"决定 PC/MAR 位数:
按字节编址是 32 位,按字(4B)编址是 30 位。 - 总线数越多越快,但面积与引脚成本上升:别把"三总线优于单总线"当成无条件结论。
- Y 与 Z 不是"程序员可见寄存器":它们在数据通路内部,指令里不能直接寻址(这是与 R0~Rn 的本质区别)。
3. 解题模板("写出某指令的微操作序列")
① 取指 4 拍照抄(所有指令共用): PC->MAR / M->MDR,(PC)+1->PC / MDR->IR / 译码
② 判断指令类型:
两操作数运算(ADD/SUB/AND/OR) -> 送 Y / 算入 Z / 写回 共 3 拍
取数 LOAD -> 地址->MAR / 读->MDR / MDR->Reg 共 3 拍
存数 STORE -> 地址->MAR / Reg->MDR,写 共 2 拍
自增自减 INC/DEC -> 同运算, 第二个操作数是常数 1 共 3 拍
转移 JMP / 分支 -> 地址->PC (条件成立才写) 1~2 拍
③ 逐拍核对: 每拍至多 1 个 "out"; 需要锁存的部件都有 "in"
④ 统计拍数并给出总线占用拍数4. 与相邻章节的接口
- 第 31 篇(控制器):数据通路是"手", 控制器是"脑"。本篇写的是"每一拍数据怎么搬",控制器负责按拍发出这些
XXout/XXin信号——硬布线用组合逻辑发,微程序用微指令发。 - 第 32 篇(流水线):取指占了单总线 58.8% 的拍数(例 4),流水线正是要把这些拍数通过重叠执行藏掉。
- 第 33 篇(异常):EPC/断点现场保存要动 PC 与寄存器,都是在数据通路上完成的搬运。
- 第 20/22 篇:本篇执行的就是那些指令——
IR(addr)就是第 22 篇 I 型的 imm 字段(第 20 篇的"形式地址 A")。 - 《电 · 电路与数字逻辑》第 14 篇:寄存器、三态门、寄存器堆的电路实现(74LS374 / 74LS244 / "D 端二选一"做 LOAD)。
小结
- 数据通路 = 操作元件 + 存储元件 + 互连;前者"算与选",后者"记住",总线把它们连起来。
- 单总线的核心矛盾:总线一次只能送一个数据,而 ALU 要两个输入、还要保存结果——所以必须有 Y 和 Z。三总线不需要。
- 微操作的写法:
对应左 右 左out,右in。检查答案只需看"每拍out是否只有一个"。 - 取指 4 拍(
PC→MAR、M→MDR 且 PC+1、MDR→IR、译码),所有指令共用。 - 两操作数运算固定三拍、
LOAD三拍、STORE两拍、INC三拍;只有 PC 有"独立加一"的特权。 - MAR = 地址位数、MDR = 存储字长、IR = 指令字长;按字节/按字编址会改变地址位数。
- 单总线 vs 三总线:34 拍 vs 15 拍(2.27 倍);取指占单总线 58.8% —— 这是下一篇流水线的入口。
下一篇:控制器:硬布线与微程序
评论(0)
当前浏览器不允许本地存储,评论无法保存。
还没有评论,来说两句。