指令周期 -- 取指、译码、执行、写回
CPU 执行每一条指令都遵循同样的四步流程,就像一个永不停止的流水线节拍。本讲将带你深入了解这四步分别做了什么,并用 Python 模拟一个完整 CPU 的指令执行过程。
生活中的类比:餐厅后厨
想象你是一家餐厅的主厨。客人下单后,你按以下步骤出菜:
- 取单(Fetch):从前台取一张新的点菜单,看看客人点了什么
- 看单(Decode):分析菜单——「红烧肉」需要五花肉、酱油、冰糖,「清炒时蔬」需要青菜、蒜末
- 做菜(Execute):实际开火烹饪,切菜、翻炒、调味
- 出菜(Write Back):把做好的菜装盘,交给服务员端走
每张点菜单都要经过这四步。做完一道菜,再去取下一张单。CPU 也是这样——取一条指令、解析一条指令、执行一条指令、写回结果,周而复始。
这个四步循环是 CPU 最核心的工作节奏。从你电脑开机到关机的每一秒,CPU 都在不断地重复「取指-译码-执行-写回」这个循环,每秒执行数十亿次。
四个阶段的详解
阶段一:取指(Fetch)
CPU 首先要从内存中「取出」下一条要执行的指令。
有一个特殊的寄存器叫程序计数器(PC,Program Counter),它始终保存着下一条指令在内存中的地址。
取指阶段做了什么:
- CPU 把 PC 中的地址放到地址总线上
- CPU 在控制总线上发出「读」信号
- 内存返回该地址处的指令数据
- 指令被放入指令寄存器(IR,Instruction Register)
- PC 自动加 1,指向下一条指令
这里有一个关键设计:PC 自动递增。这意味着 CPU 默认会按顺序执行指令,除非遇到跳转指令(如 if-else、循环)来改变 PC 的值。
阶段二:译码(Decode)
取回来的指令是一串二进制数字(比如 0001 0010 0011),CPU 需要「翻译」它。
译码阶段的核心工作:
- 识别操作码(Opcode):指令的前几位告诉 CPU 要做什么——是加法、减法、加载数据、还是跳转?
- 提取操作数(Operands):指令的后面几位指明操作对象——用哪个寄存器?内存中的哪个地址?
- 设置控制信号:控制器根据译码结果,向 ALU、寄存器组、总线等部件发出相应的控制信号
比如一条指令 ADD R1, R2, R3,译码后 CPU 知道:让 ALU 做加法运算,输入来自寄存器 R2 和 R3,结果存到 R1。
阶段三:执行(Execute)
这是指令真正「干活」的阶段。根据指令类型不同,执行阶段做的事情也不同:
| 指令类型 | 执行阶段做什么 | 涉及的硬件 |
|---|---|---|
| 算术运算(ADD、SUB 等) | ALU 对操作数进行运算 | ALU、寄存器组 |
| 逻辑运算(AND、OR 等) | ALU 对操作数进行位运算 | ALU、寄存器组 |
| 内存读取(LOAD) | 通过地址总线访问内存,读取数据 | 地址总线、数据总线、内存 |
| 内存写入(STORE) | 通过地址总线访问内存,写入数据 | 地址总线、数据总线、内存 |
| 跳转(JUMP、BRANCH) | 修改 PC 的值,改变执行流程 | 控制器、PC 寄存器 |
阶段四:写回(Write Back)
执行阶段产生的结果需要被「写入」目标位置。
写回的目标可能是:
- 寄存器:将运算结果存入通用寄存器(最常见)
- 内存:将结果存储到内存中的某个地址
- 标志寄存器:更新状态标志(如结果是否为零、是否溢出、是否为负等)
写回完成后,一条指令的执行周期正式结束。如果 PC 仍然指向有效的指令地址,CPU 自动回到取指阶段,开始下一条指令。
指令周期的时间线视图
下面这个时间线展示了 3 条指令依次执行的完整过程:
时钟周期: 1 2 3 4 5 6 7 8 9 10 11 12
指令1: [取指] [译码] [执行] [写回]
指令2: [取指] [译码] [执行] [写回]
指令3: [取指] [译码] [执行] [写回]
↑ 指令1 完成后才开始指令2,完全串行执行
注意这里每条指令用了 4 个周期,3 条指令共用了 12 个周期。后面讲到流水线时,你会看到如何大幅缩短这个时间。
交互演示:SimpleCPU 模拟器
下面这个 Python 程序模拟了一个极简 CPU 的完整指令周期。它支持 LOAD(加载数值)、ADD(加法)、MUL(乘法)、CMP(比较+条件跳转)和 STORE(打印输出)指令,并会详细打印每个阶段的日志。
实例
SimpleCPU 模拟器 — 完整指令周期演示
支持指令:LOAD, ADD, MUL, CMP+JGT, STORE, HALT
每一条指令都会经历:取指(IF) → 译码(ID) → 执行(EX) → 写回(WB)
"""
class SimpleCPU:
"""一个教学用的 CPU 模拟器,展示指令周期的四个阶段"""
def __init__(self):
# 通用寄存器:R0~R3,初始值都是 0
self.registers = {'R0': 0, 'R1': 0, 'R2': 0, 'R3': 0}
# 标志寄存器:记录比较结果
self.flags = {'ZERO': False, 'GREATER': False}
# 程序计数器:指向下一条指令在内存中的位置
self.pc = 0
# 指令寄存器:存放当前正在执行的指令
self.ir = None
# 内存:存放程序的指令序列
self.memory = []
# 统计各个阶段执行的次数
self.stats = {'fetch': 0, 'decode': 0, 'execute': 0, 'writeback': 0}
self.total_cycles = 0
# 控制标志
self.running = False
def load_program(self, instructions):
"""将指令列表加载到内存中"""
self.memory = instructions
self.pc = 0
self.running = True
print(f"[系统] 程序加载完毕,共 {len(instructions)} 条指令")
print(f"[系统] 初始寄存器: {self.registers}")
print("=" * 60)
def fetch(self):
"""阶段 1:取指 (Instruction Fetch)"""
self.total_cycles += 1
if self.pc >= len(self.memory):
self.running = False
return None
# 从内存中读取 PC 指向的指令
instruction = self.memory[self.pc]
self.ir = instruction
old_pc = self.pc
self.pc += 1 # PC 自动递增
self.stats['fetch'] += 1
print(f"[周期 {self.total_cycles}] 取指 (IF)")
print(f" PC={old_pc} → 从内存地址 {old_pc} 读取指令")
print(f" 指令内容: \"{instruction}\"")
print(f" PC 更新: {old_pc} → {self.pc}")
print(f" IR ← \"{instruction}\"")
return instruction
def decode(self, instruction):
"""阶段 2:译码 (Instruction Decode)"""
self.total_cycles += 1
self.stats['decode'] += 1
# 解析操作码和操作数
parts = instruction.split()
opcode = parts[0].upper() # 操作码
operands = parts[1:] if len(parts) > 1 else [] # 操作数
print(f"[周期 {self.total_cycles}] 译码 (ID)")
print(f" 操作码 (Opcode): {opcode}")
print(f" 操作数 (Operands): {operands}")
# 解释操作码的含义
opcode_meaning = {
'LOAD': '从立即数加载到寄存器',
'ADD': '寄存器加法',
'MUL': '寄存器乘法',
'CMP': '比较两个寄存器的值',
'JGT': '条件跳转(大于时跳转)',
'STORE':'将寄存器值输出',
'HALT': '停止执行',
}
if opcode in opcode_meaning:
print(f" 含义: {opcode_meaning[opcode]}")
return opcode, operands
def execute(self, opcode, operands):
"""阶段 3:执行 (Execute)"""
self.total_cycles += 1
self.stats['execute'] += 1
print(f"[周期 {self.total_cycles}] 执行 (EX)")
if opcode == 'HALT':
print(f" HALT: 结束程序执行")
self.running = False
elif opcode == 'LOAD':
# LOAD Rx, value → 将立即数 value 存入寄存器 Rx
reg = operands[0]
value = int(operands[1])
print(f" 操作: 将立即数 {value} 加载到寄存器 {reg}")
print(f" 涉及部件: 数据总线(传输立即数 {value})")
# 暂存结果,写回阶段再更新寄存器
return ('LOAD', reg, value)
elif opcode == 'ADD':
# ADD Rx, Ry → Rx = Rx + Ry
rx, ry = operands[0], operands[1]
a = self.registers[rx]
b = self.registers[ry]
result = a + b
print(f" 操作: ALU 执行 {rx}({a}) + {ry}({b}) = {result}")
print(f" 涉及部件: ALU(运算器)、寄存器组")
return ('WRITE_REG', rx, result)
elif opcode == 'MUL':
# MUL Rx, Ry → Rx = Rx * Ry
rx, ry = operands[0], operands[1]
a = self.registers[rx]
b = self.registers[ry]
result = a * b
print(f" 操作: ALU 执行 {rx}({a}) * {ry}({b}) = {result}")
print(f" 涉及部件: ALU(运算器)、寄存器组")
return ('WRITE_REG', rx, result)
elif opcode == 'CMP':
# CMP Rx, Ry → 比较两个寄存器,设置标志位
rx, ry = operands[0], operands[1]
a = self.registers[rx]
b = self.registers[ry]
self.flags['ZERO'] = (a == b)
self.flags['GREATER'] = (a > b)
print(f" 操作: 比较 {rx}({a}) 和 {ry}({b})")
print(f" 结果: ZERO={self.flags['ZERO']}, GREATER={self.flags['GREATER']}")
print(f" 涉及部件: ALU(比较器)、标志寄存器")
return ('FLAGS', None, None)
elif opcode == 'JGT':
# JGT address → 如果 GREATER 标志为真,则跳转到 address
target = int(operands[0])
if self.flags['GREATER']:
old_pc = self.pc
self.pc = target
print(f" 条件满足 (GREATER=True): PC 跳转 {old_pc} → {target}")
print(f" 涉及部件: 控制器、PC 寄存器")
else:
print(f" 条件不满足 (GREATER=False): 不跳转,PC 保持 {self.pc}")
return None
elif opcode == 'STORE':
# STORE Rx → 输出寄存器的值
reg = operands[0]
value = self.registers[reg]
print(f" 操作: 输出寄存器 {reg} 的值")
return ('OUTPUT', reg, value)
return None
def writeback(self, action):
"""阶段 4:写回 (Write Back)"""
self.total_cycles += 1
self.stats['writeback'] += 1
if action is None:
print(f"[周期 {self.total_cycles}] 写回 (WB) - 无操作")
return
action_type = action[0]
print(f"[周期 {self.total_cycles}] 写回 (WB)")
if action_type == 'LOAD':
reg, value = action[1], action[2]
old_val = self.registers[reg]
self.registers[reg] = value
print(f" 目标: 寄存器 {reg}")
print(f" 数据: {old_val} → {value}")
print(f" 通道: 数据总线 → 寄存器组")
elif action_type == 'WRITE_REG':
reg, result = action[1], action[2]
old_val = self.registers[reg]
self.registers[reg] = result
print(f" 目标: 寄存器 {reg}")
print(f" 数据: {old_val} → {result}")
print(f" 通道: ALU 输出 → 寄存器组(内部总线)")
elif action_type == 'OUTPUT':
reg, value = action[1], action[2]
print(f" 输出: 寄存器 {reg} = {value}")
print(f" 通道: 寄存器组 → 数据总线 → I/O 接口")
print(f" 当前寄存器状态: {self.registers}")
print(f" 标志位: {self.flags}")
print("-" * 60)
def run(self):
"""运行整个程序,逐条指令执行"""
print("=" * 60)
print("SimpleCPU 模拟器启动 — 指令周期演示 (runoob)")
print("=" * 60)
while self.running:
# 四个阶段的循环
instruction = self.fetch() # 阶段 1: 取指
if instruction is None:
break
opcode, operands = self.decode(instruction) # 阶段 2: 译码
action = self.execute(opcode, operands) # 阶段 3: 执行
self.writeback(action) # 阶段 4: 写回
# 执行完毕,打印统计信息
print("\n" + "=" * 60)
print("程序执行完毕!统计信息:")
print(f" 总时钟周期数: {self.total_cycles}")
print(f" 取指阶段执行: {self.stats['fetch']} 次")
print(f" 译码阶段执行: {self.stats['decode']} 次")
print(f" 执行阶段执行: {self.stats['execute']} 次")
print(f" 写回阶段执行: {self.stats['writeback']} 次")
print(f" 最终寄存器: {self.registers}")
print(f" 最终标志位: {self.flags}")
print(f" 最终 PC: {self.pc}")
print("=" * 60)
# ===== 运行演示程序 =====
# 程序逻辑:计算 1+2 的结果,然后乘以 3,并与 10 比较
# LOAD R0, 1 → R0 = 1
# LOAD R1, 2 → R1 = 2
# ADD R0, R1 → R0 = R0 + R1 = 1 + 2 = 3
# LOAD R2, 3 → R2 = 3
# MUL R0, R2 → R0 = R0 * R2 = 3 * 3 = 9
# CMP R0, R2 → 比较 R0(9) 和 R2(3),设置 GREATER=True
# STORE R0 → 输出 R0 的值
# HALT → 停止
cpu = SimpleCPU()
program = [
"LOAD R0 1", # R0 = 1
"LOAD R1 2", # R1 = 2
"ADD R0 R1", # R0 = R0 + R1 = 3
"LOAD R2 3", # R2 = 3
"MUL R0 R2", # R0 = R0 * R2 = 9
"CMP R0 R2", # 比较 9 和 3,GREATER=True
"JGT 8", # 如果 GREATER 则跳转到指令 8
"LOAD R3 0", # (这条不会执行,因为 JGT 跳过了)
"STORE R0", # 输出 R0 = 9
"HALT", # 停止
]
cpu.load_program(program)
cpu.run()
上面代码在每条指令的每个阶段都打印了详细日志,你可以清楚看到 CPU 内部在每个时钟周期做了什么工作。注意观察:
- PC 自动递增:每执行一条指令,PC 自动 +1(取指阶段)
- 条件跳转:CMP+JGT 组合实现了 if 判断——如果 R0 > R2 就跳转到指令 8,跳过了指令 7
- 寄存器作为中转站:所有数据都要先加载到寄存器,ALU 从寄存器取数、算完写回寄存器
交互演示:CPU 内部结构 & 指令周期分步播放
下面用 vis-network 绘制了简化版 CPU 内部结构图。点击「下一步」按钮,观察指令在四个阶段中分别使用了哪些硬件部件,被激活的部件会以对应阶段颜色高亮显示。
