当前位置: 代码迷 >> 综合 >> 从零开始实现一个基于RISC-V的流水线处理器 (1) RISC-V指令集架构
  详细解决方案

从零开始实现一个基于RISC-V的流水线处理器 (1) RISC-V指令集架构

热度:28   发布时间:2024-02-08 10:51:23.0

目录

  • 基于RISC-V的流水线处理器
    • RISC-V指令集
    • RV32I
      • R-Type
      • I-Type
      • J-Type
      • B-Type
      • Load & Store
    • 总结
    • 后记

基于RISC-V的流水线处理器

RISC-V是一个基于精简指令集原则的开源指令集架构。它由加州大学伯克利分校的David Patterson教授领导下的小组完成,至今已成为RISC处理器中一股强劲的新生力量。

至今github上已有多款优秀的基于RISC-V指令集的微处理器项目被发布,如蜂鸟E203RocketChip等,但它们所涉及的知识层级较深,初学者很难迅速上手。而CSDN上也没有找到类似的RISC-V处理器的项目,因此笔者决定从零开始,由基础指令集做起,实现一款基于RISC-V指令集的微处理器。

RISC-V指令集

实现一款处理器,首先也是最重要的就是要确定处理器的指令集架构。

RISC-V指令集可以分为以下几个子集:

  • RV32I:基本整数集,包括整数计算指令,LOAD/STORE,以及控制指令。RV32I拥有32位寻址空间,32个32位寄存器。
  • RV32E:指令与RV32I相同,但是寄存器数量变为16个,用于嵌入式环境。
  • RV64I:整数指令,拥有64位寻址空间,32个64位寄存器。
  • RV128I:整数指令,拥有128位寻址空间,32个128位寄存器。

上述子集是RISC-V的基本指令集。在实际设计中,我们可以根据需要加入如下的扩展指令集:

  1. M:标准乘法和除法扩展,增加了乘法和除法的指令,并把结果保存在整数寄存器。
  2. A:标准原子指令扩展,增加了原子的读,修改,以及写存储器的指令。
  3. F:标准单精度浮点扩展,增加了浮点寄存器,单精度计算指令,以及单精度的LOAD/STORE指令。
  4. D:标准双精度浮点扩展,同样增加了浮点寄存器,且增加了双精度计算指令,双精度的LOAD/STORE指令。

上述均为RISC-V基金会认证的扩展指令集,此外还有V/P/T等处于草稿修改阶段的扩展指令集,这里就不再赘述了。

RV32I

在本次设计中,我们要实现的是基于最基本的整数指令集——RV32I的微处理器。就让我们从RV32I的具体指令格式看起。
RV32I的基本指令格式
上图展示了RV32I的基本指令格式。为了简化译码过程,源寄存器(rs1 和 rs2)和目标寄存器(rd)在RISC-V ISA 的所有指令格式中的位置保持一致。立即数被压缩在指令中最左边的可用位,并已经分配好,从而降低硬件复杂度。特别的,对于所有的立即数,指令的 31 位总是符号位,这样可以加速符号扩展电路。

下面我们将对各类型的指令进行具体的解释。

R-Type

R-Type Instructions

R-Type的指令为寄存器-寄存器指令,其格式如上图所示。R-Type指令的特点是2个源操作数和目标操作数对象都是寄存器,在实际执行过程中需要经历寄存器的写回。

Instruction Description Format Comment
ADD ADD rd, rs1, rs2 rd = rs1 + rs2,忽略溢出,保留低32bit
SUB SUB rd, rs1, rs2 rd = rs1 - rs2,忽略溢出,保留低32bit
AND AND rd, rs1, rs2 rd = rs1 & rs2,按位与
OR OR rd, rs1, rs2 rd = rs1 | rs2,按位或
XOR XOR rd, rs1, rs2 rd = rs1 ^ rs2, 按位异或
SLT set less than SLT rd, rs1, rs2 if (rs1 < rs2) rd = 1 else rd = 0
SLTU set less than unsigned SLTU rd, rs1, rs2 if((unsigned)rs1 < (unsigned)rs2) rd = 1 else rd = 0
SLL shift left logical SLL rd, rs1, rs2 rd = rs1 << rs2 [4 : 0],逻辑左移,低位补零,rs2的lower 5 bits作为偏移量
SRL shift right logical SRL rd, rs1, rs2 rd = rs1 >> rs2 [4 : 0],逻辑右移,高位补零,rs2的lower 5 bits作为偏移量
SRA shift rignt arithmetric SRA rd, rs1, rs2 rd = rs1 >> rs2 [4 : 0],算数右移,高位补符号位,rs2的lower 5 bits作为偏移量
NOP no operation NOP 不进行任何操作,相当于ADDI x0, x0, 0

I-Type

I-Type Instructions
I-Type的指令为寄存器-立即数指令,其指令格式如上图所示。R-Type指令中rs2和funct7的位置在I-Type指令中被imm替代,指令仅靠funct3来确定具体指令类型。
imm是一个12位的立即数,在与32位的寄存器进行逻辑运算时必须进行符号位扩展。

Instruction Description Format Comment
ADDI add immediate ADD rd, rs1, imm rd = rs1 + (sign-extended) imm,忽略溢出,保留低32bit
ANDI and immediate AND rd, rs1, imm rd = rs1 & (sign-extended) imm,按位与
ORI or immediate OR rd, rs1, imm rd = rs1 | (sign-extended) imm,按位或
XORI xor immediate XOR rd, rs1, imm rd = rs1 ^ (sign-extended) imm, 按位异或
SLTI set less than immediate SLT rd, rs1, imm if (rs1 < (sign-extended) imm) rd = 1 else rd = 0
SLTIU set less than unsigned immediate SLTU rd, rs1, imm if((unsigned)rs1 < (unsigned)imm) rd = 1 else rd = 0

对于移位指令,使用立即数的低5位作为偏移量,高7位也作为判断具体指令的操作码,如下图所示。
imm

Instruction Description Format Comment
SLLI shift left logical SLL rd, rs1, imm rd = rs1 << rs2 [4 : 0],逻辑左移,低位补零
SRLI shift right logical SRL rd, rs1, imm rd = rs1 >> rs2 [4 : 0],逻辑右移,高位补零
SRAI shift rignt arithmetric SRA rd, rs1, imm rd = rs1 >> rs2 [4 : 0],算数右移,高位补符号位

J-Type

J-Type指令为无条件跳转指令,主要功能是更改PC的指向地址,让处理器在下一个时钟上升沿在指定的地址中取指令。
JAL

JAL的指令格式如上图所示。对于JAL,使用一个20位的有符号立即数作为偏移量,目标地址为pc + offset,并把原pc + 4的地址存入rd。
JALR
JALR的指令格式如上图所示。对于JALR,其使用的指令格式是I-Type的指令格式,目标地址为pc + offset后把LSB置为0后的结果,并把原pc + 4的地址存入rd。
由此,我们可以写出下表的指令。

Instruction Description Format Comment
JAL jump and link JAL rd, offset pc += (sign-extended) offset, rd = pc + 4
JALR jump and link register JALR rd, rs1, offset pc = [rs + (sign-extended) offset] &~ 1, rd = pc + 4
JR jump register JR rs1 pc = rs1

B-Type

B-Type

B-Type指令为条件分支指令,仅在满足条件的情况下进行跳转,跳转的偏移量由一个12位的立即数给出。其指令格式如上图所示。

Instruction Description Format Comment
BEQ branch equal BEQ rs1, rs2, offset if (rs1 == rs2) pc += (sign-extended) offset
BNE branch not equal BNE rs1, rs2, offset if (rs1 != rs2) pc += (sign-extended) offset
BLT branch less than BLT rs1, rs2, offset if (rs1 < rs2) pc += (sign-extended) offset
BGE branch bigger than or equal BGE rs1, rs2, offset if (rs1 >= rs2) pc += (sign-extended) offset
BGEU branch bigger than or equal unsigned BGEU rs1, rs2, offset if((unsigned) rs1 >= (unsigned) rs2) pc += (sign-extended) offset

Load & Store

在RV32I中,只有Load & Store指令拥有访问内存的权限。RV32I提供32-bit的寻址空间。
LOAD & STORE
上图展示了Load & Store指令的具体格式。可以注意到,LOAD指令使用的是I-Type指令的格式,而STORE指令使用的是S-Type指令的格式。

Load指令和Store指令的目标地址由rs1 + 符号位扩展的偏移量给出。当指令为LH/LB时,从内存的指定地址中取低16位/低8位,进行符号位扩展后存入rd。而当指令为LW时,直接从内存中取4字节的数据存入rd。

Instruction Description Format Comment
LW load word LW rd, rs1, offset rd = mem[rs1 + (sign-extended) offset] [31:0]
LH load half LH rd, rs1, offset rd = (sign-extended) mem[rs1 + (sign-extend) offset)] [15:0]
LB load byte LB rd, rs1, offset rd = (sign-extended) mem[rs1 + (sign-extend) offset)] [7:0]
LBU load byte unsigned LBU rd, rs1, offset rd = (zero-extended) mem[rs1 + (sign-extend) offset)] [7:0]
SW store word SW rs1, rs2, offset mem[rs1 + (sign-extend) offset)] = rs2

总结

至此,RV32I中基本指令的说明告一段落。

然而,这些说明并不完整——RV32I中还有FENCE指令、Environment Call and Breakpoints指令、HINT指令等尚未提及,这些指令对于本次的设计来说过于复杂,由于本设计完成的处理器仅包括基本的运算及存储功能,因此在这里就将对这些指令的描述略去了。感兴趣的读者可以通过这个链接下载RISC-V的中文手册,或自行下载英文原版手册,对其进行深入阅读。

后记

RISC-V作为一个新兴的RISC架构,在具体的实现中博采众长,真正做到了对其他早期RISC架构的“取其精华,去其糟粕”。例如,RISC-V取消了MIPS-32 ISA中的延迟分支,单纯依靠现代已经相当发达的硬件预测器预测分支结果,实现了架构和具体实现的分离,使得指令集和芯片具体实现的分离成为可能。

芯片商可以采用统一的、免费的开放指令集,但各个厂商可以有各自的内部模块实现,并可以申请专利予以保护。这样既可以构建同一个软件生态系统,又保持了芯片企业之间的独立性。

对于笔者而言,RISC-V和MIPS的区别不仅在于指令中各元素位置的变化,更在于其精心设计的、称得上优雅的具体指令集架构和强大的可扩展性。RV32I中在rs1和rd之间插入一个3 bit的funct,在I-Type的指令中使用12位而非16位的立即数,想必也是从可扩展性的角度考虑得来的结果。

笔者同样作为一个RISC-V的初学者,对于指令集的理解难免有纰漏或错误的地方,欢迎大家对笔者的文章进行批评指正,也欢迎大家与笔者进行交流。

  相关解决方案