pYYBAGLwsHOAc4hVAABmp1yVSCk758.png

RISC-V指令格式

我们这次来讲讲RISC-V指令集,看看他们的指令集是如何设计的。

RISC-V指令集项目在2010年始于伯克利大学,是一个新颖先进的指令集。我们曾在前几章中多次提到过这个指令集,不过也就是顺嘴一题,这次我们具体看看究竟什么是RISC-V指令集。

首先RISC-V指令集由几部分组成。最主要的部分是最基础32位的RV32I,这部分是最基础的指令集,是兼容RISC-V程序的必要部分。其次是16位的RVC,作为压缩指令,可以极大节省程序占用内存的空间。再者是32位的RV32M,用以支持乘除法指令。此外还有RV32F、RV32A等等。为什么要分成这么多部分呢?因为我们设计的CPU大多不会对这些指令全部兼容,而是选择有目的的部分兼容。比如,设计一个低功耗的单片机CPU,我们就用不到乘除法等指令,我们可以选择只兼容RV32I,既简单又高效。当然这些指令还有64位版本,用以支持更高位数的计算。

RISC-V指令集规定了CPU中有32个寄存器。有疑问吗?你可能觉得指令集不就是指令的集合嘛,为什么还规定我们的硬件设计?不要忘了,32个寄存器意味着寄存器序号一共是5位,而这是由指令集决定的。顺便一说X86指令集中仅规定有8个寄存器。其中,00000即第一个寄存器本质并不是寄存器而是硬件连线0,始终代表数字0。这个设计是有意义的,可以借此写出许多骚操作的指令。

我们先讲最基础的RV32I指令集。作为最基础的指令集,其包括几种指令类型。分别是数字运算指令(包括寄存器指令和立即数指令)、pc跳转指令、分支指令和内存读写指令。我们上次定义的指令集属于数字运算指令,二者类型相同但内容并不完全一致。现在我来说说RV32I中的数字运算指令,你们可以和上次定义的指令集比比有何不同?这些差别能带来好处还是坏处?

pYYBAGLwsHOAc4hVAABmp1yVSCk758.png

add指令释义

首先是运算指令。RV32I一共定义了10种运算,分别是加法、减法、有符号比较、无符号比较、与、或、非、异或、逻辑左移、逻辑右移和算数右移。而这些运算分为寄存器指令和立即数指令。立即数指令中是无需减法指令的,因为我们曾经说过,减法可以通过对其中一个加数取反加一再与另一个加数求和实现,所以立即数可以直接在立即数上做文章,不需要减法指令。那我们是否需要十种运算电路来分别对应这十种计算指令呢?不用,我们只需要八种。少的那两种分别是减法和移位。减法完全可以复用加法电路,左移完全可以复用右移电路。什么是复用?为什么要复用?复用就是重复利用原有的电路,减少设置新的电路。这样可以节省芯片面积,节约生产成本,降低发热功耗。那么如何复用呢?不同的电路有不同的复用方法,以减法复用加法为例,使用加法电路前,将其中一个数取反加一便可成为减法电路。

综上所述,我们仍需要3位数字表示这八种运算逻辑,它们分别是000到111,这三位数字被称为funct3(3位功能数字)。不过加法和移位运算中需要额外的一位数字用以区分加减和左右。这一位数字在哪呢?我们先讲立即数移位指令,立即数一般是12位数字,但在移位运算中用不到这么多位数,一般只有5位。那么在这五位数之前会有7个空位,第二个空位便是这位数字所在。立即数加法指令不需要这一位数字,原因刚才有指出。寄存器指令中,同样会存在7位空位,第二位空位是这位数字所在。

然后我们讲一下另一类指令,内存读写指令。不过我们在此之前需要指出一件事,我们现在有两种指令类型了,分别是运算指令和内存读写指令,我们怎么区分呢?RV32I设置了另一种功能数字,funct7(7位功能数字),为什么会有七位呢?因为指令类型很多,funct7不单起到区分RV32I指令类型的作用,同时还区分所有RISC-V的所有指令,甚至还包括16位和64位指令,所以funct7会有7位数字。回到内存读写指令上来,读和写可以被看成两种类型,需要一位数字区分,这位数字在funct7中。读和写都需要地址,否则不知道读哪或是写哪。

同时读指令还需要知道取到的内容放到哪,而写指令需要知道写什么内容。先说地址,地址是由某一寄存器中的数字加上七位立即数得到,这样正好组成之前说的12位数字。读指令放到哪呢?放到目标寄存器嘛。写指令的内容从哪来呢?来源寄存器嘛。这不就和之前的指令样式对应起来了吗?所以这两种指令样式区别其实不大。只不过执行内容有所区别。

我们现在刚刚简单讲完两种指令类型,是不是很多人就已经迷失自我了?难道所有这些指令规则只能通过繁琐的文字来讲述吗?这里就要提到指令集图卡了。

pYYBAGLwsLCANZaWAAC_XgiafkQ258.png

RV32I指令集图卡

上图就是实拍RV32I指令集图卡,每一行都代表着一条指令,你所要做的便是填入对应的寄存器序号和立即数即可。其中rs1和rs2分别是来源寄存器1和2,rd是目标寄存器,imm是立即数。有的指令可能只需要一个来源寄存器甚至一个都不要,有的指令可能不需要目标寄存器,有的指令可能需要12位立即数,有的则可能要20位立即数。这些在指令集图卡中都体现出来了。看到右边英文中,我手写的几道黑横线了吗?夹在里面的指令是我们在前文中所讲的指令类型,可以再根据这张图对前文进行理解,会容易得多哦。

举个例子,比如加法指令,看到最右侧有两个add,分别是I addi和R add,区别在于一个是立即数加法,一个是寄存器加法。对应到靠右边的方框中是不是可以看到7位数字,这就是funct7,中间有3位数字000,这就是funct3。寄存器指令中的最左侧7位数字是空着的为0000000,而立即数指令中最左侧则是12位的立即数。再看R add下面的R sub,与R add唯一的区别是不是左边第二位数字变成了1?这就是之前所说的复用所需的那一位数字。现在是不是能完全和之前所说的联系起来了?也没那么难对吧?

发表回复

您的电子邮箱地址不会被公开。