这是我对riscv-sodor处理器的学习过程。
1、 什么是riscv-sodor?
维基:https://github.com/ucb-bar/riscv-sodor/wiki
Github: https://github.com/ucb-bar/riscv-sodor
riscv-sodor处理器是教学处理器,相比于rocket-chip会更容易学习,可以用来学chisel & RISCV32指令 & cpu内部结构。
riscv-sodor有5个处理器的例子。分别是:
1)1阶段(基本上是ISA模拟器)
2) 2阶段(演示Chisel中的流水线操作)
3) 3阶段(使用顺序记忆;支持哈佛和普林斯顿版本)
4) 5阶段(可以在完全旁路或完全互锁之间切换)
5) 基于“总线”的微编码实现
(这部分说明先放这里,后面会写得多的话,会单独开一个目录。)
2、riscv-sodor-rv32_1stage
1) 先贴一张rv32_1stage的结构图:
2)然后看/riscv-sodor/src/rv32_1stage中的scala代码:
consts.scala cpath.scala package.scala top.scala core.scala dpath.scala tile.scala
有些scala代码是在/riscv-sodor/src/common中的,我们用到了再作说明。
3)我们一个个scala文件进行分析。
先进行说明的是cpath.scala:
package Sodor
{
import chisel3._
import chisel3.util._
import Common._
import Common.Instructions._
import Constants._
//这个是定义cpath到dpath的一些控制信号。
//pc_sel:用于告诉dpath模块pc下一步怎么变化
//op1_sel:操作数1,指定一些特定操作,可以看consts.scala,后面会对consts.scala进行说明
//op2_sel:c操作数2,指定一些特定操作,可以看consts.scala,后面会对consts.scala进行说明
//alu_fun:alu对应的操作类型
//wb_sel:是否进行回写操作
//rf_wen:写寄存器文件使能
//csr_cmd:该指令的操作是否为csr相关指令
//exception:该指令是否为异常/非法指令
class CtlToDatIo extends Bundle()
{
val stall = Output(Bool())
val pc_sel = Output(UInt(PC_4.getWidth.W))
val op1_sel = Output(UInt(OP1_X.getWidth.W))
val op2_sel = Output(UInt(OP2_X.getWidth.W))
val alu_fun = Output(UInt(ALU_X.getWidth.W))
val wb_sel = Output(UInt(WB_X.getWidth.W))
val rf_wen = Output(Bool())
val csr_cmd = Output(UInt(CSR.SZ))
val exception = Output(Bool())
}
//定义cpath模块的端口
//Flipped是反转的作用,将DebugCPath()中定义的端口反过来,并在cpath模块中命名为dcpath,即input在这边边ouput
//imem是cpath取指令的memory,MemPortIo()在/commo/memory.scala中定义,后面再说明吧,在这里知道它是声明了一片sram就可以了
//dmem是cpath出来数据的memory,MemPortIo()在/commo/memory.scala中定义,后面再说明吧,在这里知道它是声明了一片sram就可以了
//dat是在DatToCtlIo()中声明端口的反端口
//ctl是CtlToDatIo()的调用,CtlToDatIo()在上面已经作了说明,用法是:ctl.exception、ctl.csr_cmd,某些信号在rtl中能找到对应的名字
class CpathIo(implicit conf: SodorConfiguration) extends Bundle()
{
val dcpath = Flipped(new DebugCPath())
val imem = new MemPortIo(conf.xprlen)
val dmem = new MemPortIo(conf.xprlen)
val dat = Flipped(new DatToCtlIo())
val ctl = new CtlToDatIo()
override def cloneType = { new CpathIo().asInstanceOf[this.type] }
}
//这个是主体部分
class CtlPath(implicit conf: SodorConfiguration) extends Module
{
//先声明一个io的端口,调用的就是上面的CpathIo(),并赋随机值
val io = IO(new CpathIo())
io := DontCare
// ListLookup就是查找表,对输入的数据io.dat.inst进行查找,与指令的操作码进行比较,当符合相应指令时,将相应的控制信号赋给csignals,并不是将指令数据直接赋给控制信号,控制信号是有差异的,BR_N , OP1_X , OP2_X , ALU_X , WB_X , REN_0, MEN_0, M_X , MT_X, CSR.N代表的意义各不相同,具体的控制信号意义可以看consts.scala
val csignals =
ListLookup(io.dat.inst,
List(N, BR_N , OP1_X , OP2_X , ALU_X , WB_X , REN_0, MEN_0, M_X , MT_X, CSR.N),
Array( /* val | BR | op1 | op2 | ALU | wb | rf | mem | mem | mask | csr */
/* inst | type | sel | sel | fcn | sel | wen | en | wr | type | cmd */
LW -> List(Y, BR_N , OP1_RS1, OP2_IMI , ALU_ADD , WB_MEM, REN_1, MEN_1, M_XRD, MT_W, CSR.N),
LB -> List(Y, BR_N , OP1_RS1, OP2_IMI , ALU_ADD , WB_MEM, REN_1, MEN_1, M_XRD, MT_B, CSR.N),
LBU -> List(Y, BR_N , OP1_RS1, OP2_IMI , ALU_ADD , WB_MEM, REN_1, MEN_1, M_XRD, MT_BU, CSR.N),
LH -> List(Y, BR_N , OP1_RS1, OP2_IMI , ALU_ADD , WB_MEM, REN_1, MEN_1, M_XRD, MT_H, CSR.N),
LHU -> List(Y, BR_N , OP1_RS1, OP2_IMI , ALU_ADD , WB_MEM, REN_1, MEN_1, M_XRD, MT_HU, CSR.N),
SW -> List(Y, BR_N , OP1_RS1, OP2_IMS , ALU_ADD , WB_X , REN_0, MEN_1, M_XWR, MT_W, CSR.N),
SB -> List(Y, BR_N , OP1_RS1, OP2_IMS , ALU_ADD , WB_X , REN_0, MEN_1, M_XWR, MT_B, CSR.N),
SH -> List(Y, BR_N , OP1_RS1, OP2_IMS , ALU_ADD , WB_X , REN_0, MEN_1, M_XWR, MT_H, CSR.N),
AUIPC -> List(Y, BR_N , OP1_IMU, OP2_PC , ALU_ADD , WB_ALU, REN_1, MEN_0, M_X , MT_X, CSR.N),
LUI -> List(Y, BR_N , OP1_IMU, OP2_X , ALU_COPY1, WB_ALU, REN_1, MEN_0, M_X , MT_X, CSR.N),
ADDI -> List(Y, BR_N , OP1_RS1, OP2_IMI , ALU_ADD , WB_ALU, REN_1, MEN_0, M_X , MT_X, CSR.N),
ANDI -> List(Y, BR_N , OP1_RS1, OP2_IMI , ALU_AND , WB_ALU, REN_1, MEN_0, M_X , MT_X, CSR.N),
ORI -> List(Y, BR_N , OP1_RS1, OP2_IMI , ALU_OR , WB_ALU, REN_1, MEN_0, M_X , MT_X, CSR.N),
XORI -> List(Y, BR_N , OP1_RS1, OP2_IMI , ALU_XOR , WB_ALU, REN_1, MEN_0, M_X , MT_X, CSR.N),
SLTI -> List(Y, BR_N , OP1_RS1, OP2_IMI , ALU_SLT , WB_ALU, REN_1, MEN_0, M_X , MT_X, CSR.N),
SLTIU -> List(Y, BR_N , OP1_RS1, OP2_IMI , ALU_SLTU, WB_ALU, REN_1, MEN_0, M_X , MT_X, CSR.N),
SLLI -> List(Y, BR_N , OP1_RS1, OP2_IMI , ALU_SLL , WB_ALU, REN_1, MEN_0, M_X , MT_X, CSR.N),
SRAI -> List(Y, BR_N , OP1_RS1, OP2_IMI , ALU_SRA , WB_ALU, REN_1, MEN_0, M_X , MT_X, CSR.N),
SRLI -> List(Y, BR_N , OP1_RS1, OP2_IMI , ALU_SRL , WB_ALU, REN_1, MEN_0, M_X , MT_X, CSR.N),
SLL -> List(Y, BR_N , OP1_RS1, OP2_RS2 , ALU_SLL , WB_ALU, REN_1, MEN_0, M_X , MT_X, CSR.N),
ADD -> List(Y, BR_N , OP1_RS1, OP2_RS2 , ALU_ADD , WB_ALU, REN_1, MEN_0, M_X , MT_X, CSR.N),
SUB -> List(Y, BR_N , OP1_RS1, OP2_RS2 , ALU_SUB , WB_ALU, REN_1, MEN_0, M_X , MT_X, CSR.N),
SLT -> List(Y, BR_N , OP1_RS1, OP2_RS2 , ALU_SLT , WB_ALU, REN_1, MEN_0, M_X , MT_X, CSR.N),
SLTU -> List(Y, BR_N , OP1_RS1, OP2_RS2 , ALU_SLTU, WB_ALU, REN_1, MEN_0, M_X , MT_X, CSR.N),
AND -> List(Y, BR_N , OP1_RS1, OP2_RS2 , ALU_AND , WB_ALU, REN_1, MEN_0, M_X , MT_X, CSR.N),
OR -> List(Y, BR_N , OP1_RS1, OP2_RS2 , ALU_OR , WB_ALU, REN_1, MEN_0, M_X , MT_X, CSR.N),
XOR -> List(Y, BR_N , OP1_RS1, OP2_RS2 , ALU_XOR , WB_ALU, REN_1, MEN_0, M_X , MT_X, CSR.N),
SRA -> List(Y, BR_N , OP1_RS1, OP2_RS2 , ALU_SRA , WB_ALU, REN_1, MEN_0, M_X , MT_X, CSR.N),
SRL -> List(Y, BR_N , OP1_RS1, OP2_RS2 , ALU_SRL , WB_ALU, REN_1, MEN_0, M_X , MT_X, CSR.N),
JAL -> List(Y, BR_J , OP1_X , OP2_X , ALU_X , WB_PC4, REN_1, MEN_0, M_X , MT_X, CSR.N),
JALR -> List(Y, BR_JR , OP1_RS1, OP2_IMI , ALU_X , WB_PC4, REN_1, MEN_0, M_X , MT_X, CSR.N),
BEQ -> List(Y, BR_EQ , OP1_X , OP2_X , ALU_X , WB_X , REN_0, MEN_0, M_X , MT_X, CSR.N),
BNE -> List(Y, BR_NE , OP1_X , OP2_X , ALU_X , WB_X , REN_0, MEN_0, M_X , MT_X, CSR.N),
BGE -> List(Y, BR_GE , OP1_X , OP2_X , ALU_X , WB_X , REN_0, MEN_0, M_X , MT_X, CSR.N),
BGEU -> List(Y, BR_GEU, OP1_X , OP2_X , ALU_X , WB_X , REN_0, MEN_0, M_X , MT_X, CSR.N),
BLT -> List(Y, BR_LT , OP1_X , OP2_X , ALU_X , WB_X , REN_0, MEN_0, M_X , MT_X, CSR.N),
BLTU -> List(Y, BR_LTU, OP1_X , OP2_X , ALU_X , WB_X , REN_0, MEN_0, M_X , MT_X, CSR.N),
CSRRWI -> List(Y, BR_N , OP1_IMZ, OP2_X , ALU_COPY1, WB_CSR, REN_1, MEN_0, M_X , MT_X, CSR.W),
CSRRSI -> List(Y, BR_N , OP1_IMZ, OP2_X , ALU_COPY1, WB_CSR, REN_1, MEN_0, M_X , MT_X, CSR.S),
CSRRCI -> List(Y, BR_N , OP1_IMZ, OP2_X , ALU_COPY1, WB_CSR, REN_1, MEN_0, M_X , MT_X, CSR.C),
CSRRW -> List(Y, BR_N , OP1_RS1, OP2_X , ALU_COPY1, WB_CSR, REN_1, MEN_0, M_X , MT_X, CSR.W),
CSRRS -> List(Y, BR_N , OP1_RS1, OP2_X , ALU_COPY1, WB_CSR, REN_1, MEN_0, M_X , MT_X, CSR.S),
CSRRC -> List(Y, BR_N , OP1_RS1, OP2_X , ALU_COPY1, WB_CSR, REN_1, MEN_0, M_X , MT_X, CSR.C),
ECALL -> List(Y, BR_N , OP1_X , OP2_X , ALU_X , WB_X , REN_0, MEN_0, M_X , MT_X, CSR.I),
MRET -> List(Y, BR_N , OP1_X , OP2_X , ALU_X , WB_X , REN_0, MEN_0, M_X , MT_X, CSR.I),
DRET -> List(Y, BR_N , OP1_X , OP2_X , ALU_X , WB_X , REN_0, MEN_0, M_X , MT_X, CSR.I),
EBREAK -> List(Y, BR_N , OP1_X , OP2_X , ALU_X , WB_X , REN_0, MEN_0, M_X , MT_X, CSR.I),
WFI -> List(Y, BR_N , OP1_X , OP2_X , ALU_X , WB_X , REN_0, MEN_0, M_X , MT_X, CSR.N), // implemented as a NOP
FENCE_I -> List(Y, BR_N , OP1_X , OP2_X , ALU_X , WB_X , REN_0, MEN_0, M_X , MT_X, CSR.N),
FENCE -> List(Y, BR_N , OP1_X , OP2_X , ALU_X , WB_X , REN_0, MEN_1, M_X , MT_X, CSR.N)
// we are already sequentially consistent, so no need to honor the fence instruction
))
//将csignals中的值,逐一赋给下面的变量,cs_val_ins、cs_br_type、cs_op1_sel……
// Put these control signals into variables
val (cs_val_inst: Bool) :: cs_br_type :: cs_op1_sel :: cs_op2_sel :: cs0 = csignals
val cs_alu_fun :: cs_wb_sel :: (cs_rf_wen: Bool) :: cs1 = cs0
val (cs_mem_en: Bool) :: cs_mem_fcn :: cs_msk_sel :: cs_csr_cmd :: Nil = cs1
// 利用cs_br_type的新值和io.dat.br_x中的返回值判断下一个pc是否为跳转的情况,为什么要用上io.dat.br_x? 因为某些循环会用到上次的值,例如要循环100次,判断cs_br_type知道指令为跳转指令,但不知道循环了多少次,所以需要用io.dat.br_x来记录
// Branch Logic
val ctrl_pc_sel = Mux(io.dat.csr_eret ||
io.ctl.exception , PC_EXC,
Mux(cs_br_type === BR_N , PC_4,
Mux(cs_br_type === BR_NE , Mux(!io.dat.br_eq, PC_BR, PC_4),
Mux(cs_br_type === BR_EQ , Mux( io.dat.br_eq, PC_BR, PC_4),
Mux(cs_br_type === BR_GE , Mux(!io.dat.br_lt, PC_BR, PC_4),
Mux(cs_br_type === BR_GEU, Mux(!io.dat.br_ltu, PC_BR, PC_4),
Mux(cs_br_type === BR_LT , Mux( io.dat.br_lt, PC_BR, PC_4),
Mux(cs_br_type === BR_LTU, Mux( io.dat.br_ltu, PC_BR, PC_4),
Mux(cs_br_type === BR_J , PC_J,
Mux(cs_br_type === BR_JR , PC_JR,
PC_4))))))))))
//流水线暂停的标志位,当imem的数据没有准备好,或者正在进行dmem数据写入,且数据还没有完成写成功是,则stall信号为1,使流水线暂停
val stall = !io.imem.resp.valid || !((cs_mem_en && io.dmem.resp.valid) || !cs_mem_en)
//将指令解码的控制信号分别赋给相应的端口,准备好送给dpath
// Set the data-path control signals
io.ctl.stall := stall
io.ctl.pc_sel := ctrl_pc_sel
io.ctl.op1_sel := cs_op1_sel
io.ctl.op2_sel := cs_op2_sel
io.ctl.alu_fun := cs_alu_fun
io.ctl.wb_sel := cs_wb_sel
io.ctl.rf_wen := Mux(stall || io.ctl.exception, false.B, cs_rf_wen)
//如果是csr操作指令,则将指令进行解码,并将相应的数据送到dpath中
//rs1_addr = 指令数据的[19:15]
//csr_cmd:该指令为csr的set/clear操作
//csr_cmd:若csr_ren为1,则输出CSR.R,若为0,则输出cs_csr_cmd
//若stall为1,怎输出CSR.N(没有csr操作指令),若为0,则输出csr_cmd
// convert CSR instructions with raddr1 == 0 to read-only CSR commands
val rs1_addr = io.dat.inst(RS1_MSB, RS1_LSB)
val csr_ren = (cs_csr_cmd === CSR.S || cs_csr_cmd === CSR.C) && rs1_addr === 0.U
val csr_cmd = Mux(csr_ren, CSR.R, cs_csr_cmd)
io.ctl.csr_cmd := Mux(stall, CSR.N, csr_cmd)
//请求指令数据
// Memory Requests
io.imem.req.valid := true.B
io.imem.req.bits.fcn := M_XRD
io.imem.req.bits.typ := MT_WU
io.dmem.req.valid := cs_mem_en
io.dmem.req.bits.fcn := cs_mem_fcn
io.dmem.req.bits.typ := cs_msk_sel
// Exception Handling ---------------------
// We only need to check if the instruction is illegal (or unsupported)
// or if the CSR file wants us to be interrupted.
// Other exceptions are detected later in the pipeline by passing the
// instruction to the CSR File and letting it redirect the PC as it sees
// fit.
//若为非法指令/异常指令,则输出io.ctl.exception到dpath模块
io.ctl.exception := (!cs_val_inst && io.imem.resp.valid)
}
}