RISC-V指令集是UC Berkley 大学设计的第五代开源 RISC ISA, V 也可以认为是允许变种(Variations)和向量(Vector)向量实现,数据的并行加速功能也是明确支持目标,是专用硬件发展的一个重要方向。RISC ISA相对于成熟的指令集来说有开源、简捷、可扩展、和后发优势(没有历史包袱,可以绕过很多弯路,也不需要考虑兼容历史指令集)等。
指令集分为基本部分和扩展部分,基本部分的指令集所有硬件实现都必须有这一部分实现,而扩展部分则是可选的。扩展部分又分为标准扩展和非标准扩展。例如,乘除法、单双精度的浮点、原子操作就在标准扩展子集中。
“I” 基本整数集,其中包含整数的基本计算、Load/Store和控制流,所有的硬件实现都必须包含这一部分。
“M” 标准整数乘除法扩展集,增加了整数寄存器中的乘除法指令。
“A” 标准操作原子扩展集,增加对储存器的原子读、写、修改和处理器间的同步。
“F” 标准单精度浮点扩展集,增加了浮点寄存器、计算指令、L/S指令。
“D” 标准双精度扩展集,扩展双精度浮点寄存器,双精度计算指令、L/S指令。
I+M+F+A+D 被缩写为 “G” ,共同组成通用的标量指令。
在后续ISA的版本迭代过程中,RV32G和RV64G总是保持不变。
基本RISC-V ISA具有32位固定长度,并且需要32位地址对齐。但是也支持变长扩展,要求指令长度为16位整数倍,16位地址对齐。
32位指令最低2位为“11”,而16位变长指令可以是“00、01、10”,48位指令低5位位全1,64位指令低6位全1。任何长度的指令,如果所有位全0或全1,都认为是非法指令,前者跳入填满0的储存区域,后者通常意味着总线或储存器损坏。
另外,RISC-V默认用小端储存系统,但非标准变种中可以支持大端或者双端储存系统。
异常:RISC-V线程中出现了指令相关的非正常情况。
自陷:RISC-V线程中出现了指令相关的异常情况,控制同步传输到自陷处理函数(一般在高特权环境中执行)。
中断:RISC-V线程外异步出现了一个事件,如果需要处理则需要选择某条指令来接收,并顺序产生自陷。
基本整数子集用户可见状态为31个寄存器x1~x31,用来保存整数值,其中x0是常数0。还有一个用户可见的寄存器pc用来保存当前指令的地址。
4种核心指令格式(R/I/S/U),都是固定32位长度的指令。基于立即数的处理,还有SB/UI这两种指令格式的变种。
在所有格式中,RISC-V ISA将源寄存器(rs1和rs2)和目标寄存器(rd)固定在同样的位 置,以简化指令译码。在指令中,立即数被打包,朝着最左边可用位的方向,并且已分配好以减少硬件复杂度。特别地,所有立即数的符号位总是在指令的第31位,以加速符号扩展电路。
整数计算指令要么使用I类格式编码R-I操作,要么使用R类格式编码R-R操作。其目标都是rd,不会产生异常。这里没有支持溢出检测指令,uint加法和int数组边界检测用一条分支即可完成,有符号数的加法溢出检测则需要几条指令(与被加数是立即数还是变量有关)。
ADDI/SLTI(U)/ANDI/ORI/XORI
ADDI—> rs1+=imm(12bit),溢出被忽略。ADDI rd,rs1,0 ==> 伪码 mv rd, rs1
SLTI(set less than imm), if(rs1
ANDI、ORI、XORI均为逻辑操作,在寄存器rs1和符号扩展位上的12bit按位AND、OR、XOR。
比如 XORI rd,rs1,-1 被用来按位取反 NOT rd,rs 。
SLLI/SRLI/SRAI
rs1被移位imm[4:0]次,SLLI/SRLI分别是逻辑左右移(空出来的位填0),SRAI是算术右移(符号位的单独处理,正数填充0,负数填充1)。
LUI/AUIPC
LUI用于构建32位常数,LUI将imm放到rd的高20位,低12位填0.
AUIPC(add upper immediate to pc) 用imm构建一个偏移量的高20位,低12位填0,并将此偏移加到pc上,将结果写入rd。
AUIPC+JALR可以将控制转移到任意32位相对地址,而加上一条12位立即数偏移的load/store指令就可以访问任意32位pc相对数据地址。
ADD/SLT/SLTU/AND/OR/XOR/SLL/SRL/SUB/SRA
ADD/SUB分别用于执行加减法、忽略溢出。
SLT/SLTU分别用于执行有无符号数的比较 if rs1
NOP指令
此指令不改变任何用户的可见状态,用于pc的向前推进。 被编码为 ADDI x0,x0,0
RV32I提供了两类控制转移指令,无条件跳转和条件分支。并且没有在体系结构中可见的分支延迟槽。
无条件跳转
JAL在立即数处编码了一个有符号偏移量,这个偏移量加到pc上后形成跳转目标地址,并将跳转指令后面指令的地址(pc+4)加载到rd,跳转范围为±1MB。标准软件调用约定使用寄存器x1来作为返回地址寄存器。
JALR(jump and link register) 通过有符号立即数加上rs1,然后将结果的最低位设置为0,作为目标地址,将跳转指令后面的地址存到rd中。
如果目标地址没有对齐到32位,JAL和JALR指令均会产生一个非对齐指令取址异常。
所有无条件跳转指令都是用pc相对寻址,有助于支持位置无关代码。JALR可以用来跳转到任何32位绝对地址空间。
首先LUI将目标地址的高20位加载到rs1中,然后JALR可以加上低12位。事实上,绝大多数JALR指令的使用要么是一个立即数0,要么就是配合LUI或者AUIPC来跳转到32位地址空间。
条件分支
所有的分支指令使用SB类指令格式。12位立即数编码了以2字节倍数的有符号偏移量,并被加到当前pc上,生成目标地址。于是条件分支的范围是±4KB。
BEQ/BNQ if (rs1==/!=rs2) Jmp
BLT/BLTU if(rs1
其他的如 BGT/BGTU/BLE/BLEU可以通过前面的比较指令组合来实现。
优化过程中,频率较大的分支放在直线位置上,较小的分支被放到跳转分支上,尽量减少跳转。无条件跳转应该总是用JAL指令而不是用永为真的条件跳转。 JAL既可以有更大的跳转范围,也不会占用分支预测器的条件预测表。
RV32I中只有load和store指令可以访问储存器,其他指令只能操作寄存器。
Load 为I类格式,而Store为S类指令格式。load类储存器地址是通过rs1+imm(偏移来实现),都是将储存器值复制到寄存器中。
store将rs2中的值复制储存器中。
LW 将32位值复制到rd中,LH从储存器中读取16位,然后将其符号扩展到32位,保存到dr中。LHU指令读取存储器16位,然后0扩展到32位,再保存到rd中。LB/LBU则是读取8位。SW/SH/SB分别将寄存器rs2中的低32/16/8/位到储存器中。
load和store操作的数据地址应该与操作的数据类型长度保持地址对齐,否则会被分解成两次访问,还需要额外的同步来保证原子性。
例如:将x2中的32位指令,保存到x3指向的存储器
sh x2, 0(x3) // 将指令的低半部分保存到第一个包裹中
srli x2, x2, 16 // 将高位移动到低位,覆盖x2
sh x2, 2(x3) // 将高位保存到第二个包裹中
RISC-V ISA允许在单一用户地址空间中支持多个线程同时执行。每个线程拥有自己的寄存器和程序计数器,并执行一段互不相关的指令流。 线程的创建和管理根据环境来定义,线程间的通讯可以根据环境或者共享储存器系统来通讯。RISC-V每个线程看到自己的存储操作就好像是按照程序中顺序执行的一样。线程间的存储模型在不同线程间的储存器上操作时需要FENCE指令来确保某些特定的顺序。
FENCE就是一个栅栏操作,其前后指令之间不能被乱序,可用于线程间的同步。
FENCE.I用于同步指令和序列流,用于线程内的同步。
用户级的CSR指令只能访问少数几个只读寄存器。
CSRRW(Atomic Read/write CSR)原子性低交换CSR和整数寄存器中的值。读取CSR 中的旧值将其0扩展到XLEN位,写入整数寄存器rd中。rs1中的值被写到CSR中。
CSRRS(Atomic Read and Set Bits in CSR) 读取CSR的值,0扩展到XLEN位,写入整数寄存器rd中。rs1中的值被当作掩码指明CSR中哪些位置被置1。rs1中为1的位会导致CSR中对应的位被置1,其他位不受影响(CSR|=rs1)。
CSRRC(Atomic Read and Clear Bits in CSR)功能同上,但是rs1中的值用来指明哪些位置零,即rs1中为1的位CSR对应的位将会被置0,其他位不受影响.
CSRRWI/CSRRSI/CSRRCI分别对应上面的几条指令,但是被扩展的是5位立即数而不是rs1寄存器的值.
读取CSR的汇编伪指令 CSRR 被编码为 CSRRS rd,csr,x0
写入CSR的汇编位指令 CSRW 被编码为CRSRW x0,csr,rs1
伪指令 CSRWI csr,zimm 被编码为 CSRRWI x0,csr,zimm
当不需要CSR旧值时, 同来设置和清除CSR中的位 CSRS/CSRC csr,rs1 CSRSI/CSRCI csr,zimm
RV32I提供了多个用户级只读的64位计数器,它们被映射到一个12位的CSR地址空间中, 它们可以使用CSRRS指令以32位片段的形式进行访问。
RDCYCLE 伪指令用来读取cycle CSR的低XLEN位,这个值是硬件线程开始执行以来的时钟周期数.RDCYCLEH用于读取高32位.
RDTIME读取time CSR的低XLEN位,这个数值是从过去任意时刻开始以来的在墙实时时间计数值.RDTIMEH是读取高32位.
RDINSTRET 读取instret CSR的低XLEN位,用于计数本硬件线程的线程退休指令数值.
这些计数器必须提供,而且可以被用户以较小代价访问.当然允许提供额外的寄存器以帮助性能诊断.
ECALL用于向环境(通常是操作系统)发出一个请求,系统环境ABI具体确定参数如何让传递,但是这些参数在整数寄存器中的位置应该确定.
EBREAK 被调试器使用,用来将控制权传送回调试器.
待续…