处理器通过重复一个周期来运行程序:从内存取出(fetch)下一条指令,解码(decode)它,然后执行(execute)它。追踪这个周期,就能看到每一步之后各寄存器存着什么。
这项技能属于硬件与处理。它是下一课每个寄存器的作用的基础,也与算法题里的追踪表密切相关。
每个阶段发生什么?
我们使用有五个寄存器的简化模型:程序计数器(PC)、内存地址寄存器(MAR)、内存数据寄存器(MDR)、当前指令寄存器(CIR)和累加器(ACC)。
Fetch
- PC 中的地址被复制到 MAR。
- PC 加 1,指向下一条指令。
- MAR 所指内存位置的内容被复制到 MDR。
- MDR 的内容被复制到 CIR。
Decode。 控制单元(control unit)弄清 CIR 中指令的意思:做什么操作,用哪个地址。
Execute。 执行指令。加载(load)把一个值复制到 ACC,算术指令使用算术逻辑单元(ALU)并把结果留在 ACC,存储(store)则把 ACC 复制到内存。
用伪代码写,fetch 如下:
MAR ← PC
PC ← PC + 1
MDR ← Memory[MAR]
CIR ← MDR
例题详解
内存中有下面的程序和数据。LDA 200 把地址 200 的值加载到 ACC,ADD 201 把地址 201 的值加到 ACC,STO 202 把 ACC 存到地址 202,END 表示停止。
| 地址 | 内容 |
|---|---|
| 100 | LDA 200 |
| 101 | ADD 201 |
| 102 | STO 202 |
| 103 | END |
| 200 | 7 |
| 201 | 5 |
| 202 | 0 |
PC 从 100 开始。ACC 一开始是空的,用短横线表示。
| 步骤 | PC | MAR | MDR | CIR | ACC |
|---|---|---|---|---|---|
| 开始 | 100 | - | - | - | - |
| Fetch 1 | 101 | 100 | LDA 200 | LDA 200 | - |
| Execute 1 | 101 | 200 | 7 | LDA 200 | 7 |
| Fetch 2 | 102 | 101 | ADD 201 | ADD 201 | 7 |
| Execute 2 | 102 | 201 | 5 | ADD 201 | 12 |
| Fetch 3 | 103 | 102 | STO 202 | STO 202 | 12 |
| Execute 3 | 103 | 202 | 12 | STO 202 | 12 |
| Fetch 4 | 104 | 103 | END | END | 12 |
关键数值要核对两次。Execute 2 中,ALU 计算 7 + 5 = 12。Execute 3 中,12 被写入地址 202,所以位置 202 从 0 变成 12。
每次 fetch 读取的是 PC 增加之前的地址:100、101、102、103。
同一个周期用 Python 写出来,用字典(dictionary)当作内存:
memory = {100: "LDA 200", 101: "ADD 201", 102: "STO 202", 103: "END",
200: 7, 201: 5, 202: 0}
pc = 100
acc = 0
while True:
mar = pc
pc = pc + 1
mdr = memory[mar]
cir = mdr
op = cir.split()[0]
if op == "END":
break
address = int(cir.split()[1])
if op == "LDA":
acc = memory[address]
elif op == "ADD":
acc = acc + memory[address]
elif op == "STO":
memory[address] = acc
print(memory[202]) # 12
这是教学模型,不是真实处理器的构造方式,你可以用安全的 Python 推理沙盒运行这样的小例子。
要留意的错误
常见的失误是在 execute 阶段之后才更新 PC,或让 PC 停在正在运行的指令的地址上。
错误的 Fetch 1 行: PC 100,MAR 100,MDR LDA 200,CIR LDA 200
学生让 PC 停在 100,因为“指令就在那里”。
PC 在 fetch 期间就加 1,所以 Fetch 1 之后它已经是 101。这个错误会扩散:之后每个 MAR 都差一,整个追踪就全错了。改正的办法是把“PC 加 1”写成每次 fetch 的第二行。
自我检测
1. 一次 fetch 开始时 PC 是 300。写出 fetch 前两步之后 MAR 和 PC 的值。
查看答案
第 1 步把 PC 复制到 MAR,所以 MAR = 300。第 2 步把 PC 加 1,所以 PC = 301。
2. 控制单元解码指令时,指令存放在哪个寄存器?
查看答案
CIR(当前指令寄存器)。MDR 只在指令从内存传来的途中暂时存放它。
3. 指令 JMP 150 存放在地址 120。写出它 fetch 之后和 execute 之后的 PC。
查看答案
fetch 之后 PC = 121。执行跳转会用新地址覆盖 PC,所以 execute 之后 PC = 150。下一次 fetch 使用 MAR = 150。
下一步
会追踪这个周期之后,请把每个寄存器与其作用对应起来,再试硬件与处理练习题。受限伪代码追踪训练器适合培养同样的逐步追踪数值的习惯。
有些学生在课堂上能跟上追踪,一个人面对空白纸时却理不出头绪。线上一对一计算机科学补习的老师可以陪你一个寄存器一个寄存器地追踪,直到这个规律稳固下来。