diff options
| -rw-r--r-- | core/src/main/scala/chisel3/Aggregate.scala | 56 | ||||
| -rw-r--r-- | core/src/main/scala/chisel3/internal/Builder.scala | 8 | ||||
| l--------- | no-plugin-tests/src/test/scala/chiselTests/AutoClonetypeSpec.scala | 1 | ||||
| l--------- | no-plugin-tests/src/test/scala/chiselTests/AutoNestedCloneSpec.scala | 1 | ||||
| -rw-r--r-- | no-plugin-tests/src/test/scala/chiselTests/MissingCloneBindingExceptionSpec.scala (renamed from src/test/scala/chiselTests/MissingCloneBindingExceptionSpec.scala) | 0 | ||||
| -rw-r--r-- | plugin/src/main/scala-2.12/chisel3/internal/plugin/BundleComponent.scala | 125 | ||||
| -rw-r--r-- | plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselComponent.scala | 7 | ||||
| -rw-r--r-- | plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselPlugin.scala | 6 | ||||
| -rw-r--r-- | src/test/scala/chisel3/testers/TestUtils.scala | 12 | ||||
| -rw-r--r-- | src/test/scala/chiselTests/AutoClonetypeSpec.scala | 179 | ||||
| -rw-r--r-- | src/test/scala/chiselTests/AutoNestedCloneSpec.scala | 73 |
11 files changed, 388 insertions, 80 deletions
diff --git a/core/src/main/scala/chisel3/Aggregate.scala b/core/src/main/scala/chisel3/Aggregate.scala index 6942313e..30276230 100644 --- a/core/src/main/scala/chisel3/Aggregate.scala +++ b/core/src/main/scala/chisel3/Aggregate.scala @@ -798,6 +798,13 @@ abstract class Bundle(implicit compileOptions: CompileOptions) extends Record { case _ => None } + /** Indicates if a concrete Bundle class was compiled using the compiler plugin + * + * Used for optimizing Chisel's performance and testing Chisel itself + * @note This should not be used in user code! + */ + protected def _usingPlugin: Boolean = false + // Memoize the outer instance for autoclonetype, especially where this is context-dependent // (like the outer module or enclosing Bundles). private var _outerInst: Option[Object] = None @@ -808,7 +815,33 @@ abstract class Bundle(implicit compileOptions: CompileOptions) extends Record { private val _containingModule: Option[BaseModule] = Builder.currentModule private val _containingBundles: Seq[Bundle] = Builder.updateBundleStack(this) - override def cloneType : this.type = { + private def checkClone(clone: Bundle): Unit = { + for ((name, field) <- elements) { + if (clone.elements(name) eq field) { + throw new AutoClonetypeException( + s"Automatically cloned $clone has field '$name' aliased with base $this." + + " In the future, this will be solved automatically by the compiler plugin." + + " For now, ensure Chisel types used in the Bundle definition are passed through constructor arguments," + + " or wrapped in Input(...), Output(...), or Flipped(...) if appropriate." + + " As a last resort, you can override cloneType manually." + ) + } + } + + } + + override def cloneType: this.type = { + val clone = _cloneTypeImpl.asInstanceOf[this.type] + checkClone(clone) + clone + } + + /** Implementation of cloneType using runtime reflection. This should _never_ be overridden or called in user-code + * + * @note This is overridden by the compiler plugin (it is never called when using the plugin) + */ + protected def _cloneTypeImpl: Bundle = { + assert(Builder.allowReflectiveAutoCloneType, "reflective autoclonetype is disallowed, this should only happen in testing") // This attempts to infer constructor and arguments to clone this Bundle subtype without // requiring the user explicitly overriding cloneType. import scala.language.existentials @@ -816,24 +849,19 @@ abstract class Bundle(implicit compileOptions: CompileOptions) extends Record { val clazz = this.getClass - def autoClonetypeError(desc: String): Nothing = { - throw new AutoClonetypeException(s"Unable to automatically infer cloneType on $clazz: $desc") - } + def autoClonetypeError(desc: String): Nothing = + throw new AutoClonetypeException( + s"Unable to automatically infer cloneType on $clazz. " + + "cloneType is now implemented by the Chisel compiler plugin so please ensure you are using it in your build. " + + "If you cannot use the compiler plugin or you are using it and you still see this message, please file an issue and let us know. " + + s"For those not using the plugin, here is the 'runtime reflection' cloneType error message: $desc" + ) def validateClone(clone: Bundle, equivDiagnostic: String): Unit = { if (!clone.typeEquivalent(this)) { autoClonetypeError(s"Automatically cloned $clone not type-equivalent to base $this. " + equivDiagnostic) } - - for ((name, field) <- elements) { - if (clone.elements(name) eq field) { - autoClonetypeError(s"Automatically cloned $clone has field $name aliased with base $this." + - " In the future, this can be solved by wrapping the field in Field(...)," + - " see https://github.com/freechipsproject/chisel3/pull/909." + - " For now, ensure Chisel types used in the Bundle definition are passed through constructor arguments," + - " or wrapped in Input(...), Output(...), or Flipped(...) if appropriate.") - } - } + checkClone(clone) } val mirror = runtimeMirror(clazz.getClassLoader) diff --git a/core/src/main/scala/chisel3/internal/Builder.scala b/core/src/main/scala/chisel3/internal/Builder.scala index e95384cd..b1016a2e 100644 --- a/core/src/main/scala/chisel3/internal/Builder.scala +++ b/core/src/main/scala/chisel3/internal/Builder.scala @@ -311,6 +311,8 @@ private[chisel3] class DynamicContext(val annotationSeq: AnnotationSeq) { val components = ArrayBuffer[Component]() val annotations = ArrayBuffer[ChiselAnnotation]() var currentModule: Option[BaseModule] = None + // This is only used for testing, it can be removed if the plugin becomes mandatory + var allowReflectiveAutoCloneType = true /** Contains a mapping from a elaborated module to their aspect * Set by [[ModuleAspect]] @@ -530,6 +532,12 @@ private[chisel3] object Builder extends LazyLogging { dynamicContext.currentReset = newReset } + // This should only be used for testing + def allowReflectiveAutoCloneType: Boolean = dynamicContext.allowReflectiveAutoCloneType + def allowReflectiveAutoCloneType_=(value: Boolean): Unit = { + dynamicContext.allowReflectiveAutoCloneType = value + } + def forcedClock: Clock = currentClock.getOrElse( throwException("Error: No implicit clock.") ) diff --git a/no-plugin-tests/src/test/scala/chiselTests/AutoClonetypeSpec.scala b/no-plugin-tests/src/test/scala/chiselTests/AutoClonetypeSpec.scala new file mode 120000 index 00000000..ae3a7597 --- /dev/null +++ b/no-plugin-tests/src/test/scala/chiselTests/AutoClonetypeSpec.scala @@ -0,0 +1 @@ +../../../../../src/test/scala/chiselTests/AutoClonetypeSpec.scala
\ No newline at end of file diff --git a/no-plugin-tests/src/test/scala/chiselTests/AutoNestedCloneSpec.scala b/no-plugin-tests/src/test/scala/chiselTests/AutoNestedCloneSpec.scala new file mode 120000 index 00000000..4ff3aa4f --- /dev/null +++ b/no-plugin-tests/src/test/scala/chiselTests/AutoNestedCloneSpec.scala @@ -0,0 +1 @@ +../../../../../src/test/scala/chiselTests/AutoNestedCloneSpec.scala
\ No newline at end of file diff --git a/src/test/scala/chiselTests/MissingCloneBindingExceptionSpec.scala b/no-plugin-tests/src/test/scala/chiselTests/MissingCloneBindingExceptionSpec.scala index 28673495..28673495 100644 --- a/src/test/scala/chiselTests/MissingCloneBindingExceptionSpec.scala +++ b/no-plugin-tests/src/test/scala/chiselTests/MissingCloneBindingExceptionSpec.scala diff --git a/plugin/src/main/scala-2.12/chisel3/internal/plugin/BundleComponent.scala b/plugin/src/main/scala-2.12/chisel3/internal/plugin/BundleComponent.scala new file mode 100644 index 00000000..0711e08c --- /dev/null +++ b/plugin/src/main/scala-2.12/chisel3/internal/plugin/BundleComponent.scala @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.internal.plugin + +import scala.collection.mutable +import scala.tools.nsc +import scala.tools.nsc.{Global, Phase} +import scala.tools.nsc.plugins.PluginComponent +import scala.tools.nsc.symtab.Flags +import scala.tools.nsc.transform.TypingTransformers + +// TODO This component could also implement val elements in Bundles +private[plugin] class BundleComponent(val global: Global) extends PluginComponent with TypingTransformers { + import global._ + + val phaseName: String = "chiselbundlephase" + val runsAfter: List[String] = "typer" :: Nil + def newPhase(prev: Phase): Phase = new BundlePhase(prev) + + private class BundlePhase(prev: Phase) extends StdPhase(prev) { + override def name: String = phaseName + def apply(unit: CompilationUnit): Unit = { + // This plugin doesn't work on Scala 2.11 nor Scala 3. Rather than complicate the sbt build flow, + // instead we just check the version and if its an early Scala version, the plugin does nothing + val scalaVersion = scala.util.Properties.versionNumberString.split('.') + if (scalaVersion(0).toInt == 2 && scalaVersion(1).toInt >= 12) { + unit.body = new MyTypingTransformer(unit).transform(unit.body) + } + } + } + + private class MyTypingTransformer(unit: CompilationUnit) extends TypingTransformer(unit) { + + def inferType(t: Tree): Type = localTyper.typed(t, nsc.Mode.TYPEmode).tpe + + val bundleTpe = inferType(tq"chisel3.Bundle") + val dataTpe = inferType(tq"chisel3.Data") + + // Not cached because it should only be run once per class (thus once per Type) + def isBundle(sym: Symbol): Boolean = sym.tpe <:< bundleTpe + + val isDataCache = new mutable.HashMap[Type, Boolean] + // Cached because this is run on every argument to every Bundle + def isData(sym: Symbol): Boolean = isDataCache.getOrElseUpdate(sym.tpe, sym.tpe <:< dataTpe) + + def cloneTypeFull(tree: Tree): Tree = + localTyper.typed(q"chisel3.experimental.DataMirror.internal.chiselTypeClone[${tree.tpe}]($tree)") + + def isNullaryMethodNamed(name: String, defdef: DefDef): Boolean = + defdef.name.decodedName.toString == name && defdef.tparams.isEmpty && defdef.vparamss.isEmpty + + def getConstructorAndParams(body: List[Tree]): (Option[DefDef], Seq[Symbol]) = { + val paramAccessors = mutable.ListBuffer[Symbol]() + var primaryConstructor: Option[DefDef] = None + body.foreach { + case acc: ValDef if acc.symbol.isParamAccessor => + paramAccessors += acc.symbol + case con: DefDef if con.symbol.isPrimaryConstructor => + primaryConstructor = Some(con) + case d: DefDef if isNullaryMethodNamed("_cloneTypeImpl", d) => + val msg = "Users cannot override _cloneTypeImpl. Let the compiler plugin generate it. If you must, override cloneType instead." + global.globalError(d.pos, msg) + case d: DefDef if isNullaryMethodNamed("_usingPlugin", d) => + val msg = "Users cannot override _usingPlugin, it is for the compiler plugin's use only." + global.globalError(d.pos, msg) + case _ => + } + (primaryConstructor, paramAccessors.toList) + } + + + override def transform(tree: Tree): Tree = tree match { + + case bundle: ClassDef if isBundle(bundle.symbol) && !bundle.mods.hasFlag(Flag.ABSTRACT) => + + // ==================== Generate _cloneTypeImpl ==================== + val (con, params) = getConstructorAndParams(bundle.impl.body) + if (con.isEmpty) { + global.reporter.warning(bundle.pos, "Unable to determine primary constructor!") + return super.transform(tree) + } + val constructor = con.get + + val thiz = gen.mkAttributedThis(bundle.symbol) + + // The params have spaces after them (Scalac implementation detail) + val paramLookup: String => Symbol = params.map(sym => sym.name.toString.trim -> sym).toMap + + // Create a this.<ref> for each field matching order of constructor arguments + // List of Lists because we can have multiple parameter lists + val conArgs: List[List[Tree]] = + constructor.vparamss.map(_.map { vp => + val p = paramLookup(vp.name.toString) + // Make this.<ref> + val select = gen.mkAttributedSelect(thiz, p) + // Clone any Data parameters to avoid field aliasing, need full clone to include direction + if (isData(vp.symbol)) cloneTypeFull(select) else select + }) + + val ttpe = Ident(bundle.symbol) + val neww = localTyper.typed(New(ttpe, conArgs)) + + // Create the symbol for the method and have it be associated with the Bundle class + val cloneTypeSym = bundle.symbol.newMethod(TermName("_cloneTypeImpl"), bundle.symbol.pos.focus, Flag.OVERRIDE | Flag.PROTECTED) + // Handwritten cloneTypes don't have the Method flag set, unclear if it matters + cloneTypeSym.resetFlag(Flags.METHOD) + // Need to set the type to chisel3.Bundle for the override to work + cloneTypeSym.setInfo(NullaryMethodType(bundleTpe)) + + val cloneTypeImpl = localTyper.typed(DefDef(cloneTypeSym, neww)) + + // ==================== Generate _usingPlugin ==================== + // Unclear why quasiquotes work here but didn't for cloneTypeSym, maybe they could. + val usingPlugin = localTyper.typed(q"override protected def _usingPlugin: Boolean = true") + + val withMethods = deriveClassDef(bundle) { t => + deriveTemplate(t)(_ :+ cloneTypeImpl :+ usingPlugin) + } + + super.transform(localTyper.typed(withMethods)) + + case _ => super.transform(tree) + } + } +} diff --git a/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselComponent.scala b/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselComponent.scala index f86b8322..b1302ba3 100644 --- a/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselComponent.scala +++ b/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselComponent.scala @@ -19,9 +19,10 @@ class ChiselComponent(val global: Global) extends PluginComponent with TypingTra class ChiselComponentPhase(prev: Phase) extends StdPhase(prev) { override def name: String = phaseName def apply(unit: CompilationUnit): Unit = { - // This plugin doesn't work on Scala 2.11. Rather than complicate the sbt build flow, + // This plugin doesn't work on Scala 2.11 nor Scala 3. Rather than complicate the sbt build flow, // instead we just check the version and if its an early Scala version, the plugin does nothing - if(scala.util.Properties.versionNumberString.split('.')(1).toInt >= 12) { + val scalaVersion = scala.util.Properties.versionNumberString.split('.') + if (scalaVersion(0).toInt == 2 && scalaVersion(1).toInt >= 12) { unit.body = new MyTypingTransformer(unit).transform(unit.body) } } @@ -211,4 +212,4 @@ class ChiselComponent(val global: Global) extends PluginComponent with TypingTra case _ => super.transform(tree) } } -}
\ No newline at end of file +} diff --git a/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselPlugin.scala b/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselPlugin.scala index f8a1752c..d5eb21cc 100644 --- a/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselPlugin.scala +++ b/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselPlugin.scala @@ -10,7 +10,9 @@ import nsc.plugins.{Plugin, PluginComponent} class ChiselPlugin(val global: Global) extends Plugin { val name = "chiselplugin" val description = "Plugin for Chisel 3 Hardware Description Language" - val components: List[PluginComponent] = List[PluginComponent](new ChiselComponent(global)) + val components: List[PluginComponent] = List[PluginComponent]( + new ChiselComponent(global), + new BundleComponent(global) + ) } - diff --git a/src/test/scala/chisel3/testers/TestUtils.scala b/src/test/scala/chisel3/testers/TestUtils.scala index 12712bf7..c72c779a 100644 --- a/src/test/scala/chisel3/testers/TestUtils.scala +++ b/src/test/scala/chisel3/testers/TestUtils.scala @@ -3,10 +3,22 @@ package chisel3.testers import TesterDriver.Backend +import chisel3.{Bundle, RawModule} +import chisel3.internal.firrtl.Circuit +import chisel3.stage.ChiselStage import firrtl.AnnotationSeq object TestUtils { // Useful because TesterDriver.Backend is chisel3 package private def containsBackend(annos: AnnotationSeq): Boolean = annos.collectFirst { case b: Backend => b }.isDefined + + // Allows us to check that the compiler plugin cloneType is actually working + val usingPlugin: Boolean = (new Bundle { def check = _usingPlugin }).check + def elaborateNoReflectiveAutoCloneType(f: => RawModule): Circuit = { + ChiselStage.elaborate { + chisel3.internal.Builder.allowReflectiveAutoCloneType = !usingPlugin + f + } + } } diff --git a/src/test/scala/chiselTests/AutoClonetypeSpec.scala b/src/test/scala/chiselTests/AutoClonetypeSpec.scala index b791297d..a3da109a 100644 --- a/src/test/scala/chiselTests/AutoClonetypeSpec.scala +++ b/src/test/scala/chiselTests/AutoClonetypeSpec.scala @@ -3,7 +3,7 @@ package chiselTests import chisel3._ -import chisel3.stage.ChiselStage +import chisel3.testers.TestUtils class BundleWithIntArg(val i: Int) extends Bundle { val out = UInt(i.W) @@ -65,10 +65,14 @@ class NestedAnonymousBundle extends Bundle { // Not necessarily good style (and not necessarily recommended), but allowed to preserve compatibility. class BundleWithArgumentField(val x: Data, val y: Data) extends Bundle +// TODO all `.suggestNames` are due to https://github.com/chipsalliance/chisel3/issues/1802 class AutoClonetypeSpec extends ChiselFlatSpec with Utils { + val usingPlugin: Boolean = TestUtils.usingPlugin + val elaborate = TestUtils.elaborateNoReflectiveAutoCloneType _ + "Bundles with Scala args" should "not need clonetype" in { - ChiselStage.elaborate { new Module { - val io = IO(new Bundle{}) + elaborate { new Module { + val io = IO(new Bundle{}).suggestName("io") val myWire = Wire(new BundleWithIntArg(8)) assert(myWire.i == 8) @@ -76,8 +80,8 @@ class AutoClonetypeSpec extends ChiselFlatSpec with Utils { } "Bundles with Scala implicit args" should "not need clonetype" in { - ChiselStage.elaborate { new Module { - val io = IO(new Bundle{}) + elaborate { new Module { + val io = IO(new Bundle{}).suggestName("io") implicit val implicitInt: Int = 4 val myWire = Wire(new BundleWithImplicit()) @@ -87,8 +91,8 @@ class AutoClonetypeSpec extends ChiselFlatSpec with Utils { } "Bundles with Scala explicit and impicit args" should "not need clonetype" in { - ChiselStage.elaborate { new Module { - val io = IO(new Bundle{}) + elaborate { new Module { + val io = IO(new Bundle{}).suggestName("io") implicit val implicitInt: Int = 4 val myWire = Wire(new BundleWithArgAndImplicit(8)) @@ -99,16 +103,16 @@ class AutoClonetypeSpec extends ChiselFlatSpec with Utils { } "Subtyped Bundles" should "not need clonetype" in { - ChiselStage.elaborate { new Module { - val io = IO(new Bundle{}) + elaborate { new Module { + val io = IO(new Bundle{}).suggestName("io") val myWire = Wire(new SubBundle(8, 4)) assert(myWire.i == 8) assert(myWire.i2 == 4) } } - ChiselStage.elaborate { new Module { - val io = IO(new Bundle{}) + elaborate { new Module { + val io = IO(new Bundle{}).suggestName("io") val myWire = Wire(new SubBundleVal(8, 4)) @@ -117,68 +121,84 @@ class AutoClonetypeSpec extends ChiselFlatSpec with Utils { } } } - "Subtyped Bundles that don't clone well" should "be caught" in { - a [ChiselException] should be thrownBy extractCause[ChiselException] { - ChiselStage.elaborate { new Module { - val io = IO(new Bundle{}) - val myWire = Wire(new SubBundleInvalid(8, 4)) - } } + def checkSubBundleInvalid() = { + elaborate { new Module { + val io = IO(new Bundle{}).suggestName("io") + val myWire = Wire(new SubBundleInvalid(8, 4)) + } } + } + if (usingPlugin) { + "Subtyped Bundles that don't clone well" should "be now be supported!" in { + checkSubBundleInvalid() + } + } else { + "Subtyped Bundles that don't clone well" should "be caught" in { + a [ChiselException] should be thrownBy extractCause[ChiselException] { + checkSubBundleInvalid() + } } } "Inner bundles with Scala args" should "not need clonetype" in { - ChiselStage.elaborate { new ModuleWithInner } + elaborate { new ModuleWithInner } } "Bundles with arguments as fields" should "not need clonetype" in { - ChiselStage.elaborate { new Module { - val io = IO(Output(new BundleWithArgumentField(UInt(8.W), UInt(8.W)))) + elaborate { new Module { + val io = IO(Output(new BundleWithArgumentField(UInt(8.W), UInt(8.W)))).suggestName("io") io.x := 1.U io.y := 1.U } } } + it should "also work when giving directions to the fields" in { + elaborate { new Module { + val io = IO(new BundleWithArgumentField(Input(UInt(8.W)), Output(UInt(8.W)))).suggestName("io") + io.y := io.x + } } + } + "Bundles inside companion objects" should "not need clonetype" in { - ChiselStage.elaborate { new Module { - val io = IO(Output(new CompanionObjectWithBundle.Inner)) + elaborate { new Module { + val io = IO(Output(new CompanionObjectWithBundle.Inner)).suggestName("io") io.data := 1.U } } } "Parameterized bundles inside companion objects" should "not need clonetype" in { - ChiselStage.elaborate { new Module { - val io = IO(Output(new CompanionObjectWithBundle.ParameterizedInner(8))) + elaborate { new Module { + val io = IO(Output(new CompanionObjectWithBundle.ParameterizedInner(8))).suggestName("io") io.data := 1.U } } } "Nested directioned anonymous Bundles" should "not need clonetype" in { - ChiselStage.elaborate { new Module { - val io = IO(new NestedAnonymousBundle) + elaborate { new Module { + val io = IO(new NestedAnonymousBundle).suggestName("io") val a = WireDefault(io) io.a.a := 1.U } } } "3.0 null compatibility" should "not need clonetype" in { - ChiselStage.elaborate { new Module { + elaborate { new Module { class InnerClassThing { def createBundle: Bundle = new Bundle { val a = Output(UInt(8.W)) } } - val io = IO((new InnerClassThing).createBundle) + val io = IO((new InnerClassThing).createBundle).suggestName("io") val a = WireDefault(io) } } } "Aliased fields" should "be caught" in { a [ChiselException] should be thrownBy extractCause[ChiselException] { - ChiselStage.elaborate { new Module { + elaborate { new Module { val bundleFieldType = UInt(8.W) val io = IO(Output(new Bundle { val a = bundleFieldType - })) + })).suggestName("io") io.a := 0.U } } } @@ -190,8 +210,8 @@ class AutoClonetypeSpec extends ChiselFlatSpec with Utils { val a = typeTuple._1 } - ChiselStage.elaborate { new Module { - val io = IO(Output(new BadBundle(UInt(8.W), 1))) + elaborate { new Module { + val io = IO(Output(new BadBundle(UInt(8.W), 1))).suggestName("io") io.a := 0.U } } } @@ -204,7 +224,7 @@ class AutoClonetypeSpec extends ChiselFlatSpec with Utils { val x = Output(UInt(3.W)) })) } - ChiselStage.elaborate { new TestModule } + elaborate { new TestModule } } "Wrapped IO construction with parent references" should "not fail for autoclonetype" in { @@ -216,6 +236,97 @@ class AutoClonetypeSpec extends ChiselFlatSpec with Utils { val x = Output(UInt(blah.W)) })) } - ChiselStage.elaborate { new TestModule(3) } + elaborate { new TestModule(3) } + } + + "Autoclonetype" should "support Bundles with if-blocks" in { + class MyModule(n: Int) extends MultiIOModule { + val io = IO(new Bundle { + val in = Input(UInt(8.W)) + val out = Output(UInt(8.W)) + if (n > 4) { + println("Here we are!") + } + }) + io.out := io.in + } + elaborate(new MyModule(3)) + } + + // New tests from the plugin + if (usingPlugin) { + behavior of "Compiler Plugin Autoclonetype" + + it should "support Bundles with non-val parameters" in { + class MyBundle(i: Int) extends Bundle { + val foo = UInt(i.W) + } + elaborate { new MultiIOModule { + val in = IO(Input(new MyBundle(8))) + val out = IO(Output(new MyBundle(8))) + out := in + }} + } + + it should "support type-parameterized Bundles" in { + class MyBundle[T <: Data](gen: T) extends Bundle { + val foo = gen + } + elaborate { new MultiIOModule { + val in = IO(Input(new MyBundle(UInt(8.W)))) + val out = IO(Output(new MyBundle(UInt(8.W)))) + out := in + }} + } + + it should "support Bundles with non-val implicit parameters" in { + class MyBundle(implicit i: Int) extends Bundle { + val foo = UInt(i.W) + } + elaborate { new MultiIOModule { + implicit val x = 8 + val in = IO(Input(new MyBundle)) + val out = IO(Output(new MyBundle)) + out := in + }} + } + + it should "support Bundles with multiple parameter lists" in { + class MyBundle(i: Int)(j: Int, jj: Int)(k: UInt) extends Bundle { + val foo = UInt((i + j + jj + k.getWidth).W) + } + elaborate { + new MultiIOModule { + val in = IO(Input(new MyBundle(8)(8, 8)(UInt(8.W)))) + val out = IO(Output(new MyBundle(8)(8, 8)(UInt(8.W)))) + out := in + } + } + } + + it should "support Bundles that implement their own cloneType" in { + class MyBundle(i: Int) extends Bundle { + val foo = UInt(i.W) + override def cloneType = new MyBundle(i).asInstanceOf[this.type] + } + elaborate { new MultiIOModule { + val in = IO(Input(new MyBundle(8))) + val out = IO(Output(new MyBundle(8))) + out := in + }} + } + + it should "support Bundles that capture type parameters from their parent scope" in { + class MyModule[T <: Data](gen: T) extends MultiIOModule { + class MyBundle(n: Int) extends Bundle { + val foo = Vec(n, gen) + } + val in = IO(Input(new MyBundle(4))) + val out = IO(Output(new MyBundle(4))) + out := in + } + elaborate(new MyModule(UInt(8.W))) + } + } } diff --git a/src/test/scala/chiselTests/AutoNestedCloneSpec.scala b/src/test/scala/chiselTests/AutoNestedCloneSpec.scala index 8e40ad20..401766e2 100644 --- a/src/test/scala/chiselTests/AutoNestedCloneSpec.scala +++ b/src/test/scala/chiselTests/AutoNestedCloneSpec.scala @@ -1,10 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 package chiselTests -import Chisel.ChiselException -import org.scalatest._ import chisel3._ -import chisel3.stage.ChiselStage +import chisel3.testers.TestUtils import org.scalatest.matchers.should.Matchers class BundleWithAnonymousInner(val w: Int) extends Bundle { @@ -13,11 +11,15 @@ class BundleWithAnonymousInner(val w: Int) extends Bundle { } } +// TODO all `.suggestNames` are due to https://github.com/chipsalliance/chisel3/issues/1802 class AutoNestedCloneSpec extends ChiselFlatSpec with Matchers with Utils { + val usingPlugin: Boolean = TestUtils.usingPlugin + val elaborate = TestUtils.elaborateNoReflectiveAutoCloneType _ + behavior of "autoCloneType of inner Bundle in Chisel3" it should "clone a doubly-nested inner bundle successfully" in { - ChiselStage.elaborate { + elaborate { class Outer(val w: Int) extends Module { class Middle(val w: Int) { class InnerIOType extends Bundle { @@ -25,7 +27,7 @@ class AutoNestedCloneSpec extends ChiselFlatSpec with Matchers with Utils { } def getIO: InnerIOType = new InnerIOType } - val io = IO(new Bundle {}) + val io = IO(new Bundle {}).suggestName("io") val myWire = Wire((new Middle(w)).getIO) } new Outer(2) @@ -33,9 +35,9 @@ class AutoNestedCloneSpec extends ChiselFlatSpec with Matchers with Utils { } it should "clone an anonymous inner bundle successfully" in { - ChiselStage.elaborate { + elaborate { class TestTop(val w: Int) extends Module { - val io = IO(new Bundle {}) + val io = IO(new Bundle {}).suggestName("io") val myWire = Wire(new Bundle{ val a = UInt(w.W) }) } new TestTop(2) @@ -43,18 +45,18 @@ class AutoNestedCloneSpec extends ChiselFlatSpec with Matchers with Utils { } it should "pick the correct $outer instance for an anonymous inner bundle" in { - ChiselStage.elaborate { + elaborate { class Inner(val w: Int) extends Module { val io = IO(new Bundle{ val in = Input(UInt(w.W)) val out = Output(UInt(w.W)) - }) + }).suggestName("io") } class Outer(val w: Int) extends Module { val io = IO(new Bundle{ val in = Input(UInt(w.W)) val out = Output(UInt(w.W)) - }) + }).suggestName("io") val i = Module(new Inner(w)) val iw = Wire(chiselTypeOf(i.io)) iw <> io @@ -65,9 +67,9 @@ class AutoNestedCloneSpec extends ChiselFlatSpec with Matchers with Utils { } it should "clone an anonymous, bound, inner bundle of another bundle successfully" in { - ChiselStage.elaborate { + elaborate { class TestModule(w: Int) extends Module { - val io = IO(new BundleWithAnonymousInner(w) ) + val io = IO(new BundleWithAnonymousInner(w) ).suggestName("io") val w0 = WireDefault(io) val w1 = WireDefault(io.inner) } @@ -76,14 +78,14 @@ class AutoNestedCloneSpec extends ChiselFlatSpec with Matchers with Utils { } it should "clone an anonymous, inner bundle of a Module, bound to another bundle successfully" in { - ChiselStage.elaborate { + elaborate { class TestModule(w: Int) extends Module { val bun = new Bundle { val foo = UInt(w.W) } val io = IO(new Bundle { val inner = Input(bun) - }) + }).suggestName("io") val w0 = WireDefault(io) val w1 = WireDefault(io.inner) } @@ -92,31 +94,48 @@ class AutoNestedCloneSpec extends ChiselFlatSpec with Matchers with Utils { } it should "clone a double-nested anonymous Bundle" in { - ChiselStage.elaborate { + elaborate { class TestModule() extends Module { val io = IO(new Bundle { val inner = Input(new Bundle { val x = UInt(8.W) }) - }) + }).suggestName("io") } new TestModule() } } - // Test ignored because the compatibility null-inserting autoclonetype doesn't trip this - ignore should "fail on anonymous doubly-nested inner bundle with clear error" in { - intercept[ChiselException] { extractCause[ChiselException] { ChiselStage.elaborate { - class Outer(val w: Int) extends Module { - class Middle(val w: Int) { - def getIO: Bundle = new Bundle { - val in = Input(UInt(w.W)) + if (usingPlugin) { + // This works with the plugin, but is a null pointer exception when using reflective autoclonetype + it should "support an anonymous doubly-nested inner bundle" in { + elaborate { + class Outer(val w: Int) extends Module { + class Middle(val w: Int) { + def getIO: Bundle = new Bundle { + val in = Input(UInt(w.W)) + } } + val io = IO(new Bundle {}).suggestName("io") + val myWire = Wire((new Middle(w)).getIO) } - val io = IO(new Bundle {}) - val myWire = Wire((new Middle(w)).getIO) + new Outer(2) } - new Outer(2) - }}}.getMessage should include("Unable to determine instance") + } + + it should "support anonymous Inner bundles that capture type parameters from outer Bundles" in { + elaborate(new MultiIOModule { + class MyBundle[T <: Data](n: Int, gen: T) extends Bundle { + val foo = new Bundle { + val x = Input(Vec(n, gen)) + } + val bar = Output(Option(new { def mkBundle = new Bundle { val x = Vec(n, gen) }}).get.mkBundle) + } + val io = IO(new MyBundle(4, UInt(8.W))) + val myWire = WireInit(io.foo) + val myWire2 = WireInit(io.bar) + io.bar.x := io.foo.x + }) + } } } |
