Simple-MIPS教学指令集
定位与记号
Simple-MIPS是为教学目的设计的简化指令集。它参考MIPS等真实架构,但并不完全等同于真实的MIPS指令集,目的是更直观地讲解计算机组成原理的核心概念。本页的指令都按Simple-MIPS的写法给出;PC、IR、ALU和寄存器组如何配合执行这些指令,见CPU的核心部件。
每条Simple-MIPS指令固定32位,每个通用寄存器也固定32位。常见指令的基本形式是
操作码 目标, 源1, 源2操作数按角色分为以下几类:
| 记号 | 含义 |
|---|---|
$rd | 目标寄存器,结果写在这里 |
$rs | 第一个源寄存器;访存时兼作基址寄存器 |
$rt | 第二个源寄存器;在load/store中承担另一角色 |
offset | 立即数偏移,与 $rs中的地址相加 |
shamt | 移位位数,取0–31 |
label | 标签,代表一条指令的地址 |
寄存器编号的具体分工($zero、$t0–$t9、$s0–$s7等)由寄存器组的约定决定,不在指令本身的语义里体现。
指令与共同约定
指令总表
| 指令 | 类别 | 语法 | 语义 |
|---|---|---|---|
| load | 数据加载 | lw $rt, offset($rs) | 从地址 $rs + offset读出一个字,存入 $rt |
| store | 数据存储 | sw $rt, offset($rs) | 把 $rt的一个字写到地址 $rs + offset |
| mov | 数据移动 | move $rd, $rs | $rd = $rs |
| sub | 算术运算 | sub $rd, $rs, $rt | $rd = $rs - $rt |
| shiftl | 逻辑左移 | sll $rd, $rt, shamt | $rd = $rt << shamt,右侧补0 |
| shiftr | 逻辑右移 | srl $rd, $rt, shamt | $rd = $rt >> shamt,左侧补0 |
| slt | 比较 | slt $rd, $rs, $rt | $rs < $rt时 $rd = 1,否则 $rd = 0 |
| sle | 比较(伪指令) | sle $rd, $rs, $rt | $rs <= $rt时 $rd = 1,否则 $rd = 0 |
| beqz | 条件分支 | beqz $rs, label | $rs == 0时转到label |
| goto | 无条件跳转 | goto label | 直接转到label |
表中的mov、shiftl、shiftr是教学指令集里的名称,汇编写法分别是move、sll、srl;goto对应真实MIPS的j。读真实MIPS汇编时按真实指令集的助记符理解,不要混用。
访存指令的地址计算
load与store都采用“基址寄存器加偏移”的寻址方式:$rs中存放基址,offset是立即数,实际访问的地址为两者之和。偏移可以为0,此时直接访问 $rs指向的字。这两条指令一次都只移动一个32位的字。
可以借助一个类比记住方向:寄存器像手边的工作台,内存像书架;load把数据从书架搬到工作台,store把工作台的内容放回书架。所以load的 $rt是写入目标,store的 $rt是数据来源。
复制、减法与移位
复制指令把源寄存器的值写到目标寄存器,两个寄存器此后的值相同。Simple-MIPS把复制视为一条基本指令;真实MIPS通常用add或or与 $zero配合实现,例如add $t0, $t1, $zero。
sub把 $rs当作被减数、$rt当作减数,结果写入 $rd。减法由ALU完成,操作数来自寄存器组,写法与slt一样是三个寄存器。例如 $t1为10、$t2为3时,sub $t0, $t1, $t2使 $t0等于7。
shamt给出移动的位数。当左移结果未丢失高位时,左移shamt位相当于乘以 ;对无符号数,右移shamt位相当于除以 并向下取整。这个“相当于除以”的结论只对非负整数成立:srl是逻辑右移,左侧补0,不能用它处理带符号数的除法。
控制流指令的组合方式
slt和sle不改变执行顺序,它们只把比较结果写成0或1;beqz检查一个寄存器是否为0,为0就转到标签;goto无条件转到标签,实质是把PC置为标签对应的地址,例如goto Loop回到循环开头。三者配合,才能让程序离开“顺序执行”:
-
用
slt把条件变成0或1。 -
用
beqz检查这个0或1,决定是否跳出。 -
用标签和
goto回到循环开头。
sle是伪指令,汇编器通常把它展开成与slt相关的指令序列。它的语义完整,可以直接在作业中使用,只是不要把它当成硬件直接实现的一条指令。
例子
load与store的地址计算
设 $s1中存放0x1000。
lw $t0, 4($s1) # 读地址0x1004sw $t0, 8($s1) # 写地址0x1008lw把地址0x1004处的字读入 $t0;随后sw把 $t0的内容写到地址0x1008。两次访问的地址都由 $s1加上偏移得到,$s1本身不变。
复制寄存器:move与add ... $zero
move $t0, $t1add $t0, $t1, $zero两条指令都把 $t1的值复制到 $t0。第二条利用 $zero恒为0:$t1 + 0仍等于 $t1,所以结果与move相同。
移位代替乘除
sll $t0, $t1, 2 # $t1 = 5时,$t0 = 20srl $t0, $t2, 2 # $t2 = 13时,$t0 = 3左移2位相当于乘以 ,所以 ;右移2位相当于除以4并向下取整,所以 。
比较结果如何交给分支
设 $s2的值为5,依次考察 $s1取不同值时的结果:
$s1的值 | slt $t0, $s1, $s2 | sle $t0, $s1, $s2 | beqz接在slt后 | beqz接在sle后 |
|---|---|---|---|---|
| 3 | 1 | 1 | 不跳转 | 不跳转 |
| 5 | 0 | 1 | 跳转 | 不跳转 |
| 7 | 0 | 0 | 跳转 | 跳转 |
slt在相等时给出0,sle在相等时给出1,这是两条比较指令唯一的差别。beqz只看寄存器是不是0,所以它接在slt后面时,跳转的含义是“不小于”;若接在sle后面,跳转的含义变成“大于”。写循环时要先确定自己用的是哪一条。
探索
比较结果如何控制分支?
改变寄存器的值和比较指令,查看beqz的去向。
正在准备演示…
练习
若
$s1的值为4096,执行lw $t0, 8($s1)时读取的字节地址是多少?地址是 。
参考解答
实际地址等于基址加偏移:。
下列哪条指令会把寄存器中的值写入内存?
参考解答
sw是store,把$t0写到内存;lw的方向相反,是从内存读入寄存器。设
$t1的值为5,执行sll $t0, $t1, 2后$t0的值是多少?$t0的值是 。参考解答
左移2位相当于乘以 ,。
设
$t1的值为13,执行srl $t0, $t1, 2后$t0的值是多少?$t0的值是 。参考解答
右移2位相当于除以4并向下取整,,取3。
设
$s1和$s2的值都为7。执行
slt $t0, $s1, $s2后$t0的值是 。参考解答
slt判断第一个操作数是否严格小于第二个;两者相等,结果为0。执行
sle $t0, $s1, $s2后$t0的值是 。参考解答
sle判断是否小于或等于;两者相等,结果为1。
设
$t0的值为0,执行beqz $t0, Loop时会发生什么?参考解答
beqz在寄存器等于0时跳转;$t0为0,所以转到标签Loop。要把
$t1的值复制到$t0,下列哪条指令正确?参考解答
move $t0, $t1把源寄存器$t1的值写到目标$t0;写成move $t1, $t0方向相反。sub $t0, $t1, $t1的两数相同,结果恒为0,不是复制。