RISC-V 指令集分析
背景
RISC-V指令集作为当前最新最流行的开源指令集,吸收了之前出现的指令集的优点,并极力避免了一些不足,创造性地使用了模块化指令集的概念,使得基于RISC-V指令集设计的芯片既可以用于极低功耗的嵌入式环境,也可用于高性能高带宽的业务场景,所以RISC-V具有广阔的应用市场和发展前景。本项目希望设计一个基于RV64I指令集的CPU,加强对于RISC-V的认知和理解。RV64I是64位架构的整数指令集,其是整个RISC-V指令集的一个子集,也是RV32I指令集的超集,也就是说RV64I是在RV32I的基础上发展而来,在后面具体的指令集分析时候,也可以看出来。设计CPU的第一步就是对于指令集的分析,本篇文章就是对于RV64指令集的一个简单分析。
RV64I
首先需要明确一点,RV64I和RV32I的指令宽度都是32bit。RV64I共有64条指令,包括59条基本指令,5条特权指令。59条基本指令中包括47条从RV32I指令发展而来的基本指令,这些指令大部分RV64I和RV32I通用,有小部分有细微差别;还包括12条RV64I独有的指令。总的指令分类有R型、I型、S型、B型、U型、J型六种。下图是从RISC-V unprivilege spec中截取的指令分类图。
具体的指令分析如下所示。
注意 sext即符号位扩展;shamt即shift amount即移位数量;u代表视为无符号数,s代表视为有符号数;tmp都是32位的
R型指令
R型指令的格式如下所示:
31~25 | 24~20 | 19~15 | 14~12 | 11~7 | 6~0 |
---|
func7 | rs2 | rs1 | func3 | rd | opcode |
具体指令格式则如下所示:
RV32I & RV64I
指令 | func7 | rs2 | rs1 | func3 | rd | opcode | 操作 |
---|
add | 0000000 | rs2 | rs1 | 000 | rd | 0110011 | 加法,x[rd]=x[rs1] + x[rs2],忽略算术溢出 |
sub | 0100000 | rs2 | rs1 | 000 | rd | 0110011 | 减法,x[rd]=x[rs1] - x[rs2],忽略算术溢出 |
sll | 0000000 | rs2 | rs1 | 001 | rd | 0110011 | 逻辑左移,x[rd]=x[rs1] « x[rs2],低位填0,RV64I中rs2低6位代表移位个数,忽略高位 |
srl | 0000000 | rs2 | rs1 | 101 | rd | 0110011 | 逻辑右移,x[rd]=x[rs1] » u x[rs2],高位填0,RV64I中rs2低6位代表移位个数,忽略高位 |
sra | 0100000 | rs2 | rs1 | 101 | rd | 0110011 | 算术右移,x[rd]=x[rs1] » s x[rs2],高位填rs1最高位,RV64I中rs2低6位代表移位个数,忽略高位 |
slt | 0000000 | rs2 | rs1 | 010 | rd | 0110011 | 有符号小于置位,若x[rs1] < s x[rs2],x[rd]为1,否则为0 |
sltu | 0000000 | rs2 | rs1 | 011 | rd | 0110011 | 无符号小于置位,若x[rs1] < u x[rs2],x[rd]为1,否则为0 |
and | 0000000 | rs2 | rs1 | 111 | rd | 0110011 | x[rd]=x[rs1] & x[rs2],位与 |
or | 0000000 | rs2 | rs1 | 110 | rd | 0110011 | x[rd]=x[rs1] | x[rs2],位或 |
xor | 0000000 | rs2 | rs1 | 100 | rd | 0110011 | x[rd]=x[rs1] ^ x[rs2],位异或 |
RV64I
以下指令得到的结果都是32bit的,将32bit符号扩展之后再写入64位的xd中
指令 | func7 | rs2 | rs1 | func3 | rd | opcode | 操作 |
---|
addw | 0000000 | rs2 | rs1 | 000 | rd | 0111011 | tmp=rs1[31:0]+rs2[31:0], rd=sext(tmp[31:0]);先截取rs1和rs2低32位,将其和符号扩展写入xd |
subw | 0100000 | rs2 | rs1 | 000 | rd | 0111011 | tmp=rs1[31:0]-rs2[31:0], rd=sext(tmp[31:0]);先截断rs1和rs2低32位,将其差符号扩展写入xd |
sllw | 0000000 | rs2 | rs1 | 001 | rd | 0111011 | tmp=rs1[31:0] « rs2[4:0], rd=sext(tmp[31:0]);rs1低32bit逻辑左移,低位填0,rs2低5位为移动位数 |
srlw | 0000000 | rs2 | rs1 | 101 | rd | 0111011 | tmp=rs1[31:0] » u rs2[4:0], rd=sext(tmp[31:0]);rs1低32bit逻辑右移,高位填0,rs2低5位为移动位数 |
sraw | 0100000 | rs2 | rs1 | 101 | rd | 0111011 | tmp=rs1[31:0] « s rs2[4:0], rd=sext(tmp[31:0]);rs1低32bit算术右移,高位填rs1[31],rs2低5位为移动位数 |
U型指令
属于RV31I和RV64I的U型指令只有两条,其格式如下所示:
31~12 | 11~7 | 6~0 |
---|
imm[19:0] | rd | opcode |
具体指令格式则如下所示:
RV32I & RV64I
指令 | imm[19:0] | rd | opcode | 操作 |
---|
lui | imm[19:0] | rd | 0110111 | x[rd]=sext(imm[19:0] « 12),20bit立即数逻辑左移12位,低位填0,符号扩展之后写入rd |
auipc | imm[19:0] | rd | 0010111 | x[rd]=pc+sext(imm[19:0] « 12),20bit立即数逻辑左移12位,低位填0,符号扩展到64位,然后加上pc写入rd |
J型指令
属于RV31I和RV64I的J型指令只有一条,其格式如下所示:
31 | 30~21 | 20 | 19~12 | 11~7 | 6~0 |
---|
imm[20] | imm[10:1] | imm[11] | imm[19:12] | rd | opcode |
具体指令格式则如下所示:
RV32I & RV64I
指令 | imm[20] | imm[10:1] | imm[11] | imm[19:12] | rd | opcode | 操作 |
---|
jal | imm[20] | imm[10:1] | imm[11] | imm[19:12] | rd | 1101111 | x[rd]=pc+4, pc+=sext(imm20); 跳转到立即数加pc的地址,并将pc+4数值存入rd,rd默认为x1,imm20符号扩展到64位 |
jal指令需要注意偏移量是带符号扩展的,并且偏移量是2字节对齐的(imm[20:1]),虽然RV32I和RV64I中所有指令地址都是4字节对齐的,但是jal指令还可能被用到用于兼容C扩展指令集,所以就默认imm[0]位为0,即2字节对齐。因此jal跳转的地址范围有+/-1MB的范围(2^21=2MB=+/-1MB)。
S型指令
S型指令格式如下所示:
31~25 | 24~20 | 19~15 | 14~12 | 11~7 | 6~0 |
---|
imm[11:5] | rs2 | rs1 | func3 | imm[4:0] | opcode |
具体指令格式则如下所示:
RV32I & RV64I
指令 | imm[11:5] | rs2 | rs1 | func3 | imm[4:0] | opcode | 操作 |
---|
sb | imm[11:5] | rs2 | rs1 | 000 | imm[4:0] | 0100011 | M[x[rs1]+sext(imm)]=x[rs2][7:0],将rs2的低8位存入到内存 |
sh | imm[11:5] | rs2 | rs1 | 001 | imm[4:0] | 0100011 | M[x[rs1]+sext(imm)]=x[rs2][15:0],将rs2的低16位存入到内存 |
sw | imm[11:5] | rs2 | rs1 | 010 | imm[4:0] | 0100011 | M[x[rs1]+sext(imm)]=x[rs2][31:0],将rs2的低32位存入到内存 |
RV64I
指令 | imm[11:5] | rs2 | rs1 | func3 | imm[4:0] | opcode | 操作 |
---|
sd | imm[11:5] | rs2 | rs1 | 011 | imm[4:0] | 0100011 | M[x[rs1]+sext(imm)]=x[rs2][63:0],将rs2的64位存入到内存 |
B型指令
B型指令格式如下所示:
31 | 30~25 | 24~20 | 19~15 | 14~12 | 11~8 | 7 | 6~0 |
---|
imm[12] | imm[10:5] | rs2 | rs1 | func3 | imm[4:1] | imm[11] | opcode |
B型指令同jal指令类似,偏移量都是2字节对齐,也就是意味着imm[0]默认为0。
具体指令格式则如下所示:
RV32I & RV64I
指令 | imm[12] | [10:5] | rs2 | rs1 | func3 | [4:1] | [11] | opcode | 操作 |
---|
beq | imm[12] | [10:5] | rs2 | rs1 | 000 | [4:1] | [11] | 1100011 | if (rs1 == rs2), pc += sext(imm) |
bne | imm[12] | [10:5] | rs2 | rs1 | 001 | [4:1] | [11] | 1100011 | if (rs1 != rs2), pc += sext(imm) |
blt | imm[12] | [10:5] | rs2 | rs1 | 100 | [4:1] | [11] | 1100011 | if (rs1 < rs2), pc += sext(imm),x[rs1]和x[rs2]为有符号数 |
bltu | imm[12] | [10:5] | rs2 | rs1 | 110 | [4:1] | [11] | 1100011 | if (rs1 < rs2), pc += sext(imm),x[rs1]和x[rs2]为无符号数 |
bge | imm[12] | [10:5] | rs2 | rs1 | 101 | [4:1] | [11] | 1100011 | if (rs1 >= rs2), pc += sext(imm),x[rs1]和x[rs2]为有符号数 |
bgeu | imm[12] | [10:5] | rs2 | rs1 | 111 | [4:1] | [11] | 1100011 | if (rs1 >= rs2), pc += sext(imm),x[rs1]和x[rs2]为无符号数 |
I型指令
I型指令格式如下:
31~20 | 19~15 | 14~12 | 11~7 | 6~0 |
---|
imm[11:0] | rs1 | func3 | rd | opcode |
RV32I & RV64I
指令 | imm[11:0] | rs1 | func3 | rd | opcode | 操作 |
---|
addi | imm[11:0] | rs1 | 000 | rd | 0010011 | x[rd]=x[rs1] + sext(imm),忽略算术溢出 |
slti | imm[11:0] | rs1 | 010 | rd | 0010011 | x[rd]=x[rs1] < s sext(imm),x[rs1]小于符号位扩展的imm,rd置1,否则置0,两者都为有符号数 |
sltiu | imm[11:0] | rs1 | 011 | rd | 0010011 | x[rd]=x[rs1] < u sext(imm),x[rs1]小于符号位扩展的imm,rd置1,否则置0,两者都为无符号数 |
andi | imm[11:0] | rs1 | 111 | rd | 0010011 | x[rd]=x[rs1] & sext(imm),位与 |
ori | imm[11:0] | rs1 | 110 | rd | 0010011 | x[rd]=x[rs1] | sext(imm),位或 |
xori | imm[11:0] | rs1 | 100 | rd | 0010011 | x[rd]=x[rs1] ^ sext(imm),位异或 |
slli | {000000,shamt6} | rs1 | 001 | rd | 0010011 | x[rd]=x[rs1] « shamt6,逻辑左移,移位数为shamt6,视为无符号数;RV32I中移位数为shamt低5位 |
srli | {000000,shamt6} | rs1 | 101 | rd | 0010011 | x[rd]=x[rs1] » u shamt6,逻辑右移,移位数为shamt6,视为无符号数;RV32I中移位数为shamt低5位 |
srai | {010000,shamt6} | rs1 | 101 | rd | 0010011 | x[rd]=x[rs1] » s shamt6,算术右移,移位数为shamt6,视为无符号数;RV32I中移位数为shamt低5位 |
指令 | imm[11:0] | rs1 | func3 | rd | opcode | 操作 |
---|
csrrw | imm[11:0] | rs1 | 001 | rd | 1110011 | t=CSRs[imm]; CSRs[imm]=x[rs1]; x[rd]=t,imm[11:0]指明是哪个csr,将csr旧值0扩展写入到rd,rs1的值写入到csr |
csrrs | imm[11:0] | rs1 | 010 | rd | 1110011 | t=CSRs[imm]; CSRs[imm]=t|x[rs1]; x[rd]=t,imm[11:0]指明是哪个csr,将csr旧值0扩展写入到rd,rs1的值位或上csr旧值写入到csr |
csrrc | imm[11:0] | rs1 | 011 | rd | 1110011 | t=CSRs[imm]; CSRs[imm]=t&~x[rs1]; x[rd]=t,imm[11:0]指明是哪个csr,将csr旧值0扩展写入到rd,rs1的值先按位取反,再位与上csr旧值写入到csr |
csrrwi | imm[11:0] | zimm[4:0] | 101 | rd | 1110011 | x[rd]=CSRs[imm]; CSRs[imm]=zimm,imm[11:0]指明是哪个csr,将csr旧值0扩展写入到rd,5bit的zimm0扩展写入到csr |
csrrsi | imm[11:0] | zimm[4:0] | 110 | rd | 1110011 | t=CSRs[imm]; CSRs[imm]=t|zimm; x[rd]=t,imm[11:0]指明是哪个csr,将csr旧值0扩展写入到rd,5bit的zimm0扩展和csr位或写入到csr |
csrrci | imm[11:0] | zimm[4:0] | 111 | rd | 1110011 | t=CSRs[imm]; CSRs[imm] =t&∼zimm; x[rd]=t,imm[11:0]指明是哪个csr,将csr旧值0扩展写入到rd,5bit的zimm0扩展先安慰取反,在和和csr位与写入到csr |
指令 | imm[11:0] | rs1 | func3 | rd | opcode | 操作 |
---|
lb | imm[11:0] | rs1 | 000 | rd | 0000011 | x[rd]=sext(M[x[rs1]+sext(imm)][7:0]),从内存中读取一个字节,地址是rs1和imm符号扩展后的加和,字节符号扩展后写入rd |
lh | imm[11:0] | rs1 | 001 | rd | 0000011 | x[rd]=sext(M[x[rs1]+sext(imm)][15:0]),从内存中读取一个半字,地址是rs1和imm符号扩展后的加和,半字符号扩展后写入rd |
lw | imm[11:0] | rs1 | 010 | rd | 0000011 | x[rd]=sext(M[x[rs1]+sext(imm)][31:0]),从内存中读取一个字,地址是rs1和imm符号扩展后的加和,字符号扩展后写入rd |
lbu | imm[11:0] | rs1 | 100 | rd | 0000011 | x[rd]=M[x[rs1]+sext(imm)][7:0],从内存中读取一个字节,地址是rs1和imm符号扩展后的加和,字节0扩展后写入rd |
lhu | imm[11:0] | rs1 | 101 | rd | 0000011 | x[rd]=M[x[rs1]+sext(imm)][15:0],从内存中读取一个半字,地址是rs1和imm符号扩展后的加和,半字0扩展后写入rd |
指令 | imm[11:0] | rs1 | func3 | rd | opcode | 操作 |
---|
jalr | imm[11:0] | rs1 | 000 | rd | 1100111 | t=pc+4, pc=(x[rs1]+sext(imm))&∼1, x[rd]=t; 将计算出的pc值最低有效位设为0,将pc+4值写入rd,rd默认x1 |
jalr指令是将rs1值与符号位扩展的imm[11:0]加起来,并将最后一位设为0(1取反并这个相加结果位与,就是将最后一位设为0)作为当前的pc值(最后一位为0是为了字节对齐),而(pc+4)的值写入到rd中。
指令 | 31~20 | 19~15 | 14~12 | 11~7 | 6~0 | 操作 |
---|
ecall | 000000000000 | 00000 | 000 | 00000 | 1110011 | 环境调用,通过引发环境调用异常来请求执行环境 |
ebreak | 000000000001 | 00000 | 000 | 00000 | 1110011 | 环境断点,通过抛出断点异常的方式请求调试器 |
指令 | 31~20 | 19~15 | 14~12 | 11~7 | 6~0 | 操作 |
---|
fence | {0000,pred,succ} | 00000 | 000 | 00000 | 0001111 | |
fence.i | 000000000000 | 00000 | 001 | 00000 | 0001111 | |
RV64I
指令 | imm[11:0] | rs1 | func3 | rd | opcode | 操作 |
---|
addiw | imm[11:0] | rs1 | 000 | rd | 0011011 | tmp=rs1[31:0]+sext(imm)[31:0], rd=sext(tmp); rs1低32位和imm符号扩展后的低32位相加,将和进行符号扩展写入xd,忽略算术溢出 |
slliw | {0000000,shamt5} | rs1 | 001 | rd | 0011011 | tmp=(rs1[31:0] « shamt5)[31:0], rd=sext(tmp); rs1截断低32位进行逻辑左移再截断低32位,左移位数由5bit的shamt决定,二次截断后符号位扩展写入rd |
srliw | {0000000,shamt5} | rs1 | 101 | rd | 0011011 | tmp=(rs1[31:0] » shamt5)[31:0], rd=sext(tmp); rs1截断低32位进行逻辑右移再截断低32位,右移位数由5bit的shamt决定,二次截断后符号位扩展写入rd |
sraiw | {0100000,shamt5} | rs1 | 101 | rd | 0011011 | tmp=(rs1[31:0] » s shamt5)[31:0], rd=sext(tmp); rs1截断低32位进行算术右移再截断低32位,右移位数由5bit的shamt决定,二次截断后符号位扩展写入rd |
指令 | imm[11:0] | rs1 | func3 | rd | opcode | 操作 |
---|
lwu | imm[11:0] | rs1 | 110 | rd | 0000011 | x[rd]=M[x[rs1]+sext(imm)][31:0],rs1加上符号扩展的imm作为内存地址,取该地址低32位,0扩展后写入rd |
ld | imm[11:0] | rs1 | 011 | rd | 0000011 | x[rd]=M[x[rs1]+sext(imm)][63:0],rs1加上符号扩展的imm作为内存地址,取该地址64位数据写入rd |
特权指令
指令 | 31~20 | 19~15 | 14~12 | 11~7 | 6~0 | 操作 |
---|
uret | 000000000010 | 00000 | 000 | 00000 | 1110011 | |
mret | 001100000010 | 00000 | 000 | 00000 | 1110011 | |
sret | 000100000010 | 00000 | 000 | 00000 | 1110011 | |
指令 | 31~25 | 24~20 | 19~15 | 14~12 | 11~7 | 6~0 | 操作 |
---|
sfence.vma | 0001001 | rs2 | rs1 | 000 | rd | 1110011 | |
wfi | 0001000 | 00101 | 00000 | 000 | 00000 | 1110011 | |
RV64M
RV64M指令集共有13条指令,其指令格式如下所示:
31~25 | 24~20 | 19~15 | 14~12 | 11~7 | 6~0 |
---|
func7 | rs2 | rs1 | func3 | rd | opcode |
具体指令集描述如下所示:
指令 | 名称 | 31~25 | 24~20 | 19~15 | 14~12 | 11~7 | 6~0 | 操作 |
---|
mul | 有符号乘法 | 0000001 | rs2 | rs1 | 000 | rd | 0110011 | rd=(rs1*rs2)[63:0] |
mulh | 有符号乘法取高位 | 0000001 | rs2 | rs1 | 001 | rd | 0110011 | rd=(rs1*rs2)[127:64] |
mulhu | 无符号乘法取高位 | 0000001 | rs2 | rs1 | 011 | rd | 0110011 | rd=(rs1*rs2)[127:64] |
mulhsu | 有符号无符号乘法取高位 | 0000001 | rs2 | rs1 | 010 | rd | 0110011 | rd=(rs1*rs2)[127:64]; rs1有符号数,rs2无符号数 |
div | 有符号除法 | 0000001 | rs2 | rs1 | 100 | rd | 0110011 | rd=rs1/rs2; 除数为0,结果为0xffffffffffffffff; 产生overflow,结果为0x8000000000000000 |
divu | 无符号除法 | 0000001 | rs2 | rs1 | 101 | rd | 0110011 | rd=rs1/rs2; 除数为0,结果为0xffffffffffffffff |
rem | 有符号取余 | 0000001 | rs2 | rs1 | 110 | rd | 0110011 | rd=rs1%rs2; 除数为0,求余结果为被除数; 产生overflow,余数结果为0x0 |
remu | 无符号取余 | 0000001 | rs2 | rs1 | 111 | rd | 0110011 | rd=rs1%rs2; 除数为0,求余结果为被除数 |
指令 | 名称 | 31~25 | 24~20 | 19~15 | 14~12 | 11~7 | 6~0 | 操作 |
---|
mulw | 低32位有符号乘法 | 0000001 | rs2 | rs1 | 000 | rd | 0111011 | tmp=(rs1[31:0]*rs2[31:0])[31:0], rd=sext(tmp[31:0]) |
divw | 低32位有符号除法 | 0000001 | rs2 | rs1 | 100 | rd | 0111011 | tmp=(rs1[31:0]/rs2[31:0])[31:0], rd=sext(tmp[31:0]); 除数为0,结果为0xffffffffffffffff; 产生overflow,结果为0xffffffff80000000 |
divuw | 低32位无符号除法 | 0000001 | rs2 | rs1 | 101 | rd | 0111011 | tmp=(rs1[31:0]/rs2[31:0])[31:0], rd=sext(tmp[31:0]); 除数为0,结果为0xffffffffffffffff |
remw | 低32位有符号取余 | 0000001 | rs2 | rs1 | 110 | rd | 0111011 | tmp=(rs1[31:0]%rs2[31:0])[31:0], rd=sext(tmp[31:0]); 除数为0,求余结果为被除数[31]位符号位扩展后的结果,产生overflow时,余数结果为0x0 |
remuw | 低32位无符号取余 | 0000001 | rs2 | rs1 | 111 | rd | 0111011 | tmp=(rs1[31:0]%rs2[31:0])[31:0], rd=sext(tmp[31:0]) ; 除数为0,求余结果为被除数[31]位符号位扩展后的结果 |