指令执行
指令执行是 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 只读取字:
| 地址 | 指令字 | 解释 |
|---|---|---|
| 0 | 0001 01 00 00000010 | LDI r1, 2 |
| 1 | 0001 10 00 00000000 | LDI r2, 0 |
| 2 | 0010 10 01 00000000 | ADD r2, r1 |
| 3 | 0011 01 00 00000001 | SUBI r1, 1 |
| 4 | 0100 00 01 00000010 | JNZ r1, 2 |
| 5 | 0101 00 10 00000111 | STORE r2, 7 |
| 6 | 1111 00 00 00000000 | HALT |
| 7 | 0000 00 00 00000000 | 非指令数据 |
LDI r1, 2:PC=0,地址 0 的字进入指令寄存器,PC 变 1。op=0001表示 LDI,rd=01,imm=00000010,写入 r1=2。数值原本就在指令中,不需 ALU,跳过执行阶段。LDI r2, 0:写 r2=0,建立累加器。ADD r2, r1:译码前 PC 已变 3。ALU 读取 r2=0、r1=2,结果 2 在写回阶段进入 r2。imm=00000000没被使用。SUBI r1, 1:ALU 用 r1=2 减指令中的 1,r1 变 1。JNZ r1, 2:取指先把 PC 变 5。检查 r1 非零成立,写回 PC=2。没有另设的循环机构或栈,下一次取指因 PC 的值而读取地址 2。- 再次
ADD r2, r1,r2=2+1=3。 - 再次
SUBI r1, 1,r1=0。 - 再次
JNZ r1, 2:同地址、同指令字、同字段,但 r1=0,条件失败,不写 PC,保留取指时得到的 5。顺序前进来自没有覆盖 PC。 STORE r2, 7:r2=3,imm 这次表示地址 7,把 3 写入与程序共用的内存列。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 的绝对地址。共同点是分支改变后续取指位置。