From 1ecab68a69a759e6545707e667e13a9b82801e24 Mon Sep 17 00:00:00 2001 From: Mike Urbach Date: Thu, 11 Apr 2024 14:11:42 -0600 Subject: [PATCH] Add extension point on Data for customizing Connectable behavior. (#3978) The user can override this method to customize how their Data applies waive, squeeze, and exclude in its Connectable. --- core/src/main/scala/chisel3/Data.scala | 41 ++- .../scala/chisel3/connectable/package.scala | 2 +- .../scala/chiselTests/ConnectableSpec.scala | 240 ++++++++++++++++++ 3 files changed, 281 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/chisel3/Data.scala b/core/src/main/scala/chisel3/Data.scala index a31287102ca..d37d5802ca9 100644 --- a/core/src/main/scala/chisel3/Data.scala +++ b/core/src/main/scala/chisel3/Data.scala @@ -826,7 +826,25 @@ object Data { * add to Data we also want on Connectable, so an implicit conversion makes the most sense * so the ScalaDoc can be shared. */ - implicit def toConnectableDefault[T <: Data](d: T): Connectable[T] = Connectable.apply(d) + implicit def toConnectableDefault[T <: Data](d: T): Connectable[T] = makeConnectableDefault(d) + + /** Create the default [[Connectable]] used for all instances of a [[Data]] of type T. + * + * This uses the default [[connectable.Connectable.apply]] as a starting point. + * + * Users can extend the [[HasCustomConnectable]] trait on any [[Data]] to further customize the [[Connectable]]. This + * is checked for in any potentially nested [[Data]] and any customizations are applied on top of the default + * [[Connectable]]. + */ + private[chisel3] def makeConnectableDefault[T <: Data](d: T): Connectable[T] = { + val base = Connectable.apply(d) + DataMirror + .collectMembers(d) { + case hasCustom: HasCustomConnectable => + hasCustom + } + .foldLeft(base)((connectable, hasCustom) => hasCustom.customConnectable(connectable)) + } /** Typeclass implementation of HasMatchingZipOfChildren for Data * @@ -1160,3 +1178,24 @@ final case object DontCare extends Element with connectable.ConnectableDocs { final def :>=[T <: Data](producer: => T)(implicit sourceInfo: SourceInfo): Unit = this.asInstanceOf[Data] :>= producer.asInstanceOf[Data] } + +/** Trait to indicate that a subclass of [[Data]] has a custom [[Connectable]]. + * + * Users can implement the [[customConnectable]] method, which receives a default [[Connectable]], and is expected to + * use the methods on [[Connectable]] to customize it. For example, a [[Bundle]] could define this by using + * [[connectable.Connectable.exclude(members*]] to always exlude a specific member: + * + * {{{ + * class MyBundle extends Bundle with HasCustomConnectable { + * val foo = Bool() + * val bar = Bool() + * + * override def customConnectable[T <: Data](base: Connectable[T]): Connectable[T] = { + * base.exclude(_ => bar) + * } + * } + * }}} + */ +trait HasCustomConnectable { this: Data => + def customConnectable[T <: Data](base: Connectable[T]): Connectable[T] +} diff --git a/core/src/main/scala/chisel3/connectable/package.scala b/core/src/main/scala/chisel3/connectable/package.scala index 1d3822a4135..6867babb794 100644 --- a/core/src/main/scala/chisel3/connectable/package.scala +++ b/core/src/main/scala/chisel3/connectable/package.scala @@ -16,7 +16,7 @@ package object connectable { * @param consumer the left-hand-side of the connection */ implicit class ConnectableOperators[T <: Data](consumer: T) - extends Connectable.ConnectableOpExtension(Connectable(consumer)) + extends Connectable.ConnectableOpExtension(Data.makeConnectableDefault(consumer)) /** ConnectableVec Typeclass defines the following operators on between a (consumer: Vec) and (producer: Seq): :<=, :>=, :<>=, :#= * diff --git a/src/test/scala/chiselTests/ConnectableSpec.scala b/src/test/scala/chiselTests/ConnectableSpec.scala index e3e0e48e81d..23508d51e5d 100644 --- a/src/test/scala/chiselTests/ConnectableSpec.scala +++ b/src/test/scala/chiselTests/ConnectableSpec.scala @@ -1839,4 +1839,244 @@ class ConnectableSpec extends ChiselFunSpec with Utils { } } } + describe("(9): HasCustomConnectable") { + describe("(9.a) customizing waive behavior") { + + class MyBundle(includeFoo: Boolean) extends Bundle with HasCustomConnectable { + val foo = Option.when(includeFoo)(Bool()) + val bar = Bool() + + override def customConnectable[T <: Data](base: Connectable[T]): Connectable[T] = { + if (includeFoo) { + base.waive(_ => foo.get) + } else { + base + } + } + } + + class OuterBundle(includeFoo: Boolean) extends Bundle { + val bundle = new MyBundle(includeFoo) + } + + it("(9.a.1) allows the user to customize the waive behavior of the Connectable for their class as producer") { + + class MyModule extends RawModule { + val in = IO(Input(new MyBundle(true))) + val out = IO(Output(new MyBundle(false))) + + out :<>= in + } + + testCheck( + ChiselStage.emitCHIRRTL(new MyModule()), + Seq( + "connect out.bar, in.bar" + ), + Seq( + "connect out.foo, in.foo" + ) + ) + } + + it("(9.a.2) allows the user to customize the waive behavior of the Connectable for their class as consumer") { + + class MyModule extends RawModule { + val in = IO(Input(new MyBundle(false))) + val out = IO(Output(new MyBundle(true))) + + out :<>= in + } + + testCheck( + ChiselStage.emitCHIRRTL(new MyModule()), + Seq( + "connect out.bar, in.bar" + ), + Seq( + "connect out.foo, in.foo" + ) + ) + } + + it( + "(9.a.3) allows the user to customize the waive behavior of the Connectable for their class nested in other Connectables" + ) { + + class MyModule extends RawModule { + val in = IO(Input(new OuterBundle(true))) + val out = IO(Output(new OuterBundle(false))) + + out :<>= in + } + + testCheck( + ChiselStage.emitCHIRRTL(new MyModule()), + Seq( + "connect out.bundle.bar, in.bundle.bar" + ), + Seq( + "connect out.bundle.foo, in.bundle.foo" + ) + ) + } + } + + describe("(9.b) customizing squeeze behavior") { + + class MyBundle(fooWidth: Int) extends Bundle with HasCustomConnectable { + val foo = UInt(fooWidth.W) + val bar = Bool() + + override def customConnectable[T <: Data](base: Connectable[T]): Connectable[T] = { + base.squeeze(_ => foo) + } + } + + class OuterBundle(fooWidth: Int) extends Bundle { + val bundle = new MyBundle(fooWidth) + } + + it("(9.b.1) allows the user to customize the squeeze behavior of the Connectable for their class as producer") { + + class MyModule extends RawModule { + val in = IO(Input(new MyBundle(2))) + val out = IO(Output(new MyBundle(1))) + + out :<>= in + } + + testCheck( + ChiselStage.emitCHIRRTL(new MyModule()), + Seq( + "connect out.bar, in.bar", + "connect out.foo, in.foo" + ), + Nil + ) + } + + it("(9.b.2) allows the user to customize the squeeze behavior of the Connectable for their class as consumer") { + + class MyModule extends RawModule { + val in = IO(Input(new MyBundle(1))) + val out = IO(Output(new MyBundle(2))) + + out :<>= in + } + + testCheck( + ChiselStage.emitCHIRRTL(new MyModule()), + Seq( + "connect out.bar, in.bar", + "connect out.foo, in.foo" + ), + Nil + ) + } + + it( + "(9.b.3) allows the user to customize the squeeze behavior of the Connectable for their class nested in other Connectables" + ) { + + class MyModule extends RawModule { + val in = IO(Input(new OuterBundle(2))) + val out = IO(Output(new OuterBundle(1))) + + out :<>= in + } + + testCheck( + ChiselStage.emitCHIRRTL(new MyModule()), + Seq( + "connect out.bundle.bar, in.bundle.bar", + "connect out.bundle.foo, in.bundle.foo" + ), + Nil + ) + } + } + + describe("(9.c) connectableExcludeSelection") { + + class MyBundle(includeFoo: Boolean) extends Bundle with HasCustomConnectable { + val foo = Option.when(includeFoo)(Bool()) + val bar = Bool() + + override def customConnectable[T <: Data](base: Connectable[T]): Connectable[T] = { + if (includeFoo) { + base.exclude(_ => foo.get) + } else { + base + } + } + } + + class OuterBundle(includeFoo: Boolean) extends Bundle { + val bundle = new MyBundle(includeFoo) + } + + it("(9.c.1) allows the user to customize the exclude behavior of the Connectable for their class as producer") { + + class MyModule extends RawModule { + val in = IO(Input(new MyBundle(true))) + val out = IO(Output(new MyBundle(false))) + + out :<>= in + } + + testCheck( + ChiselStage.emitCHIRRTL(new MyModule()), + Seq( + "connect out.bar, in.bar" + ), + Seq( + "connect out.foo, in.foo" + ) + ) + } + + it("(9.c.2) allows the user to customize the exclude behavior of the Connectable for their class as consumer") { + + class MyModule extends RawModule { + val in = IO(Input(new MyBundle(false))) + val out = IO(Output(new MyBundle(true))) + + out :<>= in + } + + testCheck( + ChiselStage.emitCHIRRTL(new MyModule()), + Seq( + "connect out.bar, in.bar" + ), + Seq( + "connect out.foo, in.foo" + ) + ) + } + + it( + "(9.c.3) allows the user to customize the exclude behavior of the Connectable for their class nested in other Connectables" + ) { + + class MyModule extends RawModule { + val in = IO(Input(new OuterBundle(true))) + val out = IO(Output(new OuterBundle(false))) + + out :<>= in + } + + testCheck( + ChiselStage.emitCHIRRTL(new MyModule()), + Seq( + "connect out.bundle.bar, in.bundle.bar" + ), + Seq( + "connect out.bundle.foo, in.bundle.foo" + ) + ) + } + } + } }