现在位置: 首页 > 计算机组成原理 > 正文

指令周期 -- 取指、译码、执行、写回

CPU 执行每一条指令都遵循同样的四步流程,就像一个永不停止的流水线节拍。本讲将带你深入了解这四步分别做了什么,并用 Python 模拟一个完整 CPU 的指令执行过程。


生活中的类比:餐厅后厨

想象你是一家餐厅的主厨。客人下单后,你按以下步骤出菜:

  1. 取单(Fetch):从前台取一张新的点菜单,看看客人点了什么
  2. 看单(Decode):分析菜单——「红烧肉」需要五花肉、酱油、冰糖,「清炒时蔬」需要青菜、蒜末
  3. 做菜(Execute):实际开火烹饪,切菜、翻炒、调味
  4. 出菜(Write Back):把做好的菜装盘,交给服务员端走

每张点菜单都要经过这四步。做完一道菜,再去取下一张单。CPU 也是这样——取一条指令、解析一条指令、执行一条指令、写回结果,周而复始。

这个四步循环是 CPU 最核心的工作节奏。从你电脑开机到关机的每一秒,CPU 都在不断地重复「取指-译码-执行-写回」这个循环,每秒执行数十亿次。


四个阶段的详解

阶段一:取指(Fetch)

CPU 首先要从内存中「取出」下一条要执行的指令。

有一个特殊的寄存器叫程序计数器(PC,Program Counter),它始终保存着下一条指令在内存中的地址。

取指阶段做了什么:

  1. CPU 把 PC 中的地址放到地址总线上
  2. CPU 在控制总线上发出「读」信号
  3. 内存返回该地址处的指令数据
  4. 指令被放入指令寄存器(IR,Instruction Register)
  5. 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 内部结构图。点击「下一步」按钮,观察指令在四个阶段中分别使用了哪些硬件部件,被激活的部件会以对应阶段颜色高亮显示。

CPU 内部结构 & 指令周期分步演示 (runoob)

取指 IF
译码 ID
执行 EX
写回 WB
点击「下一步」开始指令周期演示,观察各阶段中 CPU 内部哪些部件被激活。