type
date
status
slug
summary
tags
category
password
icon
😀
RISC-V指令集是一种模块化的指令集,可以根据需求向实现中添加不同的指令集模块,但是其实我们只需要包含RV32I指令集就可以实现一个完整的CPU了。

前言

RISC-V是一种新兴的指令集架构,其实现目标是:
  • 完全地开放、自由;
  • suitable for direct native hardware implementation;
  • avoid “over-architecting” for a particular microarchitecture style or implementation technology;
  • 模块化与可扩展性:有一个基本的整数指令集,利用它就可以实现一个完备的处理器,同时可以在此基础上添加新的扩展;
  • 支持已修订的2008IEEE-754浮点数标准;
  • 32位和64位兼具;
  • 支持多核实现和多核高并行;
  • ……;

RISC-V硬件平台相关术语

RISC-V hardware platform

A RISC-V hardware platform can contain one or more RISC-V-compatible processing cores together with other non-RISC-V-compatible cores, fixed-function accelerators, various physical memory structures, I/O devices, and an interconnect structure to allow the components to communicate.

core

如果一个组成部分具有独立的取址单元,那它就是一个core

coprocessor

一个RISC-V core可能使用了额外的专用指令集扩展或者是一个coprocessor。这里,我们用coprocessor来代指一个存在于RISC-V core中的单元,它具有额外的一些架构状态和指令集扩展,相比于primary RISC-V instruction stream,它具有有限的自主性。

accelerator

我们使用accelerator来表示一个不可编程的固定功能的单元或者是一个能够自主运转的,且是为特定任务定制的core。在RISC-V系统中,我们期盼许多可编程的加速器都将是基于RISC-V的core,有着专用的指令集扩展或定制的协处理器。

RISC-V软件执行环境和Harts

Execution environment interface

一个RISC-V程序依赖于他所运行在的执行环境。一个RISC-V执行环境接口(EEI)定义了程序的初始状态,环境中harts的数量和类型,这包括被harts所支持的特权模式,内存和IO模块的可访问性和属性,所有执行在每一个hart上的所有的合法的指令,以及对任何发生在执行时的中断或异常的处理。对EEI举一些例子:Linux application binary interface(ABI),the RISC-V supervisor binary interface(SBI)。RISC-V执行环境的实现可以使纯硬件的、纯软件的或者是硬件和软件的结合。For example, opcode traps and software emulation can be used to implement functionality not provided in hardware.

Hart

从运行在给定的执行环境当中的软件的角度来看,一个hart是一个资源,这个资源能自主地在执行环境当中取址和执行RISC-V指令。在这一方面,一个hart表现得像一个硬件线程资源尽管是在一块真实的硬件上通过执行环境使用了时分复用。一些EEIs支持多于harts的创建和销毁,例如可以通过环境调用和fork新的harts。
执行环境负责确保每一个hart的最终进展。对于一个给定的hart,这个责任将被展示挂起,当这个hart正在使用一种显示等待一个事件的机制,such as the wait-for-interrupt instruction defined in Volume II of this specification;同时这个responsibility 会在hart结束的时候结束。
The important distinction between a hardware thread(hart) and a software thread context is that the software running inside an execution environment is not responsible for causing progress each of its harts; that is the responsibility of the outer execution environment. So the environment’s harts operate like hardware threads from the perspective of the software inside the execution environment.
在我的理解当中,hart是使用硬件来实现的一种并行化技术,而软件线程则是通过操作系统实现的。

RISC-V概述

RISC-V最大的特点就是,基化。也就是无论什么样的RISC-V实现,其都需要支持一个基ISA,这个基ISA就完全能够保证简单处理器功能的完整性,即RV32I,RV64I,RV64E以及RV128I。当然在很多的专业领域,我们往往需要更多更方便的、更高效的指令,这时候我们可以引入一种扩展ISA,将我们需要的一些新指令加入其中,并连通基ISA一同实现,就得到了我们需要的效果。
从这一点我们可以看出,RISC-V是模块化的,也可以说,RISC-V并不是整个指令集,而是很多个指令集的集合,而这种模块化,就很好的解决了x86等指令集的冗余问题。

内存

一个RISC-V hart都有一个单一的按字节寻址的空间为2^XLEN大小的内存。byte被定义位8bits,word被定义为32bits。内存地址空间是循环的,因此在2^XLEN - 1地址处的字节是与0地址处的字节相邻的。因此,内存地址运算会被硬件忽略溢出。
执行环境定义了硬件资源到hart的地址空间的映射。

基本指令长度编码

标准RISC-V编码系统是支持可变长度指令的,但是每一指令必须是16bits长度的一个parcels的倍数,parcels是16bits边界对齐的。
我们使用IALIGN来表示指令地址对齐约束,用ILEN来表示实现中支持的最大指令长度,当然它必须是IALIGN的倍数。

异常,陷入和中断

我们使用术语exception去表示与一条在hart当中的指令有关联的不寻常的条件发生。
我们使用术语interrupt来表示一个外部的异步事件,它可能导致一个RISC-V hart发生一个意料之外的控制转移。
我们使用术语trap来表示被一个异常或者中断引发的指向trap handler的控制转移。
这三种概念的定义、触发和处理等行为都是由EEI定义的。

RV32I Base Integer Instruction Set, Version 2.1

RV32I对构建一个编译目标来说是完全够的,其设计目的就是为了满足硬件平台的最小实现。RV32I总共包含40条独立的指令,使用RV32I可以模拟几乎所有的其他ISA扩展,除了A扩展(A扩展提供了额外的原子硬件指令,这在操作系统的并发中会涉及到)。

Programmers’s Model for Base Integer ISA

这里先对几个名词注释一下:
  • the unprivileged state: 非特权状态,即用户模式;
  • the machine-mode privileged: 机器模式特权,即内核模式;
RV32I的非特权整数寄存器包括x0-x31和pc总共33个32位寄存器,其中x0被硬编码为全0,其他的寄存器则可以被解释为布尔值、2的补码的有符号二进制整数和无符号二进制整数pc被用于存储当前指令的地址。
通常来讲,这些寄存器并没有专门用于栈指针和子程序返回地址的寄存器,即指令编码允许任何的寄存器用于这些目的。但是,标准软件调用规范则使用x1来保存一个调用的返回地址,用也可以用x5;使用x2来作为栈指针;除此之外,硬件可能会通过使用x1和x5寄存器来加速函数调用和返回。
notion image

基本指令格式

RV32I指令集有四种核心指令类型分别是R/I/S/U,其指令格式如下:
notion image
首先,所有的指令类型都是32位的,而且是需要4bytes对齐的,在使用无条件跳转时,如果使用了未对其的指令地址,则会对该条指令报告未对齐错误。
上述指令格式有一些细节需要注意,首先所有的寄存器标识位所在的位置都是一样的,因为对寄存器标识符的译码往往是具体电路实现中的关键路径,因此我们便将寄存器标识符位置固定,并付出了不得不跨域访问立即数的代价。其次,大多数的立即数都是符号扩展的,并且是从左手最大位开始,且其符号位位于最高位,用以简化电路的实现。

立即数编码变种

根据对立即数的处理的不同,我们又会得到两种指令格式的变体:B/J:
notion image
我们可以认为B类型是由S类型导出的,J类型是由U类型导出的,这种导出之后的不同,按道理来讲只是在语义上存在,但是为了使得导出类型和被导出类型的对立即数的译码处理有更多的重合,我们就适当调整了立即数片段的位置,当然还要保证最高位始终是符号位。

整数运算指令

Integer Register-Immediate Instructions

ADDI&SLTI[U]
notion image
notion image
ADDI指令是将11位立即数的32位符号扩展加到rs1寄存器的值上,算数溢出被忽略,结果存入rd寄存器。汇编指令格式类似如下指令:
ADDI rd,rs1,imm(伪指令:MV rd, rs1 ⇒ addi rd,rs1,0)
SLTI指令时set less than immediate的缩写,其语义是如果rs1中的值小于立即数的值,那么将rd设置为1,否则为0,两者按有符号数进行比较;SLTIU与之不同,是按无符号数进行比较的,其中立即数依旧是符号扩展,但是被视为无符号数。汇编指令格式类似如下:
SLTI[U] rd,rs1,imm(伪指令:SEQZ rd,rs ⇒ SLTIU rd,rs1,1)
ANDI&ORI&XORI
notion image
notion image
分别进行rs1和立即数的按位与或异或的运算,其汇编语法如下:
ANDI rd, rs1, imm(伪指令:NOT rd,rs ⇒ XORI rd,rs1,-1)
移位运算
notion image
这三条指令是I类型指令的一种特殊情况,唯一有问题的是SRAI指令的30位为什么是1?

两条U类型指令

notion image
LUI指令的全称为load upper immediate,它通常被用于构造一个32位的常量。LUI指令会将一个立即数的值的高20位放入寄存器rd的高20位中,并将低位都置0.
AUIPC指令的全称是add upper immediate to pc,它通常被用于构造一个pc相对地址。AUIPC会从一个20位的立即数构造一个32位的offset,并将低位都置0,将这个便宜量加到AUIPC指令的地址上(这里是当前pc吗?如果取址之后立马+4,那么基地址就不是AUIPC指令的地址了),并将结果放到rd中。
利用AUIPC指令,可以实现二指令序列来访问从PC开始的任意偏移量的控制流转移和数据访问。UIPC和JALR的组合能够将控制流转移到任意的PC相对地址处。
当前的PC值,也可以通过将U-immediate设置为0来获取。

Integer Register-Register Operations

notion image
需要注意一下移位指令的第二操作数是rs2寄存器的低5位减法是用rs1-rs2得到结果。
指令功能与上述I型指令类似,只是有一些伪指令需要说明一下:
SNEZ rd,rs ⇒ SLTU rd,x0,rs2

NOP 指令

notion image
NOP指令不会更改架构状态,除了更新pc的值并且更新一些适用的性能计数器。NOP指令是通过指令:ADDI x0,x0,0实现的。

控制转移指令

RV32I提供了两种控制转移指令:
  • 非条件跳转;
  • 条件分支;
RV32I中的条件转移指令没有可见的延迟槽。

Unconditional Jumps

JAL
notion image
JAL指令的全称是jump and link。JAL指令的立即数代表一个有符号的偏移量,而且其值是2的倍数(这里为什么要用2的倍数呢,为什么不用4的倍数呢??因为JAL可能还需要兼容其他指令集扩展,这里不详细说明了,但是如果采用2byte对齐,但是指令又是4byte对齐的,难道在实际的寻址过程中会浪费掉一位吗,因为为了达到跳转合理的目的地址的目标,我们当然需要向pc中加上一个4byte对齐的offset),通过跳转指令的地址和这个21位的有符号数,我们可以访问相对跳转地址±1MiB大小的地址范围。同时,JAL指令会将跳转指令的下一条指令的地址(PC+4)存储到rd寄存器当中。标准软件调用规范使用x1作为返回地址寄存器,且使用x5作为备用链接寄存器。
伪指令:J ⇒ JAL x0
JALR
notion image
JALR指令的全称是jump and link register,使用的是I-type编码,是一种间接跳转指令。需要注意的是,这里的offset并不是2的倍数。
目标地址通过将12位符号扩展立即数与rs1寄存器相加得到,并将结果的最低有效位置为0。同样跳转指令的下一指令地址被写入rd寄存器。
为了实现位独立位置编码,无条件跳转指令都是用PC相对寻址。同时,JALR指令可以通过而指令序列来实现一个32位绝对地址范围的跳转。

Conditional Branches

notion image
所有的条件分支指令都是B-type的,其中12bit的B-immediate被编码为有符号的2字节对齐的偏移量。经符号扩展后的偏移量加到分支指令的地址上,就得到了目的地址。
分支指令会对rs1和rs2两个操作数进行比较:
  • BEQ: rs1 == rs2
  • BNQ: rs1 != rs2
  • BLT: rs1 < rs2
  • BLTU: rs1 < rs2 unsigned
  • BGE: rs1 >= rs2
  • BGEU: rs1 >= rs2 unsigned
除此之外,其他伪指令都可以通过这些指令交换操作数来实现,比如:
  • BGT: 交换BLT的操作数;
  • BLE: 交换BGE的操作数;

Load and Store Instructions

RV32I是一个load-store架构,即只有load和store指令可以访问内存而一些算术指令智能访问处理器的寄存器。RV32I提供了一个按字节寻址的32位的地址空间,EEI会定义地址空间中的哪一部分是可以被指令访问的。
EEI也会定义内存系统是小端还是大端,在RISC-V中,endianness is byte-address invariant.以下是星火对这个概念的解释:
当我们说 "endianness is byte-address invariant" 时,我们是在强调,尽管字节序会影响多字节数据的解释和存储,但对于单个字节的访问和解释是不受字节序影响的。这意味着,如果你有一个指向某个内存地址的指针,并且你读取或写入该地址处的单个字节,那么这个操作的解释不会因系统的字节序而改变。
notion image
伪指令:LW,LH,LHU,LB,LBU; SW,SH,SB.

Memory Order instructions

notion image

Environment Call and Breakpoints

SYSTEM指令被用于访问系统功能,这些功能可能需要特权访问;被编码为I-type格式。可以被分为两类:
  • 原子地读、改、写CSRs的指令;
  • 所有其他的特权指令;
notion image
ebreak:环境断点,通过抛出断点异常的方式请求调试器。
ecall:环境调用,通过引发环境调用异常来请求执行环境。

伪指令补充

  • blez: 小于等于0
  • li: 加载立即数指令,一般通过lui和addi来实现,lui是加载高20位,addi是加载第12位;
  • JR x1 => JALR x0, x1, 0
  • RET => JALR x0, x1, 0
  • JALR x13 => JALR x1, x13, 0
How to Transform the Reality to A Game算法讲义