Contents

Rocket RISC-V processor - Adding a custom CSR, hardware point of view

Introduction

In the context of a research project related to RIMI [1], we need to add a new CSR (Control & Status Register) in the Rocket processor [2] in order to store new security settings. There are obviously modifications to do in the hardware description of the processor. This blog article tries to sum up what needs to be done.

Hardware stack of the Rocket Chip

The first difficulty with the Rocket Chip is its implementatinon langage, Chisel. It is recommended to learn the basics before going deeper in the Rocket Chip. Some resources:

  • Chisel bootcamp. A bootcamp based on Jupyter notebooks. Docker container seems a bit buggy, but it is still possible to use it offline.
  • Chisel book. A book with several exercises and slides!

Adding a custom CSR - Use case

Initial configuration

Let’s say we want to add a custom CSR labeled dmpcfg at address 0x308 (CSR ID isn’t used by any existing register).

Declaring the CSR in the Rocket Chip

The first thing to do is to create the dmpcfg register in Instructions.scala file. This file contains opcodes of each instruction as well as labels and addresses.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
@@ -1014,6 +1014,7 @@ object CSRs {
   val sscratchcsw = 0x148
   val sscratchcswl = 0x149
   val mtvt = 0x307
+  val dmpcfg = 0x308
   val mnxti = 0x345
   val mintstatus = 0x346
   val mscratchcsw = 0x348
@@ -1290,6 +1291,7 @@ object CSRs {
     res += sscratchcsw
     res += sscratchcswl
     res += mtvt
+    res += dmpcfg
     res += mnxti
     res += mintstatus
     res += mscratchcsw

Note: line numbers may differ with an up-to-date Rocket Chip repository.

This step only declare the additional CSR. However, as it is left unconnected, the Scala-to-Verilog transformation will not keep the dmpcfg CSR in its implementation.

Adding the CSR as default in the hardware implementation

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@@ -458,7 +458,9 @@ class CSRFile(
   val reg_tselect = Reg(UInt(width = log2Up(nBreakpoints)))
   val reg_bp = Reg(Vec(1 << log2Up(nBreakpoints), new BP))
   val reg_pmp = Reg(Vec(nPMPs, new PMPReg))
+  val reg_dmpcfg = Reg(UInt(width=xLen))
   val reg_mie = Reg(UInt(width = xLen))
   val (reg_mideleg, read_mideleg) = {
     val reg = Reg(UInt(xLen.W))
@@ -617,6 +619,9 @@ class CSRFile(
     CSRs.mtvec -> read_mtvec,
     CSRs.mip -> read_mip,
     CSRs.mie -> reg_mie,
+    CSRs.dmpcfg -> reg_dmpcfg,
     CSRs.mscratch -> reg_mscratch,
     CSRs.mepc -> readEPC(reg_mepc).sextTo(xLen),
     CSRs.mtval -> reg_mtval.sextTo(xLen),
@@ -1203,6 +1208,7 @@ class CSRFile(
       }
     }
     when (decoded_addr(CSRs.mie))      { reg_mie := wdata & supported_interrupts }
+    when (decoded_addr(CSRs.dmpcfg))   { reg_dmpcfg := wdata }
     when (decoded_addr(CSRs.mepc))     { reg_mepc := formEPC(wdata) }
     when (decoded_addr(CSRs.mscratch)) { reg_mscratch := wdata }
     if (mtvecWritable)

Three lines have been added in the CSR Rocket file:

  • val reg_dmpcfg = Reg(UInt(width=xLen)): declaring a register of xLen-long unsigned integers. xLen is usually 64 bits.
  • when (decoded_addr(CSRs.dmpcfg)) { reg_dmpcfg := wdata }: when the core decodes the CSR address, it will copy the date in the register previously declared.
  • CSRs.dmpcfg -> reg_dmpcfg: adding the CSR in the CSR list known as read_mapping.

read_mapping and CSR subsets

The initial list of CSR is declared here: https://github.com/chipsalliance/rocket-chip/blob/v1.5/src/main/scala/rocket/CSR.scala#L610

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
val read_mapping = LinkedHashMap[Int,Bits](
  CSRs.tselect -> reg_tselect,
  CSRs.tdata1 -> reg_bp(reg_tselect).control.asUInt,
  CSRs.tdata2 -> reg_bp(reg_tselect).address.sextTo(xLen),
  CSRs.tdata3 -> reg_bp(reg_tselect).textra.asUInt,
  CSRs.misa -> reg_misa,
  CSRs.mstatus -> read_mstatus,
  CSRs.mtvec -> read_mtvec,
  CSRs.mip -> read_mip,
  CSRs.mie -> reg_mie,
  CSRs.mscratch -> reg_mscratch,
  CSRs.mepc -> readEPC(reg_mepc).sextTo(xLen),
  CSRs.mtval -> reg_mtval.sextTo(xLen),
  CSRs.mcause -> reg_mcause,
  CSRs.mhartid -> io.hartid)

As the Rocket Chip is more a processor generator rather than a basic implementation, we can add CSR subsets depending on some features. For instance, if we need debug:

1
2
3
4
5
6
// Please note the usingDebug flag here
val debug_csrs = if (!usingDebug) LinkedHashMap() else LinkedHashMap[Int,Bits](
  CSRs.dcsr -> reg_dcsr.asUInt,
  CSRs.dpc -> readEPC(reg_dpc).sextTo(xLen),
  CSRs.dscratch -> reg_dscratch.asUInt) ++
  reg_dscratch1.map(r => CSRs.dscratch1 -> r)

These CSRs are added a bit later in read_mapping:

1
read_mapping ++= debug_csrs

In our case, for a first try, we have simply added the dmpcfg register in the default CSRs subset. Then, the Scala-to-Verilog can be executed. We are able to see the new CSR in the Verilog file and play with the small software described in a previous article.

In the generated Verilog file:

1
2
3
reg [29:0] reg_pmp_7_addr; // @[CSR.scala 460:20]
reg [63:0] reg_dmpcfg; // @[CSR.scala 462:23]
reg [63:0] reg_mie; // @[CSR.scala 464:20]

../img/rocket-log.png

References

  1. RIMI: instruction-level memory isolation for embedded systems on RISC-V
  2. GitHub - chipsalliance/rocket-chip: Rocket Chip Generator
  3. GitHub - chipsalliance/rocket-tools: Software tools that support rocket-chip
  4. Custom C simple C code