💻 第三章 体验一次手工汇编
📄 一句话概括

CPU 的一生只有四步循环:取指令、译码、执行、PC 前进——跳转不过是把最后一步换成【改 PC】。

📝 本节和前面几节的关系

寄存器认全了,这一节让它们动起来:逐条追踪一段真实程序的执行。

一、执行四步循环

取指令:按 PC 的地址从内存里读出一条指令——PC 就是 CPU 的眼睛; 译码:看操作码,弄清这条指令要干什么(3E 是装载、D3 是输出、76 是停机); 执行:干活——改寄存器、读写内存或端口; PC 前进:加上本条指令的长度,指向下一条。 四步一个循环,一节拍一节拍地进行——计算机的一生就是这四步的无限重复。

二、追踪一段真程序

程序(第二章那 5 个字节):0x0000: 3E 80(LD A,128)、0x0002: D3 02(OUT (2),A)、0x0004: 76(HALT)。 第 1 步:PC=0x0000 取到 3E,译码【装载】,执行把下一字节 80 装进 A,PC 前进到 0x0002; 第 2 步:取到 D3,译码【输出】,把 A 写端口 2,PC 到 0x0004; 第 3 步:取到 76,译码【停机】,CPU 收工。

三、循环与分支怎么来

全是这四步的变体:循环=执行时把 PC 改回前面某地址(时间倒流);分支=满足条件才改 PC。 所以第四、五章讲的程序流程(顺序、循环、分支)在硬件层面只有一件事:PC 往哪走。

四、动手 Demo:逐步执行跟踪器

用 Python 模拟 CPU 的四步循环,每步打印 PC、A、端口的值。

# 四步循环: 取指 -> 译码 -> 执行 -> PC 前进
mem = {0: 0x3E, 1: 0x80, 2: 0xD3, 3: 0x02, 4: 0x76}
pc, a, port = 0, 0, None
while True:
    op = mem[pc]
    if op == 0x3E:
        a = mem[pc+1]; pc += 2
    elif op == 0xD3:
        port = a; pc += 2
    elif op == 0x76:
        break
    print("PC=0x%04X A=%d 端口=%s" % (pc, a, port))

实跑输出:

--------------------------------------------------------------------
逐条追踪 5 字节程序的一生
--------------------------------------------------------------------

把第二章那 5 个字节装进内存,然后一条条跟踪执行,
每一步都打印 PC、A 和端口的当前值:

[第 1 步] PC=0x0000  取指=0x3E (LD A, 128)
          执行: A <- 128
          A=128  端口2=None
[第 2 步] PC=0x0002  取指=0xD3 (OUT (2), A)
          执行: 端口 2 <- A(128)
          A=128  端口2=128
[第 3 步] PC=0x0004  取指=0x76 (HALT)
          执行: 停机

追踪完这一遍,CPU 的整个工作循环就现形了:
    取指令 -> 译码(看操作码) -> 执行 -> PC 前进
四步一节拍一节拍地循环,计算机的一生就是这四步的无限重复。

代码里的 if/elif 就是【译码】这一步——真实 CPU 用电路实现,速度是纳秒级。 PC 前进的步长等于指令长度(2 字节的指令加 2)——所以指令表里每条的字节数很重要。

30 秒实验

  1. 把跟踪器代码抄来跑,改一改内存里第一个字节(把 3E 改成 76)——程序立刻变了行为。
  2. 给跟踪器加一条【JP 0x0000】支持——你会看到 PC 被改回 0x0000,程序无限循环。

常见误区

  1. 以为译码是单独的硬件阶段就以为它很慢——译码就在取指后的那个节拍里完成。
  2. 以为循环是【特殊机制】——循环只是 PC 被改回前面,硬件上毫无特殊之处。
自测三问(点开对答案) 1. CPU 执行的四步循环是什么? —— 取指令、译码、执行、PC 前进——循环往复。 2. 循环与分支在硬件层面是什么操作? —— 都是改 PC:循环改回前地址,分支满足条件才改。 3. 为什么 PC 前进的步长因指令而异? —— 不同指令长度不同(1~4 字节),要跳过整条指令才指向下一条。

相关笔记