1.RISC-V简介

RISC-V(发音为“risk-five”)是一个基于精简指令集(RISC)原则的开源指令集架构(ISA)。
与大多数指令集相比,RISC-V指令集可以自由地用于任何目的,允许任何人设计、制造和销售RISC-V芯片和软件。虽然这不是第一个开源指令集,但它具有重要意义,因为其设计使其适用于现代计算设备(如仓库规模云计算机、高端移动电话和微小嵌入式系统)。设计者考虑到了这些用途中的性能与功率效率。该指令集还具有众多支持的软件,这解决了新指令集通常的弱点。

RISC-V的优势:

  • 完全开源
  • 架构简单
  • 易于移植操作系统
  • 模块化设计
  • 完整工具链

2.RISC-V的特点

  1. 没有立即数减法:RISC-V只提供立即数加法,不提供立即数减法。当需要使用立即数减法时,由编译器将立即数转化为负数,再使用加法。简化了ALU单元的设计。
  2. x0寄存器简化指令集:RISC-V规定x0寄存器始终为0,引入该寄存器后,很多特殊指令只需要用普通指令加上x0做操作数就能解决,指令的数量大大减少,处理器的解码电路也大大简化。
  3. 支持32位常量:在ARM处理器中,会将立即数表示不下的常量存在常量池中,再用PC相关的LDR指令加载到寄存器,RISC-V的常量完全使用指令拼接实现,不需要Load指令,节省了访问周期。RISC-V单条指令可以表示12位的有符号常量,超过12位使用lui和addi两条指令合成。
  4. 只有小于和大于等于:RISC-V的比较跳转指令只有blt和bge,即只有小于和大于等于。当需要大于和小于等于时,将参与比较的两个操作数位置交换来实现。
  5. 让编译器做更多的工作:编译的次数远小于执行的次数,因此尽量让处理器少做,编译器多做。

3.指令集

RISC-V指令集的命名以RV位前缀,然后是位宽,最后代表的是指令集的字母集合

RV[###][ABC...XYZ]

符号

说明

RV

RISC-V的缩写

[###]

用于标识处理器位宽,有32,64,128三种

[abc...xyz]

标识处理器支持的指令模块集合

 3.1RISC-V模块

名称

描述

 基础

RV32I

基本整数指令集,32寄存器位宽

RV32E

基本整数指令集,32寄存器位宽,16个寄存器,用于嵌入式

RV64I

基本整数指令集,64寄存器位宽

RV128I

基本整数指令集,64寄存器位宽

扩展

M

整数乘法和除法的标准扩展

A

原子指令的标准扩展

F

单精度浮点的标准扩展

D

双精度浮点的标准扩展

B

位操作的标准扩展

N

用户级中断的标准扩展

...等

更多详见手册

3.2通用寄存器模型 

寄存器

ABI名称

描述

保存者

X0

zero

硬件0

-

X1

ra

返回地址

调用者

X2

Sp

栈指针

被调用者

X3

gp

全局指针

-

X4

tp

线程指针

-

X5-7

t0-2

临时变量

调用者

X8

s0/sp

保存的寄存器/帧指针

被调用者

X9

s1

保存的寄存器

被调用者

X10-11

a0-1

函数入口参数/返回值

调用者

X12-17

a2-7

函数入口参数

调用者

X18-27

s2-11

保存的寄存器

被调用者

X28-31

t3-6

临时变量

调用者

f0-7

ft0-7

FP临时变量

调用者

f8-9

fs0-1

FP保存的寄存器

被调用者

f10-11

fa0-1

FP参数/返回值

调用者

f12-17

fa2-7

FP参数

调用者

f18-27

fs2-11

FP保存的寄存器

被调用者

f28-31

ft8-11

FP临时变量

调用者

对于RISC-V的子程序调用,编译器会尽可能使用寄存器进行参数传递,有8个整数寄存器a0-a7和8个浮点寄存器fa0-fa7可以用于参数传递。

只有1个或者2个函数调用的返回值时,浮点返回值通过浮点寄存器fa0、fa1返回,其他值通过整数寄存器a0和a1返回值;更多的返回值时,全部通过存储器堆栈返回:调用者负责分配返回值的存储器区域,并将区域指针作为第一个隐藏参数传递给被调用者。在标准RISC-V调用约定中,栈是向下增长并且栈指针总是对齐到16字节。

除了参数传递寄存器和返回值寄存器之外,7个整数寄存器t0-t6和12个浮点寄存器ft0-ft11是临时寄存器,它们在被调用的子程序中可以被子程序代码覆盖,如果调用者后面还有使用这些寄存器的数值,在调用者中必须自己先保存其值再调用子程序。12个整数寄存器s0-s11和12个浮点寄存器fs0-fs11在子程序退出时,需要保持其值与子程序调用前一样,因此如果被调用者(子程序)需要使用这些寄存器,则被调用者(子程序)必须先保存它们,然后在结束调用(子程序返回)前恢复其原值。