Skip to content

RISC-V

基础知识点

  • riscv 的核心是 RV32I基础ISA, 是永久不会改变的
  • riscv 是模块化的,它的模块化体现在可选的标准扩展,riscv编译器需要知道当前硬件包含的扩展,把代表扩展的字母加到指令集名称之后作为指示例如:RV32IMFD.这样就可以很高效,不需要支持浮点就不支持,不需要支持向量就不支持,达到定制化的目的,不会像增量ISA那样所有的指令都是必选的,不要都不行
  • riscv不提供特殊的堆栈指令,即没有push和pop
  • riscv选择小端字节序(高字节高地址,低字节低地址)

指令集

RISCV指令集在设计上的追求

  • 成本:指令集足够精简,那么芯片面积就很可能小,成本就会低
  • 简洁:开发验证成本就会降低
  • 性能:期望利用高主频,低单指令周期来弥补RISC指令集缺陷
  • 架构和具体实现分离:这个没太明白!
  • 提升空间:保留操作码以做扩展,在专用领域用定制指令来解决问题
  • 程序大小:肯定了压缩指令,但觉得变长指令没有意义
  • 易于编程/编译/链接:提供32个寄存器,这样编译器可用的寄存器就多了,并且指令操作数仅能在寄存器中,相对简单

RISCV指令集的特点

  • RISCV指令只有6种格式,均为32位指令,精简的指令格式意味着精简的解码部件
  • 指令提供三个寄存器操作数
  • 所有指令中要读写的寄存器操作符都在同一位置,还是解码方便
  • 符号位总是在指令最高位,解码时序并行度高

ABI

ABI是指寄存器的二进制接口名称,比如说RV32I, 寄存器X0 ABI接口名为zero, X1寄存器ABI接口名为RA,ABI是人为规定了寄存器应该如何使用,编译器和线性汇编都应该遵守的接口约定

汇编器(例如gnu的gas)

汇编器的作用不仅仅是从处理器能够理解的指令产生目标代码,还能翻译一些扩展指令,这些指令对汇编程序员或者编译器的编写者来说通常很有用。这类指令在巧妙配置常规指令的基础上实现,称为伪指令Intrinsic Function

大多数的 RISC-V 伪指令依赖于 x0

汇编指示符:是汇编器的命令,具有告诉汇编器代码和数据的位置、指定程序中使用的特定代码和数据常量等作用

  • .text:进入代码段
  • .align 2:后续代码按4字节对齐
  • .globl main:声明全局符号“main”
  • .section .rodata:进入只读数据段
  • .balign 4:数据段按 4 字节对齐
  • .string “”:创建空字符结尾的字符串

riscv汇编器指令及汇编手册请参考riscv-asm.md

汇编代码举例

对riscv reader 中的hello world示例再做一次补充

c 代码如下

#include <stdio.h>
int main()
{
    printf("Hello, %s\n", "world");
    return 0;
}

对应汇编代码如下,该汇编和文档中的有些出入,该汇编是通过gcc编译出来的汇编

    .file   "test.c"
    .option nopic  #nopic是什么意思?暂时还没有查到
    .text          #代码段起始
    #进入只读数据段,下面存放了只读数据,如果
    #下面没有只读数据,则仍认为是代码段,这是和直接使用.rodata的区别
    .section    .rodata
    .align  2
.LC0:
    .string "world"
    .align  2
.LC1:
    .string "Hello, %s\n"
    .text
    .align  1 #为什么这里会是2字节对齐而不是4字节对齐?
    .globl  main
    .type   main, @function
main:
    addi    sp,sp,-16 #开辟16字节栈空间
    sw  ra,12(sp) #先进行一次返回地址压栈,做为整个程序返回地址的保留
    sw  s0,8(sp)  #保存寄存器0压栈
    addi    s0,sp,16 # s0保存了栈顶
    lui a5,%hi(.LC0)
    addi    a1,a5,%lo(.LC0)
    lui a5,%hi(.LC1)
    addi    a0,a5,%lo(.LC1)
    call    printf
    li  a5,0
    mv  a0,a5
    lw  ra,12(sp)
    lw  s0,8(sp)
    addi    sp,sp,16
    jr  ra
    .size   main, .-main
    .ident  "GCC: (GNU) 8.3.0"

Intrinsic-Function

大多数的函数是在库中,Intrinsic Function却内嵌在编译器中。Intrinsic Function作为内联函数,直接在调用的地方插入代码,即避免了函数调用的额外开销,又能够使用比较高效的机器指令对该函数进行优化。优化器(Optimizer)内置的一些Intrinsic Function行为信息,可以对Intrinsic进行一些不适用于内联汇编的优化,所以通常来说Intrinsic Function要比等效的内联汇编(inline assembly)代码快。优化器能够根据不同的上下文环境对Intrinsic Function进行调整,例如:以不同的指令展开Intrinsic Function,将buffer存放在合适的寄存器等

转载自SSE指令集学习:Compiler Intrinsic

区分exception,trap,interrupt

exception, trap, interrupt概念容易混淆,特别是exception和trap。这这里明确的区分一下

We use the term exception to refer to an unusual condition occurring at run time associated with an instruction in the current RISC-V hart. We use the term interrupt to refer to an external asynchronous event that may cause a RISC-V hart to experience an unexpected transfer of control. We use the term trap to refer to the transfer of control to a trap handler caused by either an exception or an interrupt.

但看这个官方文档中的描述,还是很难直观的区分exception和trap。从字面意思上看,exception更像是一个状态,trap更像是一个过程,而trap这个过程可能是由于exception或者interrupt触发。

trap可以分为四类

  • Contained Trap 比如说在用户模式或者特权模式下,使用ecall指令
  • Requested Trap 比如system call, 正常的执行流程可能会因为这个调用而退出,也有可能恢复(这个例子似乎还是太抽象)
  • Invisible Trap 比如emulating missing instructions或者handling non-resident page faults(这种应该是软件无感的!?)
  • Fatal Trap failing a virtual-memory page-protection check or allowing a watchdog timer to expire(这种算是致命的吗?)

开源cpu

目前riscv有不少的完全开源的cpu设计

  • SiFive E31, E51, E54 网上资料不少,官网访问速度较慢,但是有不少论坛会有相关资料
  • ibex, 好评度很高,github上有代码托管,微架构介绍很详细
  • SweRV EH1, 好评度很高,还没具体看,看简介性能应该不错。
  • BOOM, 纯正Berkeley血统,还没看