提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
RISC-V(读音“risk-five”)是一个新的指令集体系结构(ISA),它最初用于支持计算机体系结构研究和教学,该项目2010年始于加州大学伯克利分校。我大学不是学的计算机专业,学的是电气专业,但是怀着一颗探索世界的初心,自学了王爽老师的《汇编语言》之后,对cpu运行原理越来越感兴趣。尤其回想其最后用bochs启动自己写的调用bios中断程序,运行出现菜单的那一幕,仍然感到油然而生的成就感。工作之后没有太多业余时间,后来学习了雷思磊老师的《自己动手写cpu》,从搭建流水线架构的第一行代码起,到最后把一条一条指令的把指令添加到框架里,到运行程序,我学到了很多cpu 流水线,冒险,流水线控制的知识。后来结合着MIT的alpha21264 源码,学习了姚永斌老师的《超标量处理器设计》,学习了多线程乱序的实现。alpha21264 源码简洁明了,有助于初学者学习,每一个部件都是姚老师书中的完美实现。risc-v我接触的很早,刚开始公布的时候,就在自己家电脑里跑了一下rocket。后来好多年没有接触了,最近越来越火,又重新点燃了学习的兴趣。
看代码首先是了解思想,思想最终要,思想就是算法,最后希望我的专栏能够带你入门cpu设计
一、RV32I基本整数指令集
基本指令集中程序源模型如下:
有31个通用寄存器,一个0值寄存器和一个PC 指令地址寄存器。
基本指令格式如图所示。可以看到非常整齐,没有arm的那么多花花绕绕。mips也是非常整齐。
含有立即数的指令需要扩展成32位格式
二、解码模块control解析
1.引入库
package mini
import chisel3._
import chisel3.util.ListLookup
import freechips.rocketchip.config.Parameters
第1行 定义mini包,3-5行 用import 引用其他包,因为chisel本质是scala语言,需要统一引入chisel3,Parameters是可裁剪参数引用。参考chisel参数机制
2.定义单例对象control
object Control {
val Y = true.B
val N = false.B
// pc_sel
val PC_4 = 0.U(2.W)
val PC_ALU = 1.U(2.W)
val PC_0 = 2.U(2.W)
val PC_EPC = 3.U(2.W)
// A_sel
val A_XXX = 0.U(1.W)
val A_PC = 0.U(1.W)
val A_RS1 = 1.U(1.W)
// B_sel
val B_XXX = 0.U(1.W)
val B_IMM = 0.U(1.W)
val B_RS2 = 1.U(1.W)
// imm_sel
val IMM_X = 0.U(3.W)
val IMM_I = 1.U(3.W)
val IMM_S = 2.U(3.W)
val IMM_U = 3.U(3.W)
val IMM_J = 4.U(3.W)
val IMM_B = 5.U(3.W)
val IMM_Z = 6.U(3.W)
// br_type
val BR_XXX = 0.U(3.W)
val BR_LTU = 1.U(3.W)
val BR_LT = 2.U(3.W)
val BR_EQ = 3.U(3.W)
val BR_GEU = 4.U(3.W)
val BR_GE = 5.U(3.W)
val BR_NE = 6.U(3.W)
// st_type
val ST_XXX = 0.U(2.W)
val ST_SW = 1.U(2.W)
val ST_SH = 2.U(2.W)
val ST_SB = 3.U(2.W)
// ld_type
val LD_XXX = 0.U(3.W)
val LD_LW = 1.U(3.W)
val LD_LH = 2.U(3.W)
val LD_LB = 3.U(3.W)
val LD_LHU = 4.U(3.W)
val LD_LBU = 5.U(3.W)
// wb_sel
val WB_ALU = 0.U(2.W)
val WB_MEM = 1.U(2.W)
val WB_PC4 = 2.U(2.W)
val WB_CSR = 3.U(2.W)
import Instructions._
import ALU._
val default =
// kill wb_en illegal?
// pc_sel A_sel B_sel imm_sel alu_op br_type | st_type ld_type wb_sel | csr_cmd |
// | | | | | | | | | | | | |
List(PC_4, A_XXX, B_XXX, IMM_X, ALU_XXX , BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, N, CSR.N, Y)
val map = Array(
LUI -> List(PC_4 , A_PC, B_IMM, IMM_U, ALU_COPY_B, BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, Y, CSR.N, N),
AUIPC -> List(PC_4 , A_PC, B_IMM, IMM_U, ALU_ADD , BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, Y, CSR.N, N),
JAL -> List(PC_ALU, A_PC, B_IMM, IMM_J, ALU_ADD , BR_XXX, Y, ST_XXX, LD_XXX, WB_PC4, Y, CSR.N, N),
JALR -> List(PC_ALU, A_RS1, B_IMM, IMM_I, ALU_ADD , BR_XXX, Y, ST_XXX, LD_XXX, WB_PC4, Y, CSR.N, N),
BEQ -> List(PC_4 , A_PC, B_IMM, IMM_B, ALU_ADD , BR_EQ , N, ST_XXX, LD_XXX, WB_ALU, N, CSR.N, N),
BNE -> List(PC_4 , A_PC, B_IMM, IMM_B, ALU_ADD , BR_NE , N, ST_XXX, LD_XXX, WB_ALU, N, CSR.N, N),
BLT -> List(PC_4 , A_PC, B_IMM, IMM_B, ALU_ADD , BR_LT , N, ST_XXX, LD_XXX, WB_ALU, N, CSR.N, N),
BGE -> List(PC_4 , A_PC, B_IMM, IMM_B, ALU_ADD , BR_GE , N, ST_XXX, LD_XXX, WB_ALU, N, CSR.N, N),
BLTU -> List(PC_4 , A_PC, B_IMM, IMM_B, ALU_ADD , BR_LTU, N, ST_XXX, LD_XXX, WB_ALU, N, CSR.N, N),
BGEU -> List(PC_4 , A_PC, B_IMM, IMM_B, ALU_ADD , BR_GEU, N, ST_XXX, LD_XXX, WB_ALU, N, CSR.N, N),
LB -> List(PC_0 , A_RS1, B_IMM, IMM_I, ALU_ADD , BR_XXX, Y, ST_XXX, LD_LB , WB_MEM, Y, CSR.N, N),
LH -> List(PC_0 , A_RS1, B_IMM, IMM_I, ALU_ADD , BR_XXX, Y, ST_XXX, LD_LH , WB_MEM, Y, CSR.N, N),
LW -> List(PC_0 , A_RS1, B_IMM, IMM_I, ALU_ADD , BR_XXX, Y, ST_XXX, LD_LW , WB_MEM, Y, CSR.N, N),
LBU -> List(PC_0 , A_RS1, B_IMM, IMM_I, ALU_ADD , BR_XXX, Y, ST_XXX, LD_LBU, WB_MEM, Y, CSR.N, N),
LHU -> List(PC_0 , A_RS1, B_IMM, IMM_I, ALU_ADD , BR_XXX, Y, ST_XXX, LD_LHU, WB_MEM, Y, CSR.N, N),
SB -> List(PC_4 , A_RS1, B_IMM, IMM_S, ALU_ADD , BR_XXX, N, ST_SB , LD_XXX, WB_ALU, N, CSR.N, N),
SH -> List(PC_4 , A_RS1, B_IMM, IMM_S, ALU_ADD , BR_XXX, N, ST_SH , LD_XXX, WB_ALU, N, CSR.N, N),
SW -> List(PC_4 , A_RS1, B_IMM, IMM_S, ALU_ADD , BR_XXX, N, ST_SW , LD_XXX, WB_ALU, N, CSR.N, N),
ADDI -> List(PC_4 , A_RS1, B_IMM, IMM_I, ALU_ADD , BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, Y, CSR.N, N),
SLTI -> List(PC_4 , A_RS1, B_IMM, IMM_I, ALU_SLT , BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, Y, CSR.N, N),
SLTIU -> List(PC_4 , A_RS1, B_IMM, IMM_I, ALU_SLTU , BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, Y, CSR.N, N),
XORI -> List(PC_4 , A_RS1, B_IMM, IMM_I, ALU_XOR , BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, Y, CSR.N, N),
ORI -> List(PC_4 , A_RS1, B_IMM, IMM_I, ALU_OR , BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, Y, CSR.N, N),
ANDI -> List(PC_4 , A_RS1, B_IMM, IMM_I, ALU_AND , BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, Y, CSR.N, N),
SLLI -> List(PC_4 , A_RS1, B_IMM, IMM_I, ALU_SLL , BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, Y, CSR.N, N),
SRLI -> List(PC_4 , A_RS1, B_IMM, IMM_I, ALU_SRL , BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, Y, CSR.N, N),
SRAI -> List(PC_4 , A_RS1, B_IMM, IMM_I, ALU_SRA , BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, Y, CSR.N, N),
ADD -> List(PC_4 , A_RS1, B_RS2, IMM_X, ALU_ADD , BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, Y, CSR.N, N),
SUB -> List(PC_4 , A_RS1, B_RS2, IMM_X, ALU_SUB , BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, Y, CSR.N, N),
SLL -> List(PC_4 , A_RS1, B_RS2, IMM_X, ALU_SLL , BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, Y, CSR.N, N),
SLT -> List(PC_4 , A_RS1, B_RS2, IMM_X, ALU_SLT , BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, Y, CSR.N, N),
SLTU -> List(PC_4 , A_RS1, B_RS2, IMM_X, ALU_SLTU , BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, Y, CSR.N, N),
XOR -> List(PC_4 , A_RS1, B_RS2, IMM_X, ALU_XOR , BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, Y, CSR.N, N),
SRL -> List(PC_4 , A_RS1, B_RS2, IMM_X, ALU_SRL , BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, Y, CSR.N, N),
SRA -> List(PC_4 , A_RS1, B_RS2, IMM_X, ALU_SRA , BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, Y, CSR.N, N),
OR -> List(PC_4 , A_RS1, B_RS2, IMM_X, ALU_OR , BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, Y, CSR.N, N),
AND -> List(PC_4 , A_RS1, B_RS2, IMM_X, ALU_AND , BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, Y, CSR.N, N),
FENCE -> List(PC_4 , A_XXX, B_XXX, IMM_X, ALU_XXX , BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, N, CSR.N, N),
FENCEI-> List(PC_0 , A_XXX, B_XXX, IMM_X, ALU_XXX , BR_XXX, Y, ST_XXX, LD_XXX, WB_ALU, N, CSR.N, N),
CSRRW -> List(PC_0 , A_RS1, B_XXX, IMM_X, ALU_COPY_A, BR_XXX, Y, ST_XXX, LD_XXX, WB_CSR, Y, CSR.W, N),
CSRRS -> List(PC_0 , A_RS1, B_XXX, IMM_X, ALU_COPY_A, BR_XXX, Y, ST_XXX, LD_XXX, WB_CSR, Y, CSR.S, N),
CSRRC -> List(PC_0 , A_RS1, B_XXX, IMM_X, ALU_COPY_A, BR_XXX, Y, ST_XXX, LD_XXX, WB_CSR, Y, CSR.C, N),
CSRRWI-> List(PC_0 , A_XXX, B_XXX, IMM_Z, ALU_XXX , BR_XXX, Y, ST_XXX, LD_XXX, WB_CSR, Y, CSR.W, N),
CSRRSI-> List(PC_0 , A_XXX, B_XXX, IMM_Z, ALU_XXX , BR_XXX, Y, ST_XXX, LD_XXX, WB_CSR, Y, CSR.S, N),
CSRRCI-> List(PC_0 , A_XXX, B_XXX, IMM_Z, ALU_XXX , BR_XXX, Y, ST_XXX, LD_XXX, WB_CSR, Y, CSR.C, N),
ECALL -> List(PC_4 , A_XXX, B_XXX, IMM_X, ALU_XXX , BR_XXX, N, ST_XXX, LD_XXX, WB_CSR, N, CSR.P, N),
EBREAK-> List(PC_4 , A_XXX, B_XXX, IMM_X, ALU_XXX , BR_XXX, N, ST_XXX, LD_XXX, WB_CSR, N, CSR.P, N),
ERET -> List(PC_EPC, A_XXX, B_XXX, IMM_X, ALU_XXX , BR_XXX, Y, ST_XXX, LD_XXX, WB_CSR, N, CSR.P, N),
WFI -> List(PC_4 , A_XXX, B_XXX, IMM_X, ALU_XXX , BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, N, CSR.N, N))
}
obj单例对象通常用于定义同名类的常量
Y、N是用于流水线控制,可以看到Load 跳转 状态寄存器指令需要清空流水线。因为这些指令导致PC 地址发生变化。
pc_sel用于控制 PC 指令地址在下个时钟的地址变化,PC_0代表流水线暂停,指令执行需要多个周期,这时候PC地址不变,PC_4代表指令需要一个周期,PC_ALU代表跳转指令,下一条指令地址发生跳转,对于简单的流水线cpu,没有bpu单元,跳转指令需要通过ALU 计算。PC_EPC代表中断返回指令地址。
A_sel和B_sel,以及imm_sel用于选择ALU的两个输入,ALU部件有两个输入接口,详细后续介绍。
后面br_type st_type ld_type 分别用于区分3种指令类型,
wb_sel用于写回寄存器时候,根据指令类型 写的数据来源。
defult 代表nop cpu什么不做的时候,各个部件的操作。
map 用于每一条指令,流水线中各个部件需要进行的动作,这些动作相互配合,最终完成一条指令的执行。
3.对外接口ControlSignals
class ControlSignals(implicit p: Parameters) extends CoreBundle()(p) {
val inst = Input(UInt(xlen.W))
val pc_sel = Output(UInt(2.W))
val inst_kill = Output(Bool())
val A_sel = Output(UInt(1.W))
val B_sel = Output(UInt(1.W))
val imm_sel = Output(UInt(3.W))
val alu_op = Output(UInt(4.W))
val br_type = Output(UInt(3.W))
val st_type = Output(UInt(2.W))
val ld_type = Output(UInt(3.W))
val wb_sel = Output(UInt(2.W))
val wb_en = Output(Bool())
val csr_cmd = Output(UInt(3.W))
val illegal = Output(Bool())
}
上面比较简单,就是定义对外的接口,p引用config的参数
4、定义Control模块
class Control(implicit p: Parameters) extends Module {
val io = IO(new ControlSignals)
val ctrlSignals = ListLookup(io.inst, Control.default, Control.map)
// Control signals for Fetch
io.pc_sel := ctrlSignals(0)
io.inst_kill := ctrlSignals(6).toBool
// Control signals for Execute
io.A_sel := ctrlSignals(1)
io.B_sel := ctrlSignals(2)
io.imm_sel := ctrlSignals(3)
io.alu_op := ctrlSignals(4)
io.br_type := ctrlSignals(5)
io.st_type := ctrlSignals(7)
// Control signals for Write Back
io.ld_type := ctrlSignals(8)
io.wb_sel := ctrlSignals(9)
io.wb_en := ctrlSignals(10).toBool
io.csr_cmd := ctrlSignals(11)
io.illegal := ctrlSignals(12)
}
上面的比较简单,首先定义接口部分,输入是inst指令,通过ListLoop 硬件模型针对不同的指令选择出对应的控制信号。最后把对应的信号赋值给输出接口。模块中没有寄存器,就有一个选择逻辑实现。
总结
上面对于具体指令的控制信号分析比较简单,后续可以根据指令手册具体分析控制信号的设置。