总结RV64I指令集

Last Updated: 2023-10-11 01:34:17 Wednesday

-- TOC --

RV64I中的I表示Base Architecture,我觉得是Integer的首字母,它们都是最基础的必须要有的指令,共记51条指令。

Register

RISC-V的64位架构有32个64位的寄存器,命名就是直接x0--x31

x0寄存器永远为0。任何时候读得到0,任何时候写入,等于写入黑洞。

Formats

            7        5       5      3          5        7
R:  |   funct7   |  rs2  |  rs1  |funct3|     rd    | opcode
I:  |     imm[11:0]      |  rs1  |funct3|     rd    | opcode
S:  |  imm[11:5] |  rs2  |  rs1  |funct3|  imm[4:0] | opcode
SB: |imm[12|10:5]|  rs2  |  rs1  |funct3|imm[4:1|11]| opcode
U:  |           imm[31:12]              |     rd    | opcode
UJ: |     imm[20|10:1|11|19:12]         |     rd    | opcode

Arithmetic

add x5, x6, x7   // x5 = x6 + x7
sub x5, x6, x7   // x5 = x6 - x7
addi x6, x6, 20  // x5 = x6 + 20
addw
subw
addiw

addi指令格式为I-type,立即数有12bit,按补码表达,取值范围-2^11 -- 2^11-1,即[-2048 -- 2047]。因此没有subi指令,直接addi一个负数即可。3条word version指令,只操作寄存器的低32bits。

Logical

and x5, x6, x7   // x5 = x6 & x7
or  x5, x6, x7   // x5 = x6 | x7
xor x5, x6, x7   // x5 = x6 ^ x7
andi x5, x6, 20  // x5 = x6 & 20
ori x5, x6, 20   // x5 = x6 | 20
xori x5, x6, 20  // x5 = x6 ^ 20

取反操作,通过异或全F来实现,没有单独的指令。(这样不会破坏3寄存器指令的美感)

andi,ori和xori采用I-type。

Shift

sll x5, x6, x7    // x5 = x6 << x7
srl x5, x6, x7    // x5 = x6 >> x7 (logical)
sra x5, x6, x7    // x5 = x6 >> x7 (arithmetic)
slli x5, x6, 3    // x5 = x6 << 3
srli x5, x6, 3    // x5 = x6 >> 3  (logical)
srai x5, x6, 3    // x5 = x6 >> 3  (arithmetic)
sllw
srlw
sraw 
slliw
srliw
sraiw

slli,srli和srai采用I-type,但12bits的立即数,只用低6bits,因为位移超过63是没意义的。(gcc会有warning,超过63后结果不可预测)高6bits取名为funct6,用来区分这3条指令。6条word version指令,只操作寄存器的低32bits。

获取更大的立即数,使用lui指令

lui x5, 0x12345    // x5 = 0x12345000

lui指令采用U-type格式。它将20bits的立即数,存入目标寄存器,然后左移12位,目标寄存器的高32位,复制此20bits的最高位。lui配合addi,就可以在寄存器中得到一个32bits的立即数。

Data Transfer

RISC-V uses byte addresses。ld指令使用I-type,12bits立即数,补码表达,byte offset。sd指令也只有12bits立即数,不同的是,sd指令采用S-type,这12bits的分布位置不同。

ld x5, 40(x6)      // x5 = Memory[x6+40] (doubleword, 64bits)
sd x5, 40(x6)      // Memory[x6+40] = x5
lw x5, 40(x6)      // x5 = Memory[x6+40] (word, 32bits, sign extension)
lwu x5, 40(x6)     // x5 = Memory[x6+40] (unsigned word, zero extension)
sw x5, 40(x6)      // Memory[x6+40] = x5
lh x5, 40(x6)      // x5 = Memory[x6+40] (half word, 16bits)
lhu x5, 40(x6)     // x5 = Memory[x6+40] (unsigned half word)
sh x5, 40(x6)      // Memory[x6+40] = x5
lb x5, 40(x6)      // x5 = Memory[x6+40] (byte)
lbu x5, 40(x6)     // x5 = Memory[x6+40] (unsigned byte)
sb x5, 40(x6)      // Memory[x6+40] = x5

Branch

Conditional

beq x5, x6, 100     // if(x5 == x6) goto PC+100
bne x5, x6, 100     // if(x5 != x6) goto PC+100
blt x5, x6, 100     // if(x5 < x6) goto PC+100
bge x5, x6, 100     // if(x5 >= x6) goto PC+100
bltu x5, x6, 100    // if(x5 < x6) goto PC+100 (unsigned)
bgeu x5, x6, 100    // if(x5 >= x6) goto PC+100 (unsigned)

采用SB-type格式,12bits立即数,但使用时要乘2,因此conditional branch只能branch to even address。实际地址范围PC + [-4096 -- 4094]。因为RV64I地址固定4字节,每条指令的起始地址都是偶数。另外有一套RISC-V的标准,地址是2字节,为了兼容,因此这里只乘了2。

Unconditional

jal x1, 100        // x1 = PC+4; goto PC+100
jalr x1, 100(x5)   // x1 = PC+4; goto x5+100

jal是唯一采用UJ格式的指令,UJ格式有20bits的地址立即数。同样,jal指令中的立即数也要乘2,只能encode偶数地址。而jalr有2个寄存器,采用I-type格式。

更大的跳转地址,用auipc指令

auipc x1, 0x12345    // x1 = PC+0x12345000

类似lui指令,auipc指令将一个20bits的立即数与PC相加,但是加到PC的12到31位,或者说,左移12位再加,结果存入寄存器。如果再配合上一条addi指令,就可以实现围绕PC进行4G地址空间的地址计算。

Set Less Than

slt x5, x6, x7       // x5 = x6<x7 ? 1: 0
sltu x5, x6, x7      // x5 = x6<x7 ? 1: 0 (unsigned)
slti x5, x6, 42      // x5 = x6<42 ? 1: 0
sltiu x5, x6, 42     // x5 = x6<42 ? 1: 0 (unsigned)

slti和sltiu采用I-type格式。

Synchronization

同步源语。

lr.d x10, (x20)        // x10 = Memory[x20],load-reserved
// 尝试将x12写入Memroy[20],
// 如果写入时Memroy[20]值没有发生变化,写入成功,x11=1,
// 如果写入时Memroy[20]值与lr.d时不一样,写入失败,x11=0。
sc.d x11, x12, (x20)  

举个例子:

        addi x12, x0, 1       // x12 = 1
again:  lr.d x10, (x20)       // read lock
        bne x10, x0, again    // if not zero, read again
        sc.d x11, x12, (x20)  // if zero, try to set 1
        bne x11, x0, again    // if set fail, try again

Addressing

所谓addressing,就是通过哪种方式,确定operand在哪里!共有4种Addressing Modes:

伪指令

前面内容,已经将RV64I涉及的所有51条指令全部总结了。而所谓伪指令,就是RISC-V汇编器提供的为了方便编写代码但ISA中并不真实存在的指令。由于这些指令不属于ISA,因此称为伪指令。伪指令汇编之后,会被翻译成真实指令,一条伪指令对应一条或多条真实指令。伪指令的存在,稍微降低了一点点直接用汇编编写代码的难度。

li(load immediate)

加载立即数。如果使用li伪指令获得立即数,当立即数大于12bit能够表达的范围时,li伪指令将会被翻译为lui和addi的组合,以支持一个32bit可以表达的立即数。

la(load address)

加载地址。如果使用la伪指令来实现基于PC的寻址,当需要的地址大于20bit所内表达的范围时,la伪指令会被翻译为auipc和addi的组合,以支持一个32bit可以表达的立即数。

bnez(branch if not zero)

某寄存器不等于0时,分支跳转。

beqz(branch if zero)

某寄存器等于0时,分支跳转。

seqz(set if zero)

snez(set if not zero)

mv(move)

x86体系经典的move,在RISC-V这里,只是一条伪指令。

j(jump)

jr(jump register)

ret(return)

neg(negate)

用0去减。

not

与全F做xor。

nop(no operation)

资料上说,就是用addi指令做加法,用x0寄存器。

本文链接:https://cs.pynote.net/hd/asm/202309282/

-- EOF --

-- MORE --