蒙珣的博客

活好当下,做好今天该做的事情。

0%

CH2-指令-计算机的语言上-汇编指令

MIPS 操作数

名字 示例 注释
32个寄存器 $s0-$s7, $t0-$t9, $zero,
$a0-$a3, $v0-$v1, $gp, $fp,
$sp, $ra, $at
寄存器用于数据的快速存取。在MIPS中,只能对存放在寄存器中的数据执行算数操作,寄存器$zero的值恒为0,寄存器$at被汇编器保留,用于处理大的常数
20^30个存储器字 Memory[0], Memory[4], …,
Memory[4294967292]
存储器只能通过数据传输指令访问。MIPS使用字节编址,所以连续的字地址相差4。存储器用于保存数据结构、数组和溢出的寄存器。

MIPS汇编语言

MIPS汇编语言

第一部分 MIPS-32概述

指令的组成 MIPS的设计思想

计算机执行任何程序,本质上都是在执行机器语言指令(instruction),每条指令都是一条0-1串

指令首先要指明执行什么操作,通常用0-1串中的前几位来表示,称为操作码 指令还要指出需要操作的数据来自哪里、操作后的结果数据放回哪里 通常用0-1串中的剩余位来表示,称为操作数地址码大部分操作数都是一个地址编号,告诉CPU从哪里取得数据、向哪里放回数据 所以操作数通常也叫做地址码

操作码 地址码 地址码

MIPS作为RISC指令集,设计力求保证硬件设备的简单性,在我们讲解的32为MIPS汇编语言(MIPS-32)中,所有指令都是32位长


MIPS-32中的通用寄存器

MIPS中运算操作的操作数必须来自寄存器(register)或者指令本身一种位于CPU、比cache更小更快的存储器,用来暂时存放运算的源数据和结果

一些寄存器是专用的,如存放执行中指令的地址的程序计数器(PC)于此相对应,用于暂时存放运算数据的寄存器称为通用寄存器

MIPS中一共有32个32位寄存器,共128B(大部分架构都采用16或32个寄存器)

我们约定:

  • 程序中的变量存放在保存寄存器(store reg)中:$s0 ~ $s7 共8个
  • 运算的临时变量、中间变量存放在临时寄存器(temp reg)中:$t0 ~ $t7 共8个
  • 还有一个零寄存器,永远存放32位的0,写作$zero

第二部分 三类汇编指令

三类汇编指令

算数运算:加add、减sub

C赋值语句:c = a + b;

加法指令 add c, a, b: 将a和b中的数据相加,并将结果存放在c中

再次强调:MIPS中运算的操作数必须来自寄存器或者指令本身!

假设变量a,b,c分别存放在寄存器$s0,$s1,$s2中,这条指令就应当写为

add $s2, $s0, $s1

加法中两个数可以对换,但减法不行,故c = a - b;必须写作

sub $s2, $s0, $s1

运算的 “原材料” a和b对应的寄存器$s0, $s1

分别称为源操作数1*(src1)和源操作数2(src2)

运算的结果c对应的寄存器$s2称为目的操作数(des)

加减指令的通式:add/sub des, src1, src2

算术运算:加立即数addi

在 i++ 即 i = i + 1;这条赋值语句中,有个确定的常数1

与其采取额外的步骤将1装入某个寄存器,不如让指令本身包含这个1

假设变量i位于寄存器$s0,我们把加法指令的第二个源操作数改为常数1

addi $s0, $s0, 1

就成了加立即数(add immediate)指令

因为addi指令中的立即数可以取负数(对立即数取负后相加)

因此,MIPS中没有subi指令

逻辑按位运算:and、or、nor指令

当两个源寄存器中,对应的位上同时为1时,与and操作结果为1 当两个源寄存器中,对应的位上至少有一个为1时,或or操作结果为1 因此,假设

$t0 = 0000 0000 0000 0000 0000 0000 0000 1001

$t1 = 0000 0000 0000 0000 0000 0000 0000 1100

执行下列两条指令后,$t2中的数据分别变为多少?

and $t2, $t0, $t1

or $t2, $t0, $t1

任何数据与0进行或非nor操作,都会0/1反转 执行下列指令后,$t2中的数据会变为多少?

nor $t2, $t0, $zero

$t2 = 1111 1111 1111 1111 1111 1111 1111 0110

逻辑位移运算:sll 和 srl 指令

比较12和120两个十进制数,通过在对低位的右边添加一个0,变成了10倍

比较11和110两个二进制数,通过在最低位的右边添加一个0,变成了多少倍?1100呢?


逻辑左移(shift left logic)指令让寄存器中的数据整体往左移动指定的位数,并在右边空出来的位上补0。

假设$s2 = 0000 0000 0000 0000 0000 0000 0000 0101

逻辑左移两位后,放到寄存器$s0中:

sll $s0, $s2, 2

这里的2不是addi指令中的立即数,而是告诉计算机移动几位的位移量(shift amount)


通过这样一条指令,我们实际上完成了x4的运算!

x2、x8、x128时,移位量分别是多少?

x2移位量为1,x8移位量为3,x128移位量为7

srl 指令可以实现/2运算,使用场景不多,不额外讨论

综合练习1:变量运算与赋值

翻译以下C语句:

result = a - 10 + (b + c * 5);其中 result 为 $s3,a 为 $s0,b 为 $s1,c 为 $s2

1
2
3
4
5
6
7
8
c * 5 -> sll $t0, $s2, 2
add $t0, $t0, $s2

b + c * 5 -> add $t1, $t0, $s1

a - 10 -> addi $t2, $s0, -10

result = add $s3, $t2, $t1

寄存器—存储器数据传送:lw指令

运算指令的操作数必须来自于寄存器/指令本身,但是,通用寄存器一共只有128B

数组元素却可以占据成千上万个字节,只能存放在内存中


这时,我们把数组第一个元素(a[0])的32位地址,称为数组的基址,放在寄存器中

基址加上要找的元素的下标,就组成了这个元素的地址


如果源操作数在内存中,是数组a的5号元素(第六个元素),数组a的基址存放在$s1中。那么,a[5]的地址就表示为5($s1)

计算机会自动计算$s1中的基址和偏移量5的和,找到a[5]的地址


将a[5]从内存传送到寄存器$s0,使用取字指令(load word)

lw $s0, 5($s1)

寄存器—存储器数据传送:字与sw指令

MIPS的通用寄存器都是32位长,这个长度就是MIPS体系结构的字长,通常代表了参与运算的数据的长度,因此我们约定:整门课程中,1=32b=4B(字节)


a[5]相对于a[0],在内存中的距离是5个字,而不是5个字节。又因为内存按字节编址,即:内存每个字节都有一个特定的编号。

所以偏移量应该是 5x4=20个字节,a[5]的地址应该表示成20($s1),于是取数指令变为lw $s0, 20($s1)


如果我们要把$t0中运算结果送回内存中的a[2],需要用到存字指令(store word): sw $t0, 8($s1)

寄存器间数据传送 装载立即数到寄存器

如果我们需要把数从$t0保存到存放某变量的$s1中,怎么实现?

MIPS没有专门的寄存器间移动数据的指令。但是,通过把源寄存器中的数据加上0再保存到目标寄存器中,可以实现相同的功能

addi $s1, $t0, 0add $s1, $t0, $zero

这个功能可以用move伪指令来代替 move $s1, $t0


假如我们要把一个常数10装入寄存器$s2,同样可以采用addi指令

addi $s2, $zero, 10 或使用取立即数(load immediate)伪指令 li $s2, 10

程序设计题中能否使用伪指令,请咨询老师!

装载32位立即数到寄存器

我们说可以用addi指令向寄存器装载立即数:addi $s2, $zero, 10

但是,addi指令中的立即数10只能占用32位指令中的一部分(16位,稍后介绍指令格式)

16位只能表示2E16,即六万多个数,寄存器却能容纳2E32即40多亿个数

addi 指令只能作用于 -2^15 ~ 2^15-1 个立即数中(即-32768 ~ 32767)


二进制与十六进制的转化在此不作介绍

假设我们要向寄存器$s2装载一个32位的立即数:10A2 7FFF(16)


我们必须先用取高位立即数(load upper immediate)指令,把10A2放入$s2的高16位

lui $s2,4258 #十六进制的10A2等于十进制的4258

再让$s2与低16位的立即数7FFF进行或运算

ori $s2, $s2, 32767 #7FFF(16)=32767(10)

这样,就分两步把32位立即数装载到了32的寄存器中

不能使用addi代替ori指令,如果低16位的最高位是1,addi会把它理解为负数

综合练习2:数组元素运算与赋值

a[i] = a[0] + 100000;

假设数组 a 的基址位于 $s0,变量 i 位于 $s1

100000(10) = 186A0(16),1(16) = 1(10),86A0(16) = 34464

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1.将a[0]传到临时寄存器中
lw $t0,0($s0)

2.将100000装载32位寄存器中
lui $t1,1
ori $t1,$t1,34464

3.将a[0] 与 100000 相加
add $t2,$t0,$t1

4.获取a[i]的元素 # a[0] 的地址存放在 $s0
sll $t3,$s1,2 # a[1] 的地址存放在 $s0 + 4 中(1个字是4字节)
add $t4,$t0,$t3 # 所以 a[i] 就是基地址 $s0 + 4i

5.将t2的值传给t4所指向的a[i]地址
sw $t2,0($t4)

决策:条件分支beq和bne

计算机和一般计算器的区别在于何处?

在于决策能力

即,根据一定的条件选择执行何种运算的能力


最基础的判断条件是相等关系

假设 $s0 = 0,$s1 = 0,$s2 = 1

相等则分支(branch if equal)指令在两个源操作数寄存器中的值相同时分支,分支以分支标签表示

beq $s0,$s1,Label

与此相对应,不等则分支(branch if not equal)指令在值不同时分支到标签

bne $s0,s2,Label

如果 不发生分支,则继续执行内存中相邻的下一条指令


综合练习3:if-else语句(无条件跳转 j 和条件分支)

If (i == j) f = g + h;

else f = g - h;

假设f、g、h、i、j 分别存放在$s0 - $s4中

1
2
3
4
bne $s3,$s4,Else
add $s0,$s1,$s2
j Exit: # 跳出
Else: sub $s0,$s1,$s2

结论:判定相等 == 使用bne,判断不等 != 使用beq

决策:小于则置位slt

除了相等、不等关系,我们还经常比较两个数的大小

MIPS有一条小于则置位(set on less than)指令slt

置位:将一位设置为1;复位:将一位设置为0


还是假设$s0 = 0,$s1 = 0,$s2 = 1

slt $t0,$s0,$s2

源操作数1 < 源操作数2 吗? Yes

此时把目的炒作输寄存器$t0置位为1


slt $t0,$s0,$s1

源操作数1 < 源操作数2 吗?No!

此时把目的操作数寄存器$t0复位为0


在比较中经常使用常数操作数,所以有立即数版本的小于则置位指令。

slti $t0, $s2, 10 # $t0 = 1 if $s2 < 10


6种条件判断及其伪指令

通过slt、beq、bne(严格来说还有小于立即数则置位slti指令,不作讨论)指令的各种组合,我们就能够实现全部六种比较指令,即六种值为真或假的布尔表达式

1
2
if (i < j) f = g + h;
else f = g - h;
1
2
3
4
5
6
slt $t0, i, j         # 当 i<j 时,把$t0置为1,否则为0
beq $t0, $zero, Else # 当 $t0为0时,执行else后的语句
add f, g, h # 否则顺着执行if后的语句
j Exit # 加法完成后退出if-else语句
Else: sub f, g, h # else
Exit:

结论:判断大于 > 或小于 < 使用 slt 和 beq,判定大于等于 >= 或小于等于 <= 使用 slt 和 bne 

对于比大小的四种比较条件,可以使用伪指令:

  • 小于则分支 blt
  • 大于则分支 bgt
  • 小于等于则分支 ble
  • 大于等于则分支 bge

综合练习4:while循环

while(a[i] == k) i++;
假设i,k分别存放在$s3和$s5中,a的基址放在$s6中

1
2
3
4
5
6
Loop: sll $t0, $s3, 2     # 4i
add $t1, $s6, $t0 # a[0] + 4i
lw $t2, 0($t1) # 将a[i]的内存地址取出,放入临时寄存器
bne $t2, $s5 Exit:
addi $s3, $s3, 1
j Loop # 跳转回判断 a[i] == k

MIPS汇编指令小结

指令格式:R型

指令中含三个寄存器的运算指令都属于R型(register type)指令

add/subdes,src1,src2

and/or/nordes,src1,src2

sltdes,src1,src2

32位的MIPS指令一共分为6个字段:

op rs rt rd shamt funct
6位 5位 5位 5位 5位 6位
  • op:operation code,源操作码
  • rs:register source,源操作数寄存器 -> rt:s后面是t,表示第二个源操作数寄存器
  • rd:register destination,目的寄存器
  • shamt:shift amount,位移量
  • funct:function code,功能码

指令格式:R型

R型指令的操作码op都是6个0,由6位功能码funct进一步指定执行什么操作

以add指令为例

  • $t0 ~ $t7分别为8~15号寄存器

  • $s0 ~ $s7分别为16~23号寄存器

将下列机器码,翻译成MIPS-32指令

000000 10001 10010 01000 00000 100000
6位 5位 5位 5位 5位 6位
1
2
3
4
5
6
7
操作码 op 为0,功能码为32,即为add指令
目的寄存器为 01000,十进制为8,即$t0
第一个源操作数寄存器为 10001,十进制为17,即s1
第二个源操作数寄存器为 10010, 十进制为18,即s2
偏移量为0

综上所述,该机器码的MIPS-32指令为 add $t0, $s1, $s2

sub指令仅仅是功能码funct字段从32变为了34,sub $s1, $s1, $s0的32位机器码是多少?

需要记忆add、sub指令的操作码(都是0)和功能码(分别为32、34)

1
2
3
4
5
6
7
8
sub 的操作码 op 为 000000
功能码 funct 为 34,即010010
偏移量为0,即 00000
第一个 $s1 为目的寄存器,且 $s117号寄存器,即10001
第二个 $s1 为第一个源操作寄存器,且 $s117号寄存器,即10001
$s0 为第二个源操作寄存器,且 $s016号寄存器,即10000

综上所述,sub $s1, $s1, $s0 的机器码为
op rs rt rd shamt funct
000000 10001 10001 10000 00000 100100

此外,使用移位量的两条逻辑移位指令

sll/srldes, src1, shamt

也属于R型指令,因为没有第二个源操作数寄存器,rt被置为0


指令格式:I型(立即数)

有两条“目的reg + 源reg + 立即数” 格式的指令

addides,src1,i

ori des,src1,i

通过把R型指令中的后桑格字段拼接成一个16为的里结束字段,让指令本身包含常数。这样的指令属于I型(immediate type)指令

op rs rt rd shamt funct
6位 5位 5位 5位 5位 6位
R 型指令格式
op rs rt constant or address
6位 5位 5位 16位
I 型指令格式

以 addi 指令为例

其操作码为8,由于 rd 字段被合并了,现在 rt 就成了目的寄存器

通过 I 型指令格式也可以看出,立即数最大只有 16位,再加上一位的正负号,只有15位可用(即-2^15 ~ 2^15-1    ->    -32768 ~ 32767)

练习1

将此指令翻译成机器指令:addi $t1, $t0, 15

1
2
3
4
5
6
addi 指令的操作码 op 为8,即001000
$t1 为目的寄存器,$t19号寄存器,即01001
$t0 为源操作数寄存器,$t08号寄存器,即01000
立即数为15,即 constant or address 0000000000001111

综上所述,addi $t1, $t0, 15 的机器码为
op rs rt constant or address
001000 01000 01001 0000000000001111

指令格式:I型(偏移量)

lw/sw reg, num(reg)

两条数据传送指令也包含两个寄存器和一个常数

同样属于I型指令

此时,16位立即数字段的含义发生了改变,表示数组元素相对于数组基址的地址偏移量

op rs rt constant or address
6位 5位 5位 16位

无论是lw还是sw指令

都是由 rs 字段表示的寄存器值与 address 字段相加,得到存储器单元地址

rt 字段表示与存储器单元交换数据的寄存器

lw、sw 指令操作码分别为 35 和 43


练习1

将此指令翻译成机器指令:lw $t0, 8($s1)

1
2
3
4
5
6
lw 指令的操作码 op 为35,所以换成二进制为10011
$t0 是第二个源操作数寄存器(第一个用于存放address),$t0为8号寄存器,且rt为5位,即01000
8($s1) 是第一个源操作数寄存器即rs,$s117号寄存器,且rs为5位,即10001
又因为偏移量是8,即1000,constan or address 16位,所以为0000000000001000

综上所述,lw $t0, 8($s1) 的机器码为
op rs rt constant or address
10011 10001 01000 0000000000001000

练习2

将此指令翻译成机器指令:sw $t2, 0($s4)

1
2
3
4
5
6
sw 指令的操作码 op 为43,所以换成二进制为11101
$t2 是第二个源操作数寄存器即rt(第一个用于存放address),$t2为10号寄存器,且rt为5位,即10010
0($s4) 是第一个源操作数寄存器即rs,$s420号寄存器,且rs为5位,即10100
又因为偏移量是0,constant or address 16位,所以为0000000000000000

综上所述,sw $t2, 0($s4) 的机器码为
op rs rt constant or address
11101 10010 10100 0000000000000000

指令格式:I 型(标签)

beq/bne src1, src2, Label

在这两条条件分支指令中,同样是使用了两个寄存器

还有一个分支标签的地址,用16位立即数字段表示(也就变成了 Address 字段)

也属于 I 型指令


例如,当 i($s0)j($s1) 相等时分支到地址为 10000 的标签 Else

beq $s0, $s1, Else

翻译为机器语言为

op rs rt constant or address
6位 5位 5位 16位

这里的 10000 实际上并不是 Else 标签指向指令的地址,讲寻址方式时再具体说明

机器语言指令格式小结

名字 格式 举例 注释
add R 0 18 19 17 0 32 add $s1, $s2, $s3
sub R 0 18 19 17 0 34 sub $s1, $s2, $s3
addi I 8 18 17 100 addi $s1, $s2, 100
lw I 35 18 17 100 lw $s1, 100($s2)
sw I 43 18 17 100 sw $s1, 100($s2)
字段宽度 6位 5位 5位 5位 5位 6位 所有 MIPS 指令均为32位
R型 R op rs rt rd shamt funct 算数指令格式
I型 I op rs rt address 数据传送指令格式

lui 指令的指令格式不作讨论

五条伪指令本身不是真正的指令,程序运行时会被替换成为真正的指令,不讨论指令格式

j 指令的指令格式稍后讲解

复习题

1、指令通常由那两个部分组成?MIPS-32 指令长度均为多少?

  • 操作码和操作数(地址码)
  • MIPS-32 指令长度为32位

2、8个临时寄存器、8个保存寄存器分别是什么编号?零寄存器存储什么?

  • 8个临时寄存器($t0 ~ $t7)的编号为 8-15
  • 8个保存寄存器($s0 ~ $s7)的编号为 16-23

3、回顾综合联系1 ~ 4,掌握运算、数据传输、决策三类汇编指令。(注意字和字节的区别

4、练习上面的五条指令(add、sub、addi、lw、sw)汇编语言和机器语言的转化


转自:B站翼云