Last Updated: 2023-10-11 01:34:17 Wednesday
-- TOC --
RV64I
中的I表示Base Architecture,我觉得是Integer的首字母,它们都是最基础的必须要有的指令,共记51
条指令。
RISC-V的64位架构有32个64位的寄存器,命名就是直接x0--x31
。
x0寄存器永远为0
。任何时候读得到0,任何时候写入,等于写入黑洞。
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
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。
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。
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的立即数。
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
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地址空间的地址计算。
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格式。
同步源语。
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,就是通过哪种方式,确定operand在哪里!共有4种Addressing Modes:
立即数
寄存器
Base Addressing
,即寄存器+立即数得到operand地址的方式PC-relative Addressing
,即PC+立即数得到operand地址的方式前面内容,已经将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 --