301-算术运算和逻辑运算
# 301-算术运算和逻辑运算
计算机的核心功能,就是运算。而运算的基本类型 包括算术运算,和逻辑运算。想要了解计算机是如何实现运算的 我们就得从算术运算指令,和逻辑运算指令开始说起。 这是我们非常熟悉的模型机。我们在最一开始介绍模型积时,就用了一条加法指令作为例子。 加法指令就是一种算术运算指令,当时这条例子中的,这条加法指令 是将R0寄存器当中的数,和存储器地址为6的存储单元的数相加, 并将结果放回到R0寄存器当中。 这条指令的格式比较接近X86指令,它可以在运算指令当中进行存储器的操作。 而现在,我们要选用MIPS指令作为示例进行讲解。 我们也知道,MIPS的运算指令是只能对寄存器进行操作的,例如这样的格式 add表示是一条加法指令,$符号带上数字,是指对应的通用寄存器 。所以这条指令要做的 是将9号寄存器的内容和10号寄存器的内容相加,并存到8号寄存器当中。如果我们写这么一段 C语言程序,并且假设在程序中定义的这几个变量, 分别被分配到了八号、九号、十号的寄存器当中, 那在C语言当中所写的这条加法语句,就会经过MIPS的编译器,生成这样的 MIPS的指令。那这条指令的操作数,全都是寄存器,所以它是一条R型指令。 首先我们查指令编码表,知道i的指令的opcode应该是0 function是16进制的20,也就是10进制的32, 因为它不是一条移位指令,所以移位的域应该为0,然后根据这条指令当中的操作数,我们可以知道,它的目的操作数,是8号寄存器, 所以rd对应是8, 第一个源操作数是9号寄存器,第二个源操作数是10号寄存器 所以这两个对应的域分别应该是9和10,根据这样的分析,我们就可以知道, 这条指令的具体编码。这条二进制的编码,就是存放在内存中的一条计算机的指令了。
那CPU通过取址,就会把这条指令,从内存中取回来。 并放到ir寄存器中,也就是存放指令编码的寄存器。 指令译码电路,看到了这条指令的编码之后, 根据opcode域为全0,知道了这是一条r型的指令,再根据function域的值, 分析出这是一条add指令,因此,控制电路会向ALU,也就是运算器, 发出对应的控制信号,指明接下来要进行加法运算,与此同时,指令译码部件 分析出,第一个源操作数rs是9号寄存器,第二个源操作数rt是10号寄存器, 我们注意看右边的内部总线,控制电路会选通对应的 源操作数寄存器,将其内容传送到ALU的输入,
同时指令译码部件还会分析出目的操作数rd, 为八号寄存器,因此控制电路还会将ALU的输出,联通到八号寄存器的输入, 在这样的设定下,ALU会根据控制电路给出的信号, 将两个输入的源操作数,进行加法运算,并送出运算的结果, 会被保存在八号寄存器中,这就完成了一次加法运算。
因此,对于MIPS指令来说,其核心的 算数运算指令,采用R型格式的有这几种: 这条加法指令,是将rs和rt寄存器的内容相加,并存在rd寄存器中, 这个addu和add,从运算操作上本身看来是一样的,我们通过后面的注释也能看出来, 但区别在于add这条指令加了一个注释的标记, 他指的是上面这条add指令,在加法运算产生溢出时,会像 控制器报告异常,由控制器进行相关的处理,而addu这条指令,在 发生溢出时不会报出异常。关于溢出的问题, 在讲解加法器的实现时,还会进一步解释。
除了加法,还有对应的减法,也同样分为sub和 subu两种运算指令,其操作与对应的加法指令是类似的。 这一类R型指令,它的源操作数都是在寄存器当中的。
但如果 有一个源操作数是立即数的话,就需要采用另一种格式的指令,也就是I型指令。 这一条I型格式的加法指令的例子,我们是将22号寄存器的内容 加上一个立即数-50,并将结果存放在21号寄存器当中。 这是对应的I型指令的格式, 我们同样也来看一看这条指令的编码,透过查指令编码表我们可以得到,这批指令的opcode是8, 然后根据这条指令当中的参数,可以知道,源操作数的寄存器编号rs 等于22,目的操作数的寄存器编号rt等于21, 而立即数这个域,应该是-50, 将这些数都转换成二进制,填入对应的区域,我们就可以知道这条指令的编码,
让CPU将这条指令的编码,取回放入ir寄存器当中后, 指令译码部件会根据opcode域 发现这是一条立即数的加法指令,因此控制电路, 会将ALU发出控制信号,指示接下来要进行加法运算, 同时,指令译码部件分析也会发现,其中一个源操作数来自于22号寄存器, 我们注意看右边的内部走线区域,22号寄存器的内容 会在控制电路的控制下,通过内部走线走到ALU的一个入口, 而这个运算的另一个操作数是一个立即数, 这个立即数是存放在指令编码中的, 因此,控制电路会从指令编码中提取出这个立即数, 并将其传送到ALU的另一个入口, 而这条运算指令指示的目的操作数rt,是第21号寄存器, 因此ALU的输出会被传送到21号寄存器, 这就是这条立即数加法指令的运算过程
但是我们要注意的一点是,这个立即数,是16位的。 而另一个源操作数以及目的寄存器,都是32位的,那如何将一个 16位数和32位数进行相加呢,我们就需要进行一些转换。 转换的方法在MIPS的指令说明中很清楚地指出了, 上面这部份是刚刚介绍过的R型指令,这是我们刚才介绍的I型指令, 我们可以发现,在指令说明中,指出将rs寄存器的内容, 加上这个立即数的符号扩展,然后再将结果存放在r寄存器当中。 关于这条指令有两个说明,第一条跟刚才介绍过的一样,就是这条指令会 在加法发生溢出时,向控制器报出异常,交由控制器来处理。
第二个说明,指的是对立即数的扩展的方法, 立即数是16位的,那它如何被扩展到32位呢?是让它的高16位 是由这低16位当中的最高位,复制了16次,填充到高16位当中。 也就是说这32位当中的高16位,都和低16位当中的最高位,完全一样。 这样的扩展方法我们称为符号扩展,熟悉的补码规则的话,我们都应该知道进行符号扩展不会改变这个数做为一个有符号数的数值, 也就是说如果你本来是+5,经过符号扩展还是+5,如果你是-7,经过符号 扩展,那还是-7,如果不了解这一点的话,就应该了解i进制补码的相关知识。
那么回到这个I型指令的说明,还有对应的addiu这条指令,它的运算过程和addi是一样的。 区别只在于它在溢出时,不会产生异常。 这就是MIPS指令系统当中,核心的算术运算指令。
除了算术运算指令,还有一大类就是逻辑运算指令。 与算术运算指令类似,逻辑运算指令也分成R型和I型两种格式, 在R型当中,有and指令,就两个寄存器 当中的数,进行与操作,并将结果放在第三个寄存器中。 还有or指令, 是对两个寄存器当中的数,进行或操作。nor指令, 是进行或非操作,也就是先进行或,然后再取反。
I型指令当中,有andi指令, 也是进行与操作,但其中一个操作数是立即数, 这与算术运算指令类似,但其不同点在于,这16位立即数的扩展 方式,它不是采用符号扩展,而是采用0扩展。 同样还有ori指令,他其中一个操作数也是立即数。 那么这个立即数的0扩展操作是这样的, 扩展后的高16位全都是0,而不是低16位当中最高位的复制,这种扩展方式,就称为0扩展, 因为算术运算指令会考虑将操作数看做是一个有符号的数,在运算时要考虑正数和负数的这个性质, 而逻辑运算指令,是将操作数,视为一组二进制的01串, 这就不存在正数或着负数这样的概念,因此它采用了0扩展,而不是符号扩展。
同样我们也来看一个例子, 这里选的是R型的与指令,如果在C语言当中 写了这样的语句,那就会对应产生这样的MIPS指令, 我们也来看看它的指令编码是什么样的, 通过查指令编码表,我们可以得到opcode域是0,function域是16进制- 的24, 移位域同样还是0,然后根据这条指令的 三个操作数得到,rd域,也就是目的操作数是8。 第一个源操作数是9,第二个源操作数是10,我们在 将这些数都转换成二进制,就可以得到这条指令的编码。
如果CPU将这条编码从内存中取回,并放回IR寄存器中, 那控制电路这一次就会发出要进行与操作的控制信号。 在这样的控制下ALU就会将九号十号寄存器当中内容,进行与运算,并将结果送回到了8号寄存器当中。
最后,我们来看一看这些算术逻辑运算的总体需求, 对于算术运算, 我们可能需要将两个32位的数相加,其运算结果,是一个32位数。 或着进行两个32位数的减法,运算结果,也是一个32位数。 除了进行运算,我们还要检查运算的结果是否发生溢出。
而对于逻辑运算,我们需要进行两个32位数的与操作, 结果也是一个32位数。第二种情况是执行或操作, 第三种情况是执行或非操作, 这些就对应了MIPS的核心指令中,提供的加法 减法、与、或、或非这些指令的功能。
现在我们已经了解了这些运算指令的基本特点, 下一步,我们就得分析这些指令在硬件电路上, 是如何实现的。