2

I am rewriting a Verilog project to Chisel HDL. The project has several de-coupled subcomponents like (ex.v, mem.v, or wb.v) and a configuration file named defines.v, which is `included in the subcomponents. For example,

Contents in defines.v

`define RstEnable 1'b1
`define RstDisable 1'b0
`define ZeroWord 32'h00000000
`define WriteEnable 1'b1
`define WriteDisable 1'b0
`define ReadEnable 1'b1
`define ReadDisable 1'b0
... ...

Contents in ex.v

`include "defines.v"

module ex(
    input wire                    rst,
    input wire[`AluOpBus]         aluop_i,
    input wire[`AluSelBus]        alusel_i,
    input wire[`RegBus]           reg1_i,
    input wire[`RegBus]           reg2_i,
    ... ...
);

    always @ (*) begin
        if(rst == `RstEnable) begin
            logicout <= `ZeroWord;
        end else begin
            case (aluop_i);
                `EXE_OR_OP:         begin
                    logicout <= reg1_i | reg2_i;
                end
                `EXE_AND_OP:        begin
                    logicout <= reg1_i & reg2_i;
                end
                ... ...
                default:                begin
                    logicout <= `ZeroWord;
                end
            endcase
        end    //if
    end      //always

    ... ...

endmodule

I'm unfamiliar with Scala, so its new LISP-style macro system is a little too powerful for me to fully understand. All I want is a simple C/C++ preprocessor style macro which does text substitution.

I have tried using variables

package cpu

import chisel3._
import chisel3.util._


object Defines {
  val RstEnable = "b1".U(1.W)
  val RstDisable = "b0".U(1.W)
  ... ...
}

The variable definitions are used in Scala as follow

class Ex extends Module {
  val io = IO(new Bundle {
    ... ...
    val aluop_i = Input(UInt(AluOpBus))
    ... ...
  })

  ... ...

  logicout := io.aluop_i match {
    case EXE_OR_OP => logicout
    case _ => 0.U
  }
}

This almost works, except the following error which signals that match isn't happy with a variable

[error] /Users/nalzok/Developer/DonkeyMIPS/chisel/src/main/scala/cpu/ex/Ex.scala:88:10: type mismatch;
[error]  found   : chisel3.UInt
[error]  required: Unit
[error]     case EXE_OR_OP => logicout
[error]          ^
[error] one error found
[error] (Compile / compileIncremental) Compilation failed
[error] Total time: 3 s, completed Jun 14, 2020 9:42:13 AM
nalzok
  • 14,965
  • 21
  • 72
  • 139

1 Answers1

3

The specific error that you're seeing is that you're trying to pattern match on the return of logicout := io.aluop_i. A hardware connection is made, this returns Unit, and then you're trying to pattern match on that.

Chisel is not translating Scala conditionals/pattern matching to hardware constructs. So, pattern matches can't be used to describe hardware multiplexing (Scala match/case is not Verilog case). However, you do have the full power of Scala conditionals/pattern matching to describe hardware generation. E.g., you can pattern match on RstEnable to produce hardware that has or doesn't have a reset.

What you want is to either use when/.elsewhen/.otherwise or switch/is. Something like:

when (io.aluop_i === EXE_OR_OP) { 
  logicout := logicout
}.otherwise {
  logicout := 0.U
}

As you'll realize that this gets verbose quickly, the utility MuxLookup provides a more terse syntax for generating the same thing with a sequence of tuples.:

logicout := MuxLookup(
  io.aluop_i,
  0.U,
  Seq(EXE_OR_OP -> logicout)
)

On Parameterization

The way that you're using parameters is probably better handled by directly parameterizing the Bundles/Modules as opposed to defining a global object.

E.g., you could have an abstract Parameters class that defines all your parameterization. You then provide concrete implementations of this for different versions of your CPU. You then pass this to your modules as they're constructed and they use what they need.

/** An abstract class that defines all the parameters for your CPU */
abstract class Parameters {
  def RstEnable: Int
  def RstDisable: Int
  def ZeroWord: Int
  def WriteEnable: Int
  def WriteDisable: Int
  def ReadEnable: Int
  def ReadDisable: Int
  def AluOpBusWidth: Int
}

/** A singleton object that defines the parameters for one instance of your CPU */
object MyParameters extends Parameters {
  val RstEnable    = 1
  val RstDisable   = 0
  val ZeroWord     = 0
  val WriteEnable  = 1
  val WriteDisable = 0
  val ReadEnable   = 1
  val ReadDisable  = 0
  val AluOpBusWidth = 32
}

/** The execution unit IO uses one of the parameters to set the bus width */
class ExIO(a: Parameters) extends Bundle {
  val aluop_i = Input(UInt(a.AluOpBusWidth.W))
  /* ... */
}

class Ex(a: Parameters) extends Module {
  val io = IO(new ExIO(a))
  /* ... */
}

You then would pass a Parameters object along with the module to construct it:

(new ChiselStage).emitVerilog(new Ex(MyParameters))

Passing all the parameters around does get tedious, though... Using implicit parameters will cut down on the characters typed. Also, a more robust parameterization approach is the "Context Dependent Environments" library that Rocket Chip uses. See: chipsalliance/api-config-chipsalliance. Most projects won't need this, but it's a cleaner approach for complex situations involving parameterization.

seldridge
  • 2,704
  • 15
  • 19