最简单的方式做最系统的教学【计算机组成入门到入土】计组核心:一篇文章搞定指令格式与所有寻址方式
🔥小陈又菜:个人主页
📖个人专栏:《MySQL:菜鸟教程》《小陈的C++之旅》《Java基础》
✨️想多了都是问题,做多了都是答案!
目录
1. 指令格式
1.1. 指令的一般格式
指令字长
定长指令系统:
变长指令系统:
1.2. 指令中的地址码字段
三地址指令:
二地址指令:
一地址指令:
情况一:
情况二:
零地址指令:
1.3. 指令中的操作码字段
定长操作码:
变长操作码:
2. 寻址方式
2.1. 指令寻址方式
顺序寻址:
跳跃寻址:
2.2. 操作数寻址方式
立即寻址:
直接寻址:
寄存器寻址:
间接寻址:
寄存器间接寻址:
相对寻址:
变址寻址:
基址寻址:
堆栈寻址:
1. 指令格式
1.1. 指令的一般格式
指令一般有操作码(OP)和地址码(A)两部分组成:
- 操作码:指明进行何种操作,加、减、移位、传送等
- 地址码:指明对应的操作数(可以包含多个操作数)
而如何获取操作数由寻址方式决定,简单说寻址方式决定了操作数的存放位置和访问方式。值得注意的是,寻址方式可以显示地包含在地址码字段中,也可以隐式地包含在操作码字段中。
指令字长
指令字长是指构成指令的二进制位数,根据指令系统中是否将指令字长固定,可以分为定长指令系统、变长指令系统。
定长指令系统:
- 指令长度固定
- 结构简单,有利于指令顺序寻址、取址和译码
- 硬件更容易实现
- 平均指令长度较长,存在较多冗余状态(浪费存储器空间)
变长指令系统:
- 指令长度可变
- 不受指令长度限制,扩展性较好
- 结构灵活,指令寻址、译码难度较大(取址过程可能存在多次访存操作)
- 硬件实现难度较大
- 平均指令长度较短冗余状态较少
这个地方大家需要注意,因为存储器的基本编址单位为字节,而指令时存储在存储器当中的。所以不管是定长指令,还是变长指令,它的字长都应该是字节的整数倍。
指令越长,占用的主存空间就越大,所耗费的访存时间也就越长。
根据指令字长与机器字长的关系,指令可以分为一下三类:
- 半字长指令
- 单字长指令
- 多字长指令
综上所述,为了提高速度,一般会将指令设计为较短的指令格式。
1.2. 指令中的地址码字段
根据指令类型和寻址方式,地址码可能包含:
- 操作数
- 操作数的地址(包括:主存地址、寄存器地址、外部设备端口地址、用于计算地址的偏移量)
根据地址码中包含的操作数地址数量,可以分为:
- 三地址指令
- 二地址指令
- 一地址指令
- 零地址指令
三地址指令:
下面我给出一个操作表达式:
其中,OP是操作码,地址码A1是源操作数1的地址,地址码A2是源操作数2的地址,地址码A3是目的操作数的地址。我们不难看出这里的OP是一个双目运算符。
举一个例子:
总结:
- 不难看出,如果操作数的寻址范围更大,则需要的地址码就会更长,指令就会更长
- 因此,三地址指令的地址码字段的是哪个地址全部用于存储单元地址的情况并不多见,较为常见的是用于寄存器地址(例如MIPS指令),这样可以减少指令长度。
二地址指令:
操作表达式如下:
其中,与三地址指令不同的是,地址码A1是源操作数1的地址、地址码A2是源操作数2的地址、同时A1还作为目的操作数操作数的地址。
- 与三地址指令相同,二地址指令也是为双目运算类设计的
- 不同的是,二地址指令为了压缩指令字长,将运算结果直接存放在源操作数1的地址A1中
根据二地址指令中两个地址码指向数据存储位置的不同,二地址指令可以分为:
操作数地址 | 源操作数地址 | 访问速度 | |
存储位置 | 寄存器 | 寄存器 | 第一 |
存储位置 | 存储器 | 寄存器 | 第二 |
存储位置 | 存储器 | 存储器 | 第三 |
一地址指令:
可以分为两种情况,一个是地址码既作为既作为源操作数地址,也作为目的操作数地址;还有一种是仅作为源操作数地址。
情况一:
仅包含一个地址码,既作为源操作数地址,也作为目的操作数地址,很明显是一个单目运算。
情况二:
这里举例的AL寄存器中实际上隐含了一个操作数,所以实际上这个一地址指令,实现的是一个双目运算。这种情况下,隐含操作数双目运算,进一步缩短了双目指令类的指令字长。
零地址指令:
零地址指令只包含操作码字段,而没有地址码字段,可以分为两类:
- 指令本身不需要操作数,例如一下x86指令:
- 空指令NOP
- 等待指令WAIT
- 停机指令HLT
- 程序返回指令RET
- 指令需要一个操作数,这个操作数隐含与CPU中的某一个寄存器
1.3. 指令中的操作码字段
定长操作码:
- 操作码长度固定,并且在指令中的位置也是固定的
- 指令译码简单,硬件容易实现
- 指令系统的规模(也就是指令系统包含的指令数量),决定了操作码的位数。若指令系统的指令数量为x,操作码位数为n,两者应该满足以下关系:
变长操作码:
- 操作码长度可变,在指令中的位置也不固定
- 能够有效减少操作码长度,用较短的指令字长表示更多的指令,给地址码留了更多的位数,以增大寻址空间
变长操作码的常见实现方式是扩展操作码技术:
核心思想是,操作码的长度随着地址码字段中地址码的数量的减少而增加
举例:
如果是定长操作码,那么将可以有16条三地址指令,而如果我们通过减少地址码数量来实现扩展操作码:
也就是我减少一个地址码,这样就成为了二地址指令,那么这里要注意的是,短操作码不能是长操作码的前缀,所以如果我们将高四位取位1111,那么就要从16个三地址指令中操作码位1111的删除,从而增加了16个二地址指令,那么此时就总共会有15+16=31条指令。
这里也会给我们一种启发,我们会倾向于给常用的指令分配更短的操作码。
这里给出几道经典的例题大家可以做一下:
题一:
解析:
题二:
解析:
题三:
解析:
要注意的是我们强调过,指令字长一定是字节的整数倍。
2. 寻址方式
寻址是指寻找指令或指令中操作数的有效地址的方式,这里的有效地址可以是主存地址,也可以是虚拟地址。
不难看出相较于指令寻址方式,操作数寻址方式更加灵活复杂,这是因为操作数是程序执行时的数据,所以为了满足不同程序的需要,操作数的存取方式会更加灵活。
这里要注意的是,因为指令寻址方式较为简单,所以在提到寻址方式的时候,如果没有特殊说明,一般是指操作数寻址方式。
2.1. 指令寻址方式
顺序寻址:
通常情况下,构成程序的所有机器指令(指令序列)在主存中按照顺序存放
大多数情况下,这照指令序列顺序执行:
所以如果我们知道指令序列中第一条指令的有效地址,然后在这个指令的有效地址的基础上增加一条指令所占主存单元的数量,就能够得到下一条指令的有效地址。我们将这种方法称作指令的顺序寻址。
跳跃寻址:
如果构成程序的指令中包含分支指令或者跳转指令,那么当程序执行到这条指令的时候,将会改变指令的执行顺序。
- 分支指令:通常用于基于条件来改变指令序列的执行顺序
- 跳转指令:通常用于无条件改变指令序列的执行顺序
此时就需要用到跳跃寻址:
总的来说,跳跃寻址时,下一条指令的地址并不是PC+1决定的,而是由本条指令本身或者需要的测试条件决定的。
2.2. 操作数寻址方式
我们知道不同指令可能采取不同的寻址方式来获取操作数,所以我们这里将指令格式中的地址码拆分成两部分:
- 寻址方式(寻址特征),字段M
- 形式地址,字段D
如下:
而这里我们可以形象地说,操作数寻址就是,将寻址方式与M与形式地址D的不同组合转换成有效地址的过程。
立即寻址:
在立即寻址下,操作数和指令一起存放在主存中(放在一起):
- 取指令时,操作数和指令一起被读取并存放到CPU中的指令寄存器IR中
- 执行指令时,直接从指令寄存器IR中读取操作数,不需要访问其他存储空间
立即寻址特点如下:
- 取操作数快
- 但是形式地址位宽有限,限制了操作数的表示范围
直接寻址:
在直接寻址方式下,操作数和指令是分开放在主存中的:
- 指令的形式地址中给出了操作数的主存地址
- 读指令时,操作数并不会被一起被读取并送到CPU的指令寄存器IR中
- 执行指令时,还访问一次主存来获取操作数
直接寻址的特点如下:
- 操作数的地址很直观,不需要任何计算
- 形式地址的位宽优先,限制了操作数的寻址范围
- 操作数的地址位于指令当中,不方便修改
寄存器寻址:
在寄存器寻址的方式下,操作数和指令是分开存放的,操作数存放在CPU中的某个通用寄存器中,指令存放在主存:
- 指令中的形式地址D字段中给出了操作数所在通用寄存器的编号
- 取指令时,操作数不会随指令一起被读取并送到CPU中的指令寄存器IR中
- 执行指令时,只需要在访问一次寄存器(不用访问主存)
寄存器寻址的特点如下:
- 获取操作数只需要访问寄存器,不需要访问主存,指令执行速度快
- 因为通用寄存器较少,所以形式地址D的位数较短,有利于缩短指令字长
- 同样因为CPU中的通用寄存器数量有限,所以无法为大量操作数提供存储服务
间接寻址:
间接寻址方式下,操作数与指令分开存放在主存中:
- 形式地址D中,给出操作数的间接地址,间接地址所指向的主存单元中才是操作数的有效地址
- 取指令时,操作数不会随指令一起被读取
- 指令执行时,还需要访问两次主存才能获取操作数
这里要注意上图展示的是单级间接寻址示例,还有多级间接寻址。
看到这里想必大家会有疑问,就是为什么要设计这么复杂的间接寻址方式呢?
间接寻址的特点如下:
假设形式地址16位,指向一个主存单元(32位),那么这个时候操作数的寻址范围就变大了,如果这里使用的是直接寻址,则只有16位。
- 能够使用较短的形式地址D,通过间接寻址来访问更大的主存空间
- 相较于直接寻址(形式地址中直接存放操作数有效地址),间接寻址在修改操作数有效地址时不需要修改指令中的形式地址D,只需要修改D指向的主存单元即可
- 指令执行时需要两次访问主存,指令速度降低
寄存器间接寻址:
寄存器间接寻址方式下,操场数和寄存器分开放在主存中:
- 形式地址D给出的不是操作数的有效地址,而是CPU中通用寄存器的编号,而该编号的通用寄存器中存放的就是操作数的主存地址
- 操作数不会随指令一起被读取
- 指令执行时,还需要访问一次主存来获取操作数
寄存器间接寻址的特点:
- 具有间接寻址的优点(扩大了操作数的寻址范围)
- 与寄存器寻址的缺点类似,因为CPU的通用寄存器有限,所以不能为大量操作数提供存储服务
相对寻址:
在相对寻址方式下,操作数和指令分开存放在主存中:
- 指令中形式地址D,是程序技术器要给PC中内容加上的偏移量,可正可负(用补码表示),这样就能得到操作数的主存地址或者转移地址
- 操作数不会随指令一起被读取并送到CPU的指令寄存器IR中
- 指令译码或执行阶段,会计算操作数的主存地址或转移地址
例题:
解析:
相对寻址的特点如下:
- 程序设计只需要给定指令与操作数之间的相对距离,而不必关系操作数在主存中的绝对地址,可以用于实现共享代码的浮动
- 实现程序跳转
变址寻址:
在变址寻址方式下,操作数与指令分开存放在主存中:
- 指令中的寄存器编号指定是CPU中的哪一个寄存器(可以是通用寄存器,也可以是专用寄存器)来存放变化的地址,该寄存器被称作变址寄存器,其内部存放的地址与形式地址D相加,就是操作数的主存地址
- 取指令时,操作数不会随指令被读取并送到指令寄存器IR中
- 指令执行时,需要访问一次主存来获取操作数
变址寻址的特点及用途如下:
- 变址寄存器n中的内容可变,而形式地址中的值设定之后在程序执行过程中保持不变
- 因为变址寄存器n的位数大于形式地址位数,所以扩大了寻址范围(可以表示整个存储空间)
- 主要应用于数组访问,将数组首地址赋值给形式地址D,然后通过变址寄存器n的值按顺序变化,就能够访问数组中的各元素
基址寻址:
基址寻址除了,上面两点不同,其他与变址寻址方式相同。
堆栈寻址:
对于存储在堆栈上的操作数进行寻址的方法叫做堆栈寻址。
堆栈分为下面两种:
(本篇完)