跳转至

RISC-V ISA

Abstract

基础知识

  • RISC-V 是一个 ISA 标准
  • RISC-V 是完全开放的,允许任何人使用
  • 简单、可扩展
  • 分为用户级指令集规范(User-level ISA spec)和特权级指令集规范(Privilege ISA spec)等
  • 命名为 RV + 数据宽度 + 扩展

指令集

  • 基础指令集:RV32I、RV64I(32/64 位带有整型操作的指令集)、RV32E(只有 16 个寄存器的 32 位整型指令集)
    • I:带有 ALU 运算、分支跳转、装载存储
  • 扩展:
    • M:增加整型乘法、除法
    • A:原子指令
    • F:增加单精度浮点数
    • D:增加双精度浮点数
    • Q:增加 128 位四精度浮点数
    • G:= IMAFD
    • ...
  • 扩展了的指令集则命名为 RV32IM、RV32IMA、RV32G 等等

编译运行

编译

编译使用 riscv-collab/riscv-gnu-toolchain

  • 需要手动克隆源码(极大),再编译,时间很长
  • 编译时注意 prefix 和选择指令集 --with-arch
  • make linux 编译出来的是 riscvxx-unknown-linux-gnu-xxx,使用 glibc 标准库,支持动态链接
  • make 或 make newlib 编译出来的是 riscvxx-unknown-elf-xxx,使用 riscv-newlib,只能静态链接

编译 c 代码使用 riscvxx-unknown-xxx-gcc 就可以了,注意指定 -march 和 -mabi,比如加上 -march=rv32i -mabi=ilp32 后编译出来的就是使用 RV32I 指令集的机器码

运行

因为 RISC-V 是另一种架构,不能在 x86 机器上直接运行,所以要使用 qemu 来运行。qemu 直接下载就可以,一般都会自带 RISC-V 的模拟器,如果是手动编译需要注意指定编译出 RISC-V

RV32I 基础

寄存器

  • 一个 PC 寄存器(program counter)
  • 32 个 32 位寄存器(x0~x31)
    • 其中 x0 永远是 0

寄存器的常用用途如下:

寄存器 ABI 名称 用途描述 saver
x0 zero 硬件 0
x1 ra 返回地址(return address) caller
x2 sp 栈指针(stack pointer) callee
x3 gp 全局指针(global pointer)
x4 tp 线程指针(thread pointer)
x5 t0 临时变量/备用链接寄存器(alternate link reg) caller
x6-7 t1-2 临时变量 caller
x8 s0/fp 需要保存的寄存器/帧指针(frame pointer) callee
x9 s1 需要保存的寄存器 callee
x10-11 a0-1 函数参数/返回值 caller
x12-17 a2-7 函数参数 caller
x18-27 s2-11 需要保存的寄存器 callee
x28-31 t3-6 临时变量 caller

其中 sp s0-11 需要在函数调用前后保证一致,其它不用保证

指令格式

RV32I 有 4 种基础的指令格式(R/I/S/U),再根据立即数解码的不同又分出两种(B/J),总共六种指令格式

R 型指令

31 25 24 20 19 15 14 12 11 7 6 0
funct7 rs2 rs1 funct3 rd opcode

使用寄存器进行数字逻辑运算的指令格式,运算由 opcode funct3 funct7 决定,rd = rs1 op rs2(shift 类例外,它们用 rs2 位置表示移位数的立即数)

I 型指令

31 20 19 15 14 12 11 7 6 0
imm[11:0] rs1 funct3 rd opcode

使用寄存器和立即数进行数字逻辑运算,以及 load 类指令等的指令格式,运算类型等由 opcode funct3 决定,如果是 ALU 运算,则 rd = rs1 op imm

立即数是 {{20{inst[31]}}, inst[31:20]},也就是对 imm[11:0] 进行符号位扩展到 32 位

S 型指令

31 25 24 20 19 15 14 12 11 7 6 0
imm[11:5] rs2 rs1 funct3 imm[4:0] opcode

store 类指令,store 的大小由 funct3 决定,以变址模式进行寻址,即 rs1 = [rs2+imm]

立即数是 {{20{inst[31]}}, inst[31:25], inst[11:7]}

B 型指令

31 25 24 20 19 15 14 12 11 7 6 0
imm[12,10:5] rs2 rs1 funct3 imm[4:1,11] opcode

由 S 型指令分来,与之区别是立即数读取顺序不同,是所有分支类指令。是否分支由 funct3 rs1 rs2 决定

立即数是 {{19{inst[31]}}, inst[31], inst[7], inst[30:25], inst[11:8], 1'b0}

U 型指令

31 12 11 7 6 0
imm[31:12] rd opcode

LUI 和 AUIPC,立即数都是在高 20 位,而且没有源操作数

立即数是 {inst[31:12], 12'b0}

J 型指令

31 12 11 7 6 0
imm[20,10:1,11,19:12] rd opcode

由 U 型指令分来,区别也是立即数读取不同,仅有 JAL 一个指令

立即数是 {{11{inst[31]}}, inst[31], inst[19:12], inst[20], inst[30:21], 1'b0}

RV32I 指令

整型计算指令

加减法指令

add R 型
31 25 24 20 19 15 14 12 11 7 6 0
0000000 rs2 rs1 000 rd 0110011
  • 指令格式:add rd, rs1, rs2
  • 指令作用:rd = rs1 + rs2
  • 注意:溢出会被忽略
sub r 型
31 25 24 20 19 15 14 12 11 7 6 0
0100000 rs2 rs1 000 rd 0110011
  • 指令格式:sub rd, rs1, rs2
  • 指令作用:rd = rs1 - rs2
  • 注意:溢出会被忽略
addi I 型
31 20 19 15 14 12 11 7 6 0
imm[11:0] rs1 000 rd 0010011
  • 指令格式:addi rd, rs1, imm
  • 指令作用:rd = rs1 + imm
  • 注意:溢出会被忽略,imm 在 [-2048, 2047] 范围内

比较运算指令

slt r 型
31 25 24 20 19 15 14 12 11 7 6 0
0000000 rs2 rs1 010 rd 0110011
  • 指令格式:slt rd, rs1, rs2
  • 指令作用:(set less than)如果 rs1 < rs2 则 rd = 1,否则 rd = 0
  • 注意:rs1 rs2 会被视为有符号数进行比较
sltu r 型
31 25 24 20 19 15 14 12 11 7 6 0
0000000 rs2 rs1 011 rd 0110011
  • 指令格式:sltu rd, rs1, rs2
  • 指令作用:(set less than unsigned)如果 rs1 < rs2 则 rd = 1,否则 rd = 0
  • 注意:rs1 rs2 会被视为无符号数进行比较
slti I 型
31 20 19 15 14 12 11 7 6 0
imm[11:0] rs1 010 rd 0010011
  • 指令格式:slti rd, rs1, imm
  • 指令作用:(set less than immediate)如果 rs1 < imm 则 rd = 1,否则 rd = 0
  • 注意:imm 在 [-2048, 2047] 范围内,被视为有符号数进行比较
sltiu I 型
31 20 19 15 14 12 11 7 6 0
imm[11:0] rs1 011 rd 0010011
  • 指令格式:sltiu rd, rs1, imm
  • 指令作用:(set less than immediate unsigned)如果 rs1 < imm 则 rd = 1,否则 rd = 0
  • 注意:imm 在 [-2048, 2047] 范围内,rs1 imm 被视为无符号数进行比较

二进制位运算指令

and r 型
31 25 24 20 19 15 14 12 11 7 6 0
0000000 rs2 rs1 111 rd 0110011
  • 指令格式:and rd, rs1, rs2
  • 指令作用:rd = rs1 & rs2 按位与
or r 型
31 25 24 20 19 15 14 12 11 7 6 0
0000000 rs2 rs1 110 rd 0110011
  • 指令格式:or rd, rs1, rs2
  • 指令作用:rd = rs1 | rs2 按位或
xor r 型
31 25 24 20 19 15 14 12 11 7 6 0
0000000 rs2 rs1 100 rd 0110011
  • 指令格式:xor rd, rs1, rs2
  • 指令作用:rd = rs1 ^ rs2 按位异或
andi I 型
31 20 19 15 14 12 11 7 6 0
imm[11:0] rs1 111 rd 0010011
  • 指令格式:andi rd, rs1, imm
  • 指令作用:rd = rs1 & imm 按位与
  • 注意:imm 在 [-2048, 2047] 范围内,会扩展符号位
ori I 型
31 20 19 15 14 12 11 7 6 0
imm[11:0] rs1 110 rd 0010011
  • 指令格式:ori rd, rs1, imm
  • 指令作用:rd = rs1 | imm 按位或
  • 注意:imm 在 [-2048, 2047] 范围内,会扩展符号位
xori I 型
31 20 19 15 14 12 11 7 6 0
imm[11:0] rs1 100 rd 0010011
  • 指令格式:xori rd, rs1, imm
  • 指令作用:rd = rs1 ^ imm 按位异或
  • 注意:imm 在 [-2048, 2047] 范围内,会扩展符号位(xori rd, rs1, -1 相当于 rd = ~rs1)

移位运算指令

sll r 型
31 25 24 20 19 15 14 12 11 7 6 0
0000000 rs2 rs1 001 rd 0110011
  • 指令格式:sll rd, rs1, rs2
  • 指令作用:rd = rs1 << rs2[4:0] 左移(左侧丢掉,右侧补 0)
  • 注意:会取 rs2 内数值的低 5 位进行运算
srl r 型
31 25 24 20 19 15 14 12 11 7 6 0
0000000 rs2 rs1 101 rd 0110011
  • 指令格式:srl rd, rs1, rs2
  • 指令作用:rd = rs1 >> rs2[4:0] 逻辑右移(左侧补 0,右侧丢掉)
  • 注意:会取 rs2 内容的低 5 位
sra r 型
31 25 24 20 19 15 14 12 11 7 6 0
0100000 rs2 rs1 101 rd 0110011
  • 指令格式:sra rd, rs1, rs2
  • 指令作用:rd = rs1 >>> rs2[4:0] 算数右移(左侧补符号位,右侧丢掉)
  • 注意:会取 rs2 内容的低 5 位进行运算
slli r 型
31 25 24 20 19 15 14 12 11 7 6 0
0000000 shamt rs1 001 rd 0010011
  • 指令格式:slli rd, rs1, shamt
  • 指令作用:rd = rs1 << shamt 左移(左侧丢掉,右侧补 0)
  • 注意:shamt(shift amount)会编码到原来 rs2 的位置,它是一个立即数,正好有 5 位
srli r 型
31 25 24 20 19 15 14 12 11 7 6 0
0000000 shamt rs1 101 rd 0010011
  • 指令格式:srli rd, rs1, shamt
  • 指令作用:rd = rs1 >> shamt 逻辑右移(左侧补 0,右侧丢掉)
  • 注意:shamt(shift amount)会编码到原来 rs2 的位置,它是一个立即数,正好有 5 位
srai r 型
31 25 24 20 19 15 14 12 11 7 6 0
0100000 shamt rs1 101 rd 0010011
  • 指令格式:srai rd, rs1, shamt
  • 指令作用:rd = rs1 >>> shamt 算数右移(左侧补符号位,右侧丢掉)
  • 注意:shamt(shift amount)会编码到原来 rs2 的位置,它是一个立即数,正好有 5 位

数据加载指令

lui U 型
31 12 11 7 6 0
imm[31:12] rd 0110111
  • 指令格式:lui imm
  • 指令作用:(load upper immediate)rd = imm << 12 将 imm 加载到 rd 的高 20 位
  • 注意:imm 不能超过 20 位,rd 以十六进制表示就是 imm 后接三个 0
auipc U 型
31 12 11 7 6 0
imm[31:12] rd 0010111
  • 指令格式:auipc rd
  • 指令作用:(add upper immediate with pc)rd = pc + imm << 12 将 imm 加载到高 20 位,然后加上 pc 值
  • 注意:常用来构建 pc 相对寻址的地址,imm 不能超过 20 位

控制流变化指令

jump 类无条件跳转指令

jal J 型
31 12 11 7 6 0
imm[20,10:1,11,19:12] rd 1101111
  • 指令格式:jal rd, imm
  • 指令作用:(jump and link)rd = pc+4, pc = pc+imm 即将当前指令下一条指令的地址存入 rd,然后相对跳转到 imm 处
  • 注意:imm 在汇编程序中一般用标号来指定,jal 可以跳到 ±1MiB 范围内的代码
jalr I 型
31 20 19 15 14 12 11 7 6 0
imm[11:0] rs1 000 rd 1100111
  • 指令格式:jalr rd, imm(rs1)
  • 指令作用:rd = pc+4, pc = (imm+rs1) & 0xFFFFFFFE 即最低位会被设为 0
  • 注意:可以实现任意位置跳转

branch 类条件跳转指令

beq B 型
31 25 24 20 19 15 14 12 11 7 6 0
imm[12,10:5] rs2 rs1 000 imm[4:1,11] 1100011
  • 指令格式:beq rs1, rs2, imm
  • 指令作用:(branch if equal)如果 rs1 == rs2,则 pc = pc+imm
  • 注意:可以跳转到 ±4KiB 范围内
bne B 型
31 25 24 20 19 15 14 12 11 7 6 0
imm[12,10:5] rs2 rs1 001 imm[4:1,11] 1100011
  • 指令格式:bne rs1, rs2, imm
  • 指令作用:(branch if not equal)如果 rs1 != rs2,则 pc = pc+imm
  • 注意:可以跳转到 ±4KiB 范围内
blt B 型
31 25 24 20 19 15 14 12 11 7 6 0
imm[12,10:5] rs2 rs1 100 imm[4:1,11] 1100011
  • 指令格式:blt rs1, rs2, imm
  • 指令作用:(branch if less than)如果 rs1 < rs2 则 pc = pc+imm
  • 注意:rs1 rs2 视为有符号数进行比较,可以跳转到 ±4KiB 范围内
bge B 型
31 25 24 20 19 15 14 12 11 7 6 0
imm[12,10:5] rs2 rs1 101 imm[4:1,11] 1100011
  • 指令格式:bge rs1, rs2, imm
  • 指令作用:(branch if greater than or equal)如果 rs1 >= rs2 则 pc = pc+imm
  • 注意:rs1 rs2 视为有符号数进行比较,可以跳转到 ±4KiB 范围内
bltu B 型
31 25 24 20 19 15 14 12 11 7 6 0
imm[12,10:5] rs2 rs1 110 imm[4:1,11] 1100011
  • 指令格式:bltu rs1, rs2, imm
  • 指令作用:(blt unsigned)如果 rs1 < rs2 则 pc = pc+imm
  • 注意:rs1 rs2 视为无符号数进行比较,可以跳转到 ±4KiB 范围内
bgeu B 型
31 25 24 20 19 15 14 12 11 7 6 0
imm[12,10:5] rs2 rs1 111 imm[4:1,11] 1100011
  • 指令格式:bgeu rs1, rs2, imm
  • 指令作用:(bge unsigned)如果 rs1 >= rs2 则 pc = pc+imm
  • 注意:rs1 rs2 视为无符号数进行比较,可以跳转到 ±4KiB 范围内

装载存储指令

load 类装载指令

lb I 型
31 20 19 15 14 12 11 7 6 0
imm[11:0] rs1 000 rd 0000011
  • 指令格式:lb rd, imm(rs1)
  • 指令作用:从 rs1 + imm 处内存读取一个字节到 rd 低八位,再进行符号扩展
lh I 型
31 20 19 15 14 12 11 7 6 0
imm[11:0] rs1 001 rd 0000011
  • 指令格式:lh rd, imm(rs1)
  • 指令作用:从 rs1+imm 处内存读取一个 16 位数到 rd 低 16 位,然后进行符号扩展
lw I 型
31 20 19 15 14 12 11 7 6 0
imm[11:0] rs1 010 rd 0000011
  • 指令格式:lw rd, imm(rs1)
  • 指令作用:从 rs1 + imm 处内存读取一个 32 位数到 rd 中
lbu I 型
31 20 19 15 14 12 11 7 6 0
imm[11:0] rs1 100 rd 0000011
  • 指令格式:lbu rd, imm(rs1)
  • 指令作用:从 rs1 + imm 处内存读取一个字节放到 rd 低 8 位,然后进行零扩展(高位全补 0)
lhu I 型
31 20 19 15 14 12 11 7 6 0
imm[11:0] rs1 101 rd 0000011
  • 指令格式:lhu rd, imm(rs1)
  • 指令作用:从 rs1 + imm 处内存读取 16 位数存入 rd,并进行零扩展(高 16 位全为 0)

store 类存储指令

sb S 型
31 25 24 20 19 15 14 12 11 7 6 0
imm[11:5] rs2 rs1 000 imm[4:0] 0100011
  • 指令格式:sb rs2, imm(rs1)
  • 指令作用:将 rs2 的低 8 位拷贝到 rs1 + imm 处内存中
sh S 型
31 25 24 20 19 15 14 12 11 7 6 0
imm[11:5] rs2 rs1 001 imm[4:0] 0100011
  • 指令格式:sh rs2, imm(rs1)
  • 指令作用:将 rs2 的低 16 位拷贝到 rs1 + imm 处内存中
sw S 型
31 25 24 20 19 15 14 12 11 7 6 0
imm[11:5] rs2 rs1 010 imm[4:0] 0100011
  • 指令格式:sw rs2, imm(rs1)
  • 指令作用:将 rs2(32 位)拷贝到 rs1 + imm 处内存中

汇编伪指令

前面的是 RV32I 的所有被编码的指令,下面是可以在 RV32I 汇编程序中写的伪指令,它们将被编译器编译为第二列中的实际指令

伪指令 实际指令 意义
la/lla rd, symbol auipc rd, delta[31 : 12] + delta[11]
addi rd, rd, delta[11:0]
加载绝对地址
delta = symbol - pc
l{b|h|w} rd, symbol auipc rd, delta[31 : 12] + delta[11]
l{b|h|w} rd, delta[11:0](rd)
加载全局变量
s{b|h|w} rd, symbol, rt auipc rt, delta[31 : 12] + delta[11]
s{b|h|w} rd, delta[11:0](rt)
保存全局变量
nop addi x0, x0, 0
li rd, imm ... 将立即数加载到 rd 中
mov rd, rs addi rd, rs, 0 从 rs 拷贝到 rd
not rd, rs xori rd, rs, -1 rd = ~rs 按位取反
neg rd, rs sub rd, x0, rs rd = -rs
seqz rd, rs sltiu rd, rs, 1 set rd if rs == 0
snez rd, rs sltu rd, x0, rs set rd if rs != 0
sltz rd, rs slt rd, rs, x0 set rd if rs < 0
sgtz rd, rs slt rd, x0, rs set rd if rs > 0
beqz rs, offset beq rs, x0, offset branch if rs == 0
bnez rs, offset bne rs, x0, offset branch if rs != 0
blez rs, offset bge x0, rs, offset branch if rs <= 0
bgez rs, offset bge rs, x0, offset branch if rs >= 0
bltz rs, offset blt rs, x0, offset branch if rs < 0
bgtz rs, offset blt x0, rs, offset branch if rs > 0
bgt rs, rt, offset blt rt, rs, offset branch if rs > rt
ble rs, rt, offset bge rt, rs, offset branch if rs <= rt
bgtu rs, rt, offset bltu rt, rs, offset branch if > unsigned
bleu rs, rt, offset bgeu rt, rs, offset branch if <= unsigned
j offset jal x0, offset 无条件跳转,不存返回地址
jal offset jal x1, offset 无条件跳转,返回地址存到 x1
jr rs jalr x0, 0(rs) 无条件跳转到 rs 位置,忽略返回地址
jalr rs jalr x1, 0(rs) 无条件跳转到 rs 位置,存返回地址
ret jalr x0, 0(x1) 通过返回地址 x1 返回
call offset auipc x1, offset[31 : 12] + offset[11]
jalr x1, offset[11:0](x1)
远调用
tail offset auipc x6, offset[31 : 12] + offset[11]
jalr x0, offset[11:0](x6)
忽略返回地址远调用

汇编代码

    .text
    .align 2
    .globl main
main:
    addi sp, sp, -16
    sw ra, 12(sp)
    lui a0, %hi(string1)
    addi a0, a0, %lo(string1)
    lui a1, %hi(string2)
    addi a1, a1, %lo(string2)
    call printf
    lw ra, 12(sp)
    addi sp, sp, 16
    li a0, 0
    ret

    .section .rodata
    .balign 4
string1:
    .string "Hello, %s!\n"
string2:
    .string "world"
  • .text:进入代码段
  • .align 2:代码段对齐到 2^2 字节
  • .globl main:声明全局标号 main
  • .section .rodata:进入 rodata 段
  • .balign 4:对齐数据段到 4 字节
  • .string ... 定义字符串

其它指令:

  • .data:进入数据段
  • .bss:进入 bss 段
  • .byte b1, b2, ..., bn:存放一些字节
  • .half w1, w2, ..., wn:存放一些半字(16 位)
  • .word w1, w2, ..., wn:存放一些字(32 位)

最后更新: 2022年6月15日 16:31:21
创建日期: 2022年5月29日 12:21:50
回到页面顶部