scalariscvchisel

Chisel Passing Enum type as IO


This is a related topic on chisel enum I have already looked at chisel "Enum(UInt(), 5)" failed

I am building a RISC-V in chisel and am running into a roadblock. I would like to abstract the ALU opcode from a combination of opcode, funct3, and funct7 to the actual ALU operation. Below I have a SystemVerilog module that shows the type of behavior I would like to emulate

// Defined in a types file
typedef enum bit [2:0] {
    alu_add = 3'b000,
    alu_sll = 3'b001,
    alu_sra = 3'b010,
    alu_sub = 3'b011,
    alu_xor = 3'b100,
    alu_srl = 3'b101,
    alu_or  = 3'b110,
    alu_and = 3'b111
} alu_ops;

module alu
(
    input alu_ops aluop,
    input [31:0] a, b,
    output logic [31:0] f
);

always_comb
begin
    unique case (aluop)
        alu_add:  f = a + b;
        alu_sll:  f = a << b[4:0];
        alu_sra:  f = $signed(a) >>> b[4:0];
        alu_sub:  f = a - b;
        alu_xor:  f = a ^ b;
        alu_srl:  f = a >> b[4:0];
        alu_or:   f = a | b;
        alu_and:  f = a & b;
    endcase
end

endmodule : alu

This is the current chisel file that I am using

AluType.scala

import chisel3._
import chisel3.Driver
import chisel3.experimental.ChiselEnum

package ALUType {

  object AluOP extends ChiselEnum {
//    type AluOP = Value
    val ADDI, SLTI, SLTIU, XORI, ORI, ANDI, SLLI, SLRI, SRAI, ADD, SUB, SLL, SLT, SLTU, XOR, SRL, SRA, OR, AND = Value
  }

}

AluFile.scala

import chisel3._
import chisel3.Driver
import chisel3.experimental.ChiselEnum
import ALUType.AluOP._

class ALUFile(val dl_size: Int, val op_size: Int, val funct3_size: Int, val funct7_size: Int) extends Module {
  val io = IO(new Bundle {
    val val_a       =  Input(UInt(dl_size.W))
    val val_b       =  Input(UInt(dl_size.W))
    val aluop       =  Input(ALUType.AluOP.Type)
    //val opcode      =  Input(UInt(op_size.W))
    //val funct3      =  Input(UInt(funct3_size.W))
    //val funct7      =  Input(UInt(funct7_size.W))
    val val_out     = Output(UInt(dl_size.W))
  })

// Actual function
}

This is the result of the sbt run

$ sbt run
[info] welcome to sbt 1.4.7 (Oracle Corporation Java 1.8.0_281)
[info] loading project definition from C:\Chisel\Test1\RegFile\project
[info] loading settings for project regfile from build.sbt ...
[info] set current project to regfile (in build file:/C:/Chisel/Test1/RegFile/)
[info] compiling 1 Scala source to C:\Chisel\Test1\RegFile\target\scala-2.12\classes ...
[error] C:\Chisel\Test1\RegFile\src\main\scala\ALUFile.scala:43:30: inferred type arguments [ALUType.AluOP.Type.type] do not conform to method apply's type parameter bounds [T <: chisel3.Data]
[error]     val aluop       =  Input(Wire(ALUType.AluOP.Type))
[error]                              ^
[error] C:\Chisel\Test1\RegFile\src\main\scala\ALUFile.scala:43:49: type mismatch;
[error]  found   : ALUType.AluOP.Type.type
[error]  required: T
[error]     val aluop       =  Input(Wire(ALUType.AluOP.Type))
[error]                                                 ^
[error] two errors found
[error] (Compile / compileIncremental) Compilation failed
[error] Total time: 4 s, completed Feb 11, 2021 8:35:18 PM

Do I just need to assign it to a UInt higher up, then decode it again? Seems silly to have to encode, then decode just to pass it from one module to the next. Is there a way to get AluOP.Type to conform to T? I would have thought that it would simply because it is a ChiselEnum.

EDIT :: 1

I tried enumerating with UInt but it says that is a non-literal type

[info] running ALUFile
[info] [0.000] Elaborating design...
[error] chisel3.internal.ChiselException: AluOp defined with a non-literal type
[error]         ...
[error]         at AluOp$.<init>(ALUFile.scala:41)
[error]         at AluOp$.<clinit>(ALUFile.scala)
[error]         at ALUFile$$anon$1.<init>(ALUFile.scala:49)
[error]         at ALUFile.<init>(ALUFile.scala:46)
[error]         at ALUFile$.$anonfun$new$6(ALUFile.scala:80)
object AluOp extends ChiselEnum {
    val addi, slti, sltiu, xori, ori, andi, slli, slri, srai, add, sub, sll, slt, sltu, xor, srl, sra, or, and = Value(UInt()) // Line where error occurs
}
import AluOp._

class ALUFile(val dl_size: Int, val op_size: Int, val funct3_size: Int, val funct7_size: Int) extends Module {
  val io = IO(new Bundle {
    val val_a       =  Input(UInt(dl_size.W))
    val val_b       =  Input(UInt(dl_size.W))
    val aluop       =  Input( AluOp() )
    val val_out     = Output(UInt(dl_size.W))
  })

  switch (io.aluop) {
    is (addi) {
      io.val_out  := 1.U
    }
    is (slti) {
      io.val_out  := 2.U
    }
  }

  // Output result
  io.val_out  := 0.U

}

From https://github.com/chipsalliance/chisel3/blob/dd6871b8b3f2619178c2a333d9d6083805d99e16/src/test/scala/chiselTests/StrongEnum.scala

The only example they have for using enum in a switch statement, but they manually map values to the type WHICH IS NOT AN ENUM!!!

object StrongEnumFSM {
  object State extends ChiselEnum {
    val sNone, sOne1, sTwo1s = Value

    val correct_annotation_map = Map[String, BigInt]("sNone" -> 0, "sOne1" -> 1, "sTwo1s" -> 2)
  }
}

class StrongEnumFSM extends Module {
  import StrongEnumFSM.State
  import StrongEnumFSM.State._

  // This FSM detects two 1's one after the other
  val io = IO(new Bundle {
    val in = Input(Bool())
    val out = Output(Bool())
    val state = Output(State())
  })

  val state = RegInit(sNone)

  io.out := (state === sTwo1s)
  io.state := state

  switch (state) {
    is (sNone) {
      when (io.in) {
        state := sOne1
      }
    }
    is (sOne1) {
      when (io.in) {
        state := sTwo1s
      } .otherwise {
        state := sNone
      }
    }
    is (sTwo1s) {
      when (!io.in) {
        state := sNone
      }
    }
  }
}

One solution that might exist can be found here https://github.com/chipsalliance/chisel3/issues/885 where he defined his own Scala object and allowed for the call to return a UInt.

Additionally if I just use Enum, I can get it to compile which may just be the best solution for now. I would like to see a ChiselEnum be able to easily define states or operations at UInts and be able to pass them as IO so that I can get away from using numbers to define states and make them much more readable.

object AluOp {
        val addi :: slti :: sltiu :: xori :: ori :: andi :: slli :: slri :: srai :: add :: sub :: sll :: slt :: sltu :: xor :: srl :: sra :: or :: and :: Nil = Enum(19)
    }
import AluOp._

class ALUFile(val dl_size: Int, val op_size: Int, val funct3_size: Int, val funct7_size: Int) extends Module {
  val io = IO(new Bundle {
    val val_a       =  Input(UInt(dl_size.W))
    val val_b       =  Input(UInt(dl_size.W))
//    val aluop       =  Input( UInt(AluOp.getWidth.W) )
    val aluop       =  Input(UInt(5.W))
//    val opcode      =  Input(UInt(op_size.W))
//    val funct3      =  Input(UInt(funct3_size.W))
//    val funct7      =  Input(UInt(funct7_size.W))
    val val_out     = Output(UInt(dl_size.W))
  })
  //  val reg_last = RegNext()

  switch (io.aluop) {
    is (addi) {
      io.val_out  := 1.U
    }
    is (slti) {
      io.val_out  := 2.U
    }
  }

Solution

  • I think all you need to do is use

    val aluop       =  Input(AluOP())
    

    Some simple example code can be found in the chisel3 unit tests

    OP Edit ::

    By adding a mapping:

    import chisel3._
    import chisel3.Driver
    import chisel3.util._
    import chisel3.experimental.ChiselEnum
    
    package ALUType {
    //    object AluOp {
    //        val addi :: slti :: sltiu :: xori :: ori :: andi :: slli :: slri :: srai :: add :: sub :: sll :: slt :: sltu :: xor :: srl :: sra :: or :: and :: Nil = Enum(19)
    //    }
        object AluOp extends ChiselEnum {
            val add, sub, sll, slt, sltu, xor, srl, sra, or, and = Value
    
            val correct_annotation_map = Map[String, UInt](
                "add"  -> 0.U,
                "sub"  -> 1.U,
                "sll"  -> 2.U,
                "slt"  -> 3.U,
                "sltu" -> 4.U,
                "xor"  -> 5.U,
                "srl"  -> 6.U,
                "sra"  -> 7.U,
                "or"   -> 8.U,
                "and"  -> 9.U
            )
        }
    }
    

    The value can be passed as an input:

    
    import ALUType.AluOp._
    
    class ALUFile(val dl_size: Int, val op_size: Int, val funct3_size: Int, val funct7_size: Int) extends Module {
      import ALUType._
      import ALUType.AluOp._
      val io = IO(new Bundle {
        val val_a       =  Input(UInt(dl_size.W))
        val val_b       =  Input(UInt(dl_size.W))
        val aluop       =  Input( AluOp() )
    //    val aluop       =  Input(UInt(5.W))
    //    val opcode      =  Input(UInt(op_size.W))
    //    val funct3      =  Input(UInt(funct3_size.W))
    //    val funct7      =  Input(UInt(funct7_size.W))
        val val_out     = Output(UInt(dl_size.W))
      })
    //  switch (io.aluop) {
    //    is (add) {
    //      io.val_out  := io.val_a + io.val_b
    //    }
    //    is (slt) {
    //      io.val_out  := 2.U
    //    }
    //  }
    
      switch (io.aluop) {
        is (add) {
          io.val_out  := io.val_a + io.val_b
        }
        is (slt) {
          io.val_out  := 2.U
        }
      }
    
      // Output result
      io.val_out  := 0.U
    
    }
    

    This is still not ideal as I would like to not have to manually map the strings to UInt values, but it is what it is. Maybe a Scala foreach loop could take the tedious assign out, who knows.