普莉姆拉计算机 v3
MIPS-I 子集虚拟机 · 在《Alice in Cradle》中实现图灵完备
本虚拟机使用游戏的 .cmd 事件脚本系统构建。32 个 MIPS 寄存器、完整五级流水(取指→译码→执行→访存→写回)、延迟槽、系统调用、交互式调试。
一、架构概览
calc.cmd — 主菜单 + 实时寄存器面板
├─ reset.cmd — 初始化 32 个 MIPS 寄存器, PC=0x3000
├─ run_once.cmd — 取指→译码→执行→访存→写回
│ ├─ memory_operation/ — ROM/RAM 读写
│ ├─ register_operation/ — 32 寄存器读写基元
│ └─ decoder/ — 指令译码器
│ ├─ op6_0.cmd — op6=0 转 SPECIAL
│ ├─ op6_1~15.cmd — I/J 型指令
│ ├─ op6_32~43.cmd — Load/Store 指令
│ └─ special/ — funct6 二级分发 (R 型)
├─ run_100/1000.cmd — 批量执行
└─ input_int.cmd — SYSCALL 整数输入
二、寄存器
| 编号 | 名称 | 用途 | 说明 |
r0 | $zero | 零寄存器 | 恒为 0,写入无效 |
r1 | $at | 汇编器临时 | 伪指令用 |
r2 r3 | $v0 $v1 | 返回值 | 函数结果 / 系统调用号 |
r4~r7 | $a0~$a3 | 参数 | 函数参数 / 系统调用参数 |
r8~r15 | $t0~$t7 | 临时变量 | 调用者保存,函数内随意使用 |
r16~r23 | $s0~$s7 | 保留变量 | 被调用者保存 |
r24 r25 | $t8 $t9 | 临时变量 | 同上 |
r26 r27 | $k0 $k1 | 内核保留 | 异常处理用 |
r28 | $gp | 全局指针 | 初始值 0x1800 |
r29 | $sp | 栈指针 | 初始值 0x2ffc |
r30 | $fp | 帧指针 | |
r31 | $ra | 返回地址 | JAL/JALR 写入 |
特殊寄存器
| 名称 | 说明 | 初始值 |
pc | 程序计数器 (Program Counter) | 0x3000 |
hi | 乘法高 32 位 / 除法余数 | 0 |
lo | 乘法低 32 位 / 除法商 | 0 |
db / dbpc | 延迟槽标志 / 跳转目标地址 | 0 |
stopped | 停机标志 | 0 |
三、指令格式
每条指令 4 字节,小端序,按字段提取:
| 字段 | 位宽 | 提取方式 | 用途 |
op6 | 6 | &0xFC00 / 1024 | 主操作码 |
rs5 | 5 | &0x03E0 / 32 | 源寄存器 |
rt5 | 5 | &0x001F | 目标/第二源寄存器 |
rd5 | 5 | &0xF800 / 2048 | 目标寄存器 (R型) |
shamt5 | 5 | &0x07C0 / 64 | 移位量 (R型) |
funct6 | 6 | &0x003F | 功能码 (R型二级) |
imm16 | 16 | &0xFFFF | 立即数 (I型) |
jmm26 | 26 | (hi16 & 0x03FF)*65536 + lo16 | 跳转目标 (J型) |
有符号立即数扩展:signimm = ((imm16 + 32768) % 65536) - 32768
分支目标地址:bta = pc + 4 + signimm * 4
跳转目标地址:jta = jmm26 * 4
四、指令集
4.1 R 型指令 (op6=0 → funct6)
| funct6 | 指令 | 格式 | 说明 |
0 | SLL | SLL rd, rt, shamt | 逻辑左移 (rd = rt << shamt) |
2 | SRL | SRL rd, rt, shamt | 逻辑右移 (零填充高位) |
3 | SRA | SRA rd, rt, shamt | 算术右移 (符号填充高位) |
4 | SLLV | SLLV rd, rt, rs | 可变逻辑左移 (移位量 = rs[4:0]) |
6 | SRLV | SRLV rd, rt, rs | 可变逻辑右移 |
7 | SRAV | SRAV rd, rt, rs | 可变算术右移 |
8 | JR | JR rs | 寄存器跳转 (pc = rs) |
9 | JALR | JALR rd, rs | 寄存器跳转并链接 (ra = pc+8, pc = rs) |
12 | SYSCALL | SYSCALL | 触发系统调用 (见 §六) |
13 | BREAK | BREAK | 断点调试 (显示关键寄存器) |
16 | MFHI | MFHI rd | 读 HI 到 rd |
17 | MTHI | MTHI rs | 写 rs 到 HI |
18 | MFLO | MFLO rd | 读 LO 到 rd |
19 | MTLO | MTLO rs | 写 rs 到 LO |
24 | MULT | MULT rs, rt | 有符号乘 (HI:LO = rs × rt),仅低 32 位有效 |
25 | MULTU | MULTU rs, rt | 符号乘存根 (未完成) |
26 | DIV | DIV rs, rt | 有符号除 (LO=商, HI=余) |
27 | DIVU | DIVU rs, rt | 无符号除存根 (未完成) |
32 | ADD | ADD rd, rs, rt | 有符号加 (rd = rs + rt) |
33 | ADDU | ADDU rd, rs, rt | 无符号加 |
34 | SUB | SUB rd, rs, rt | 有符号减 (rd = rs - rt) |
35 | SUBU | SUBU rd, rs, rt | 无符号减 |
36 | AND | AND rd, rs, rt | 按位与 |
37 | OR | OR rd, rs, rt | 按位或 |
38 | XOR | XOR rd, rs, rt | 按位异或 |
39 | NOR | NOR rd, rs, rt | 按位或非 |
42 | SLT | SLT rd, rs, rt | 有符号比较置 1 (rd = rs < rt ? 1 : 0) |
43 | SLTU | SLTU rd, rs, rt | 无符号比较置 1 |
4.2 I 型指令 (立即数)
| op6 | 指令 | 格式 | 说明 |
8 | ADDI | ADDI rt, rs, imm | 立即数加 (rt = rs + signimm) |
9 | ADDIU | ADDIU rt, rs, imm | 无符号立即数加 (与 ADDI 等价) |
10 | SLTI | SLTI rt, rs, imm | 有符号立即数比较置 1 |
11 | SLTIU | SLTIU rt, rs, imm | 无符号立即数比较置 1 |
12 | ANDI | ANDI rt, rs, imm | 立即数按位与 (零扩展) |
13 | ORI | ORI rt, rs, imm | 立即数按位或 (零扩展) |
14 | XORI | XORI rt, rs, imm | 立即数按位异或 (零扩展) |
15 | LUI | LUI rt, imm | 加载立即数到高位 (rt = signimm << 16) |
4.3 Load / Store 指令
| op6 | 指令 | 格式 | 说明 |
32 | LB | LB rt, offset(rs) | 读字节,有符号扩展 |
33 | LH | LH rt, offset(rs) | 读半字 (2 字节),有符号扩展 |
35 | LW | LW rt, offset(rs) | 读字 (4 字节) |
36 | LBU | LBU rt, offset(rs) | 读字节,无符号扩展 |
37 | LHU | LHU rt, offset(rs) | 读半字,无符号扩展 |
40 | SB | SB rt, offset(rs) | 写字节 |
41 | SH | SH rt, offset(rs) | 写半字 (2 字节) |
43 | SW | SW rt, offset(rs) | 写字 (4 字节) |
地址计算:address = rs + signimm
4.4 分支 / 跳转指令
| op6 | 指令 | 格式 | 说明 |
1 | BGEZ | BGEZ rs, label | rs≥0 则跳转 (rt5=1) |
1 | BLTZ | BLTZ rs, label | rs<0 则跳转 (rt5=0) |
2 | J | J target | 无条件跳转 (26 位地址) |
3 | JAL | JAL target | 跳转并链接 (ra = pc+8) |
4 | BEQ | BEQ rs, rt, label | 相等则跳转 |
5 | BNE | BNE rs, rt, label | 不等则跳转 |
6 | BLEZ | BLEZ rs, label | rs≤0 则跳转 (rt5=0) |
7 | BGTZ | BGTZ rs, label | rs>0 则跳转 (rt5=0) |
五、指令编码总表
每条指令 32 位,字段布局如下。位 31 最高,位 0 最低。
5.1 类型模板
| 类型 | 31–26 | 25–21 | 20–16 | 15–11 | 10–6 | 5–0 |
| R |
op=0 |
rs |
rt |
rd |
shamt |
funct |
| I |
op |
rs |
rt |
imm (16-bit) |
| J |
op |
target (26-bit) |
5.2 R 型 (op=0)
| 指令 | rs | rt | rd | shamt | funct | 操作 |
SLL | 0 | src | dst | sa | 0 | rd = rt << sa |
SRL | 0 | src | dst | sa | 2 | rd = rt >> sa (logical) |
SRA | 0 | src | dst | sa | 3 | rd = rt >> sa (arithmetic) |
SLLV | cnt | src | dst | 0 | 4 | rd = rt << rs[4:0] |
SRLV | cnt | src | dst | 0 | 6 | rd = rt >> rs[4:0] (logical) |
SRAV | cnt | src | dst | 0 | 7 | rd = rt >> rs[4:0] (arithmetic) |
JR | target | 0 | 0 | 0 | 8 | pc = rs |
JALR | target | 0 | ra | 0 | 9 | rd=pc+8, pc=rs |
SYSCALL | — | 12 | 触发系统调用 |
BREAK | — | 13 | 调试断点 |
MFHI | 0 | 0 | dst | 0 | 16 | rd = HI |
MTHI | src | 0 | 0 | 0 | 17 | HI = rs |
MFLO | 0 | 0 | dst | 0 | 18 | rd = LO |
MTLO | src | 0 | 0 | 0 | 19 | LO = rs |
MULT | a | b | 0 | 0 | 24 | LO = a×b (低32) |
MULTU | a | b | 0 | 0 | 25 | stub |
DIV | num | den | 0 | 0 | 26 | LO=商, HI=余 |
DIVU | num | den | 0 | 0 | 27 | stub |
ADD | a | b | dst | 0 | 32 | rd = rs+rt |
ADDU | a | b | dst | 0 | 33 | rd = rs+rt |
SUB | a | b | dst | 0 | 34 | rd = rs−rt |
SUBU | a | b | dst | 0 | 35 | rd = rs−rt |
AND | a | b | dst | 0 | 36 | rd = rs & rt |
OR | a | b | dst | 0 | 37 | rd = rs | rt |
XOR | a | b | dst | 0 | 38 | rd = rs ^ rt |
NOR | a | b | dst | 0 | 39 | rd = ~(rs|rt) |
SLT | a | b | dst | 0 | 42 | rd = (rs<rt) ? 1:0 |
SLTU | a | b | dst | 0 | 43 | rd = (rs<rt) ? 1:0 |
5.3 I 型
| 指令 | op | rs | rt | imm | 操作 |
ADDI | 8 | base | dst | sign | rt = rs + signimm |
ADDIU | 9 | base | dst | sign | rt = rs + signimm |
SLTI | 10 | a | dst | sign | rt = (rs<signimm) |
SLTIU | 11 | a | dst | zero | rt = (rs<zeroimm) |
ANDI | 12 | a | dst | zero | rt = rs & zeroimm |
ORI | 13 | a | dst | zero | rt = rs | zeroimm |
XORI | 14 | a | dst | zero | rt = rs ^ zeroimm |
LUI | 15 | 0 | dst | sign | rt = signimm << 16 |
5.4 Load / Store (I 型)
| 指令 | op | base | rt | offset | 操作 |
LB | 32 | addr | dst | sign | rt = sext mem8[rs+imm] |
LH | 33 | addr | dst | sign | rt = sext mem16[rs+imm] |
LW | 35 | addr | dst | sign | rt = mem32[rs+imm] |
LBU | 36 | addr | dst | sign | rt = zext mem8[rs+imm] |
LHU | 37 | addr | dst | sign | rt = zext mem16[rs+imm] |
SB | 40 | addr | src | sign | mem8[rs+imm] = rt[7:0] |
SH | 41 | addr | src | sign | mem16[rs+imm] = rt[15:0] |
SW | 43 | addr | src | sign | mem32[rs+imm] = rt |
5.5 分支 / 跳转
| 指令 | op | rs | rt | imm / target | 条件 |
BGEZ | 1 | test | 1 | offset | rs ≥ 0 |
BLTZ | 1 | test | 0 | offset | rs < 0 |
J | 2 | — target(26bit) — | ×4 | unconditional |
JAL | 3 | — target(26bit) — | ×4 | ra = pc+8 |
BEQ | 4 | a | b | offset | rs == rt |
BNE | 5 | a | b | offset | rs ≠ rt |
BLEZ | 6 | test | 0 | offset | rs ≤ 0 |
BGTZ | 7 | test | 0 | offset | rs > 0 |
offset = imm (sign extended) × 4 → bta = pc + 4 + offset
target = jmm26 (26-bit) × 4 → jta = target
六、系统调用
通过 SYSCALL 指令触发,调用号存入 $v0:
| $v0 | 服务 | 输入 | 输出 | 说明 |
1 | print_int | $a0 = 整数 | — | 打印整数到游戏对话框 |
5 | read_int | — | $v0 = 整数 | 弹出输入框读取整数 |
10 | exit | — | — | 停止程序执行 |
注意: read_int 的负数输入有特殊限制。负号按键执行的是「对已有值取反」,而非前缀。正确输入 -5 的方式:先输入 5,再按负号键,然后确认。不能先按负号再输入数字。
七、延迟槽
MIPS 经典特性:跳转指令的下一条指令必定执行(即延迟槽)。
IF npc == 0 {
pc = pc + 4 // 正常顺序
} ELSE {
pc = pc + 4 // 执行延迟槽指令
db = 1 // 标记:下周期跳转
dbpc = npc
}
含义: 跳转发生时,当前 PC+4 处的指令 仍然会被执行一次,然后下一条指令才从跳转目标开始。这是物理 MIPS CPU 的行为复刻。编写跳转指令时,确保延迟槽指令有实际用途(通常放一条 NOP 或可提前执行的无关操作)。
八、内存布局
| 区域 | 地址范围 | 说明 |
| 程序区 | 0x3000 起 | PC 初始值,程序代码加载于此 |
| 堆/数据区 | 0x1800~0x2ffb | $gp 初始 0x1800 |
| 栈区 | 0x2ffc 起 | $sp 初始 0x2ffc |
内存通过 memory_operation/memory_read_N 和 memory_write_N 事件读写,底层为游戏脚本变量数组。
九、与 cmd_computer (x86 风格) 对比
| cmd_computer | primula_computer v3 |
| 指令集 | 自定义 8-bit opcode | MIPS-I 标准 |
| 寄存器 | 8 个 (x86 风格 eax~eflags) | 32 个 MIPS 通用 + HI/LO |
| 指令宽度 | 1 字节 | 4 字节 |
| 译码 | 范围 IF 分发 | 位域提取 + 二级 funct6 分发 |
| 延迟槽 | 无 | 有 |
| 系统调用 | 无 | 有 (print/read/exit) |
| 调试 | 文本菜单 | 实时寄存器面板 + BREAK |
← aliceincradle.org