Create a generic Bundle in Chisel3

Viewed 164

In Chisel3, I want to create a generic Bundle ParamsBus with parameterized type. Then I follow the example on the Chisel3 website:

class ParamBus[T <: Data](gen: T) extends Bundle {
  val dat1 = gen
  val dat2 = gen

  override def cloneType = (new ParamBus(gen)).asInstanceOf[this.type]
}

class TestMod[T <: Data](gen: T) extends Module {
  val io = IO(new Bundle {
    val o_out = Output(gen)
  })

  val reg_d = Reg(new ParamBus(gen))

  io.o_out := 0.U
  //io.o_out := reg_d.dat1 + reg_d.dat2

  dontTouch(reg_d)
}

However, during code generation, I have the following error:

chisel3.AliasedAggregateFieldException: Aggregate ParamBus(Reg in TestMod) contains aliased fields List(UInt<8>)...
         at fpga.examples.TestMod.<init>(test.scala:20)

Moreover, if I exchange the two lines to connect io.o_out, another error appears:

/home/escou64/Projects/fpga-io/src/main/scala/examples/test.scala:23:34: type mismatch;
  found   : T
  required: String
   io.o_out := reg_d.dat1 + reg_d.dat2
                                  ^

Any idea of the issue ? Thanks for the help!

1 Answers

The issue you're running into is that the argument gen to ParamBus is a single object that is used for both dat1 and dat2. Scala (and thus Chisel) has reference semantics (like Java and Python), and thus dat1 and dat2 are both referring to the exact same object. Chisel needs the fields of Bundles to be different objects, thus the aliasing error you are seeing.

The easiest way to deal with this is to call .cloneType on gen when using it multiple times within a Bundle:

class ParamBus[T <: Data](gen: T) extends Bundle {
  val dat1 = gen.cloneType
  val dat2 = gen.cloneType
  // Also note that you shouldn't need to implement cloneType yourself anymore
}

(Scastie link: https://scastie.scala-lang.org/mJmSdq8xSqayOceSjxHkRQ)

This is definitely a bit of a wart in the Chisel3 API because we try to hide the need to call .cloneType yourself, but least as of v3.4.3, this remains the case.

Alternatively, you could wrap the uses of gen in Output. It may seem weird to use a direction here but if all directions are Output, it's essentially the same as having no directions:

class ParamBus[T <: Data](gen: T) extends Bundle {
  val dat1 = Output(gen)
  val dat2 = Output(gen)
}

(Scastie link: https://scastie.scala-lang.org/TWajPNItRX6qOKDGDPnMmw)

A third (and slightly more advanced) technique is to make gen a 0-arity function (ie. a function that takes no arguments). Instead of gen being an object to use as a type template, it's instead a function that will create fresh types for you when called. Scala is a functional programming language so functions can be passed around as values just like objects can:

class ParamBus[T <: Data](gen: () => T) extends Bundle {
  val dat1 = gen()
  val dat2 = gen()
}
// You can call it like so:
// new ParamBus(() => UInt(8.W))

(Scastie link: https://scastie.scala-lang.org/JQ7D8VZsSCWP2i6DWJ4cLA)

I tend to prefer this final version, but I understand it can be more daunting for new users. Eventually I'd like to fix the issue you're seeing with a more direct use of gen, but these are ways to deal with the issue for the time being.

Related