在大屏或电脑上体验更佳——动画和操作区域更宽敞。
输入

指令执行

指令执行是 CPU 重复进行的过程:读取程序计数器 PC 指向的字,取得下一地址,按字段译码,再执行操作码指定的动作。本课 TinyCPU 在取指时把 PC 加 1,依次展示取指、译码、执行、写回;某个操作码不需要的阶段会跳过。

CPU 不直接认识源代码中的函数、循环或文件结束。它按地址读取指令,维护寄存器和内存状态。高层结构通过指令安排与控制流实现。

指令执行如何工作

TinyCPU 有四个寄存器,每条指令占一个 16 位字,指令和数据放在同一列内存。字段固定:最高四位 op 选择操作,rd、rs 各两位指定目标和源寄存器,最低八位 imm 保存立即数字段。

imm 的含义由 op 决定:LDI r1, 2 把 2 当值,JNZ r1, 2 把 2 当地址。译码只是切分字段,不做源语言解析或前瞻。模型检查操作码是否合法;ADD 不读取 imm,因此该字段即使不是零,也不影响 ADD。

PC 也是保存数字的状态。在尚未译码的取指阶段先加 1,所以执行指令时它已指向下一字。分支若成立,后续写回再覆盖这个地址;若不成立,保留取指时算好的地址。

逐步演示

默认 n=2,程序有七条静态指令,累加 2+1 后存入内存。第三列是给读者的注释,CPU 只读取字:

地址指令字解释
00001 01 00 00000010LDI r1, 2
10001 10 00 00000000LDI r2, 0
20010 10 01 00000000ADD r2, r1
30011 01 00 00000001SUBI r1, 1
40100 00 01 00000010JNZ r1, 2
50101 00 10 00000111STORE r2, 7
61111 00 00 00000000HALT
70000 00 00 00000000非指令数据
  1. LDI r1, 2:PC=0,地址 0 的字进入指令寄存器,PC 变 1。op=0001 表示 LDI,rd=01,imm=00000010,写入 r1=2。数值原本就在指令中,不需 ALU,跳过执行阶段。
  2. LDI r2, 0:写 r2=0,建立累加器。
  3. ADD r2, r1:译码前 PC 已变 3。ALU 读取 r2=0、r1=2,结果 2 在写回阶段进入 r2。imm=00000000 没被使用。
  4. SUBI r1, 1:ALU 用 r1=2 减指令中的 1,r1 变 1。
  5. JNZ r1, 2:取指先把 PC 变 5。检查 r1 非零成立,写回 PC=2。没有另设的循环机构或栈,下一次取指因 PC 的值而读取地址 2。
  6. 再次 ADD r2, r1,r2=2+1=3。
  7. 再次 SUBI r1, 1,r1=0。
  8. 再次 JNZ r1, 2:同地址、同指令字、同字段,但 r1=0,条件失败,不写 PC,保留取指时得到的 5。顺序前进来自没有覆盖 PC。
  9. STORE r2, 7:r2=3,imm 这次表示地址 7,把 3 写入与程序共用的内存列。
  10. HALT:操作码 1111 结束执行,共执行十条动态指令。

对本课正整数 n,计数为 3n+4:初始化两条,每轮三条,收尾两条。n=5 时是十九条;n=3 时多跑一轮相同循环。七条静态指令不变,动态计数随同一地址被访问的次数增加。

跳转也是状态写入

LDI、ADD、SUBI 写寄存器,STORE 写内存,条件成立的 JNZ 写 PC。结果写向不同目标,就产生不同效果。TinyCPU 的教学图用同类写回动作展示这些更新,不表示所有真实 CPU 共用一条物理写通路。

while 循环可通过条件成立时向后改写下一取指地址实现;if 可跳过一段指令。函数调用还需要保存返回地址,以便返回时恢复控制位置;实际约定可用栈或链接寄存器。本课不实现调用,只说明地址更新如何支撑控制流。

若处理器需要多个周期才知道分支结果,就要等待或预测下一地址,这连接到分支预测和流水线。

边界情况

  • n=1:循环体只执行一次,测试时 r1 已为 0,分支从不跳转。
  • No branch:去掉跳转,PC 每条加一,执行初始化、一次 ADD 和 SUBI 后直接存储并 HALT。n>1 时,省掉的后续循环使最终寄存器和结果不同。
  • Program edits itself:STORE 把数字 3 写到地址 4,覆盖该处 HALT。写入本身不报错。后面执行两条指令,再取地址 4,读取 0000 0000 0000 0011,因 op=0000 非法而停止。错误在取指译码时出现。真实系统的代码页权限能阻止未经允许的修改。
  • Never returns:地址 1 的 JNZ 跳回地址 1,测试的寄存器永不递减。每次都按规则执行,本身没有结束条件。动画在六条指令后截断,CPU 不会因这个教学上限停机。
  • 跑过程序末尾:模型没有“源程序结束”标记。仍在分配内存内时会取到数据、零或其他字;越过模型内存边界则停止并报非法地址。HALT 是明确的结束指令。

常见错误

  • 认为 CPU 识别 main 或函数边界。运行环境按约定设置入口与初始状态,硬件执行指令地址。
  • 认为指令与数据必定分开。本课使用统一内存,PC 决定哪些字被当作指令;真实系统也可能有分离通路或地址空间,操作系统用权限限制访问。
  • 认为分支逐条“越过”中间指令。它更新地址,中间字根本不被取指。不跳转则保留先前递增的 PC。
  • 给 imm 固定一种含义。LDI 当字面值,SUBI 当减数,JNZ、STORE 当地址。
  • 认为未用字段必须为零。TinyCPU 的 ADD 忽略 imm;真实 ISA 对保留位各有规则,有些要求为零,否则陷入异常。
  • 把动态指令数当动画步数或时间。每条指令包含多个讲解动作,3n+4 计的是指令。真实耗时还受缓存、误预测和工作重叠影响。

模型范围

TinyCPU 一条指令执行完再开始下一条,直接访问模型内存。真实处理器可能使用流水、预测、乱序与缓存,不能用本课动作数当设备周期数。

指令集省略条件标志、变长或多字指令、计算地址的寻址方式、中断、内存映射 I/O 和特权级。寄存器初始化为零是教学选择;真实初值取决于架构、复位、操作系统和调用约定。

取指、译码、执行、更新结果这一结构具有共性,但取指时 PC 加一是 TinyCPU 的具体规则。真实指令长度与前端时序不同;许多机器把 PC 与通用寄存器分开,例如 Arm A64 不能把 PC 直接指定为操作数,分支也常用相对偏移而非 TinyCPU JNZ 的绝对地址。共同点是分支改变后续取指位置。