ChiselEnum

The ChiselEnum type can be used to reduce the chance of error when encoding mux selectors, opcodes, and functional unit operations. In contrast withChisel.util.Enum, ChiselEnum are subclasses of Data, which means that they can be used to define fields in Bundles, including in IOs.

Functionality and Examples

// Imports used in the following examples
import chisel3._
import chisel3.util._
import chisel3.stage.ChiselStage
import chisel3.experimental.ChiselEnum

Below we see ChiselEnum being used as mux select for a RISC-V core. While wrapping the object in a package is not required, it is highly recommended as it allows for the type to be used in multiple files more easily.

// package CPUTypes {
    object AluMux1Sel extends ChiselEnum {
        val selectRS1, selectPC = Value
        /** How the values will be mapped
            "selectRS1" -> 0.U,
            "selectPC"  -> 1.U
        */
    }

Here we see a mux using the AluMux1Sel to select between different inputs.

import AluMux1Sel._

class AluMux1Bundle extends Bundle {
        val aluMux1Sel =  Input( AluMux1Sel() )
        val rs1Out     =  Input(Bits(32.W))
        val pcOut      =  Input(Bits(32.W))
        val aluMux1Out = Output(Bits(32.W))
}

class AluMux1File extends Module {
    val io = IO(new AluMux1Bundle)

    // Default value for aluMux1Out
    io.aluMux1Out := 0.U

    switch (io.aluMux1Sel) {
        is (selectRS1) {
            io.aluMux1Out  := io.rs1Out
        }
        is (selectPC) {
            io.aluMux1Out  := io.pcOut
        }
    }
}
module AluMux1File(
  input         clock,
  input         reset,
  input         io_aluMux1Sel,
  input  [31:0] io_rs1Out,
  input  [31:0] io_pcOut,
  output [31:0] io_aluMux1Out
);
  wire  _T_2 = ~io_aluMux1Sel; // @[Conditional.scala 37:30]
  wire [31:0] _GEN_0 = io_aluMux1Sel ? io_pcOut : 32'h0; // @[Conditional.scala 39:67 chisel-enum.md 57:28 chisel-enum.md 50:19]
  assign io_aluMux1Out = _T_2 ? io_rs1Out : _GEN_0; // @[Conditional.scala 40:58 chisel-enum.md 54:28]
endmodule

ChiselEnum also allows for the user to define variables by passing in the value shown below. Note that the value must be increasing or else

chisel3.internal.ChiselException: Exception thrown when elaborating ChiselGeneratorAnnotation

is thrown during Verilog generation.

object Opcode extends ChiselEnum {
    val load  = Value(0x03.U) // i "load"  -> 000_0011
    val imm   = Value(0x13.U) // i "imm"   -> 001_0011
    val auipc = Value(0x17.U) // u "auipc" -> 001_0111
    val store = Value(0x23.U) // s "store" -> 010_0011
    val reg   = Value(0x33.U) // r "reg"   -> 011_0011
    val lui   = Value(0x37.U) // u "lui"   -> 011_0111
    val br    = Value(0x63.U) // b "br"    -> 110_0011
    val jalr  = Value(0x67.U) // i "jalr"  -> 110_0111
    val jal   = Value(0x6F.U) // j "jal"   -> 110_1111
}

The user can ‘jump’ to a value and continue incrementing by passing a start point then using a regular Value assignment.

object BranchFunct3 extends ChiselEnum {
    val beq, bne = Value
    val blt = Value(4.U)
    val bge, bltu, bgeu = Value
    /** How the values will be mapped
        "beq"  -> 0.U,
        "bne"  -> 1.U,
        "blt"  -> 4.U,
        "bge"  -> 5.U,
        "bltu" -> 6.U,
        "bgeu" -> 7.U
    */
}

Testing

When testing your modules, the .Type and .litValue attributes allow for the the objects to be passed as parameters and for the value to be converted to BigInt type. Note that BigInts cannot be casted to Int with .asInstanceOf[Int], they use their own methods like toInt. Please review the scala.math.BigInt page for more details!

def expectedSel(sel: AluMux1Sel.Type): Boolean = sel match {
  case AluMux1Sel.selectRS1 => (sel.litValue == 0)
  case AluMux1Sel.selectPC  => (sel.litValue == 1)
  case _                    => false
}

The ChiselEnum type also has methods .all and .getWidth where all returns all of the enum instances and getWidth returns the width of the hardware type.

Workarounds

As of 2/26/2021, the width of the values is always inferred. To work around this, you can add an extra Value that forces the width that is desired. This is shown in the example below, where we add a field ukn to force the width to be 3 bits wide:

object StoreFunct3 extends ChiselEnum {
    val sb, sh, sw = Value
    val ukn = Value(7.U)
    /** How the values will be mapped
        "sb" -> 0.U,
        "sh" -> 1.U,
        "sw" -> 2.U
    */
}

Signed values are not supported so if you want the value signed, you must cast the UInt with .asSInt.

Additional Resources

The ChiselEnum type is much more powerful than stated above. It allows for Sequence, Vec, and Bundle assignments, as well as a .next operation to allow for stepping through sequential states and an .isValid for checking that a hardware value is a valid Value. The source code for the ChiselEnum can be found here in the class EnumFactory. Examples of the ChiselEnum operations can be found here.