diff options
| -rw-r--r-- | build.sbt | 2 | ||||
| -rw-r--r-- | core/src/main/scala/chisel3/Aggregate.scala | 62 | ||||
| -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 | 136 | ||||
| -rw-r--r-- | plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselComponent.scala | 215 | ||||
| -rw-r--r-- | plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselPlugin.scala | 228 | ||||
| -rw-r--r-- | src/test/scala/chisel3/testers/TestUtils.scala | 12 | ||||
| -rw-r--r-- | src/test/scala/chiselTests/AutoClonetypeSpec.scala | 196 | ||||
| -rw-r--r-- | src/test/scala/chiselTests/AutoNestedCloneSpec.scala | 73 |
12 files changed, 649 insertions, 285 deletions
@@ -179,6 +179,8 @@ lazy val chisel = (project in file(".")). mimaPreviousArtifacts := Set(), libraryDependencies += defaultVersions("treadle") % "test", scalacOptions in Test ++= Seq("-language:reflectiveCalls"), + // Only used in Test for 3.4.x, used in Compile in 3.5 + scalacOptions in Test += "-P:chiselplugin:useBundlePlugin", scalacOptions in Compile in doc ++= Seq( "-diagrams", "-groups", diff --git a/core/src/main/scala/chisel3/Aggregate.scala b/core/src/main/scala/chisel3/Aggregate.scala index 6942313e..c5a917fa 100644 --- a/core/src/main/scala/chisel3/Aggregate.scala +++ b/core/src/main/scala/chisel3/Aggregate.scala @@ -798,17 +798,50 @@ 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 - // For autoclonetype, record possible candidates for outer instance. + // For reflective autoclonetype, record possible candidates for outer instance. // _outerInst should always take precedence, since it should be propagated from the original // object which has the most accurate context. - private val _containingModule: Option[BaseModule] = Builder.currentModule - private val _containingBundles: Seq[Bundle] = Builder.updateBundleStack(this) + private val _containingModule: Option[BaseModule] = if (_usingPlugin) None else Builder.currentModule + private val _containingBundles: Seq[Bundle] = if (_usingPlugin) Nil else Builder.updateBundleStack(this) + + 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 + } - override def cloneType : this.type = { + /** 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..96851e95 --- /dev/null +++ b/plugin/src/main/scala-2.12/chisel3/internal/plugin/BundleComponent.scala @@ -0,0 +1,136 @@ +// 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, arguments: ChiselPluginArguments) + 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('.') + val scalaVersionOk = scalaVersion(0).toInt == 2 && scalaVersion(1).toInt >= 12 + if (scalaVersionOk && arguments.useBundlePlugin) { + unit.body = new MyTypingTransformer(unit).transform(unit.body) + } else { + val reason = if (!scalaVersionOk) { + s"invalid Scala version '${scala.util.Properties.versionNumberString}'" + } else { + s"not enabled via '${arguments.useBundlePluginFullOpt}'" + } + // Enable this with scalacOption '-Ylog:chiselbundlephase' + global.log(s"Skipping BundleComponent on account of $reason.") + } + } + } + + 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 new file mode 100644 index 00000000..b1302ba3 --- /dev/null +++ b/plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselComponent.scala @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.internal.plugin + +import scala.collection.mutable +import scala.reflect.internal.Flags +import scala.tools.nsc +import scala.tools.nsc.{Global, Phase} +import scala.tools.nsc.plugins.PluginComponent +import scala.tools.nsc.transform.TypingTransformers + +// The component of the chisel plugin. Not sure exactly what the difference is between +// a Plugin and a PluginComponent. +class ChiselComponent(val global: Global) extends PluginComponent with TypingTransformers { + import global._ + val runsAfter: List[String] = List[String]("typer") + val phaseName: String = "chiselcomponent" + def newPhase(_prev: Phase): ChiselComponentPhase = new ChiselComponentPhase(_prev) + 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 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) + } + } + } + + class MyTypingTransformer(unit: CompilationUnit) + extends TypingTransformer(unit) { + + private def shouldMatchGen(bases: Tree*): Type => Boolean = { + val cache = mutable.HashMap.empty[Type, Boolean] + val baseTypes = bases.map(inferType) + + // If subtype of one of the base types, it's a match! + def terminate(t: Type): Boolean = baseTypes.exists(t <:< _) + + // Recurse through subtype hierarchy finding containers + // Seen is only updated when we recurse into type parameters, thus it is typically small + def recShouldMatch(s: Type, seen: Set[Type]): Boolean = { + def outerMatches(t: Type): Boolean = { + val str = t.toString + str.startsWith("Option[") || str.startsWith("Iterable[") + } + if (terminate(s)) { + true + } else if (seen.contains(s)) { + false + } else if (outerMatches(s)) { + // These are type parameters, loops *are* possible here + recShouldMatch(s.typeArgs.head, seen + s) + } else { + // This is the standard inheritance hierarchy, Scalac catches loops here + s.parents.exists( p => recShouldMatch(p, seen) ) + } + } + + // If doesn't match container pattern, exit early + def earlyExit(t: Type): Boolean = { + !(t.matchesPattern(inferType(tq"Iterable[_]")) || t.matchesPattern(inferType(tq"Option[_]"))) + } + + // Return function so that it captures the cache + { q: Type => + cache.getOrElseUpdate(q, { + // First check if a match, then check early exit, then recurse + if(terminate(q)){ + true + } else if(earlyExit(q)) { + false + } else { + recShouldMatch(q, Set.empty) + } + }) + } + } + + + private val shouldMatchData : Type => Boolean = shouldMatchGen(tq"chisel3.Data") + private val shouldMatchDataOrMem : Type => Boolean = shouldMatchGen(tq"chisel3.Data", tq"chisel3.MemBase[_]") + private val shouldMatchModule : Type => Boolean = shouldMatchGen(tq"chisel3.experimental.BaseModule") + + // Given a type tree, infer the type and return it + private def inferType(t: Tree): Type = localTyper.typed(t, nsc.Mode.TYPEmode).tpe + + // Indicates whether a ValDef is properly formed to get name + private def okVal(dd: ValDef): Boolean = { + + // These were found through trial and error + def okFlags(mods: Modifiers): Boolean = { + val badFlags = Set( + Flag.PARAM, + Flag.SYNTHETIC, + Flag.DEFERRED, + Flags.TRIEDCOOKING, + Flags.CASEACCESSOR, + Flags.PARAMACCESSOR + ) + badFlags.forall{ x => !mods.hasFlag(x)} + } + + // Ensure expression isn't null, as you can't call `null.autoName("myname")` + val isNull = dd.rhs match { + case Literal(Constant(null)) => true + case _ => false + } + + okFlags(dd.mods) && !isNull && dd.rhs != EmptyTree + } + // TODO Unify with okVal + private def okUnapply(dd: ValDef): Boolean = { + + // These were found through trial and error + def okFlags(mods: Modifiers): Boolean = { + val badFlags = Set( + Flag.PARAM, + Flag.DEFERRED, + Flags.TRIEDCOOKING, + Flags.CASEACCESSOR, + Flags.PARAMACCESSOR + ) + val goodFlags = Set( + Flag.SYNTHETIC, + Flag.ARTIFACT + ) + goodFlags.forall(f => mods.hasFlag(f)) && badFlags.forall(f => !mods.hasFlag(f)) + } + + // Ensure expression isn't null, as you can't call `null.autoName("myname")` + val isNull = dd.rhs match { + case Literal(Constant(null)) => true + case _ => false + } + val tpe = inferType(dd.tpt) + definitions.isTupleType(tpe) && okFlags(dd.mods) && !isNull && dd.rhs != EmptyTree + } + + private def findUnapplyNames(tree: Tree): Option[List[String]] = { + val applyArgs: Option[List[Tree]] = tree match { + case Match(_, List(CaseDef(_, _, Apply(_, args)))) => Some(args) + case _ => None + } + applyArgs.flatMap { args => + var ok = true + val result = mutable.ListBuffer[String]() + args.foreach { + case Ident(TermName(name)) => result += name + // Anything unexpected and we abort + case _ => ok = false + } + if (ok) Some(result.toList) else None + } + } + + // Whether this val is directly enclosed by a Bundle type + private def inBundle(dd: ValDef): Boolean = { + dd.symbol.logicallyEnclosingMember.thisType <:< inferType(tq"chisel3.Bundle") + } + + private def stringFromTermName(name: TermName): String = + name.toString.trim() // Remove trailing space (Scalac implementation detail) + + // Method called by the compiler to modify source tree + override def transform(tree: Tree): Tree = tree match { + // Check if a subtree is a candidate + case dd @ ValDef(mods, name, tpt, rhs) if okVal(dd) => + val tpe = inferType(tpt) + // If a Data and in a Bundle, just get the name but not a prefix + if (shouldMatchData(tpe) && inBundle(dd)) { + val str = stringFromTermName(name) + val newRHS = transform(rhs) // chisel3.internal.plugin.autoNameRecursively + val named = q"chisel3.internal.plugin.autoNameRecursively($str)($newRHS)" + treeCopy.ValDef(dd, mods, name, tpt, localTyper typed named) + } + // If a Data or a Memory, get the name and a prefix + else if (shouldMatchDataOrMem(tpe)) { + val str = stringFromTermName(name) + val newRHS = transform(rhs) + val prefixed = q"chisel3.experimental.prefix.apply[$tpt](name=$str)(f=$newRHS)" + val named = q"chisel3.internal.plugin.autoNameRecursively($str)($prefixed)" + treeCopy.ValDef(dd, mods, name, tpt, localTyper typed named) + // If an instance, just get a name but no prefix + } else if (shouldMatchModule(tpe)) { + val str = stringFromTermName(name) + val newRHS = transform(rhs) + val named = q"chisel3.internal.plugin.autoNameRecursively($str)($newRHS)" + treeCopy.ValDef(dd, mods, name, tpt, localTyper typed named) + } else { + // Otherwise, continue + super.transform(tree) + } + case dd @ ValDef(mods, name, tpt, rhs @ Match(_, _)) if okUnapply(dd) => + val tpe = inferType(tpt) + val fieldsOfInterest: List[Boolean] = tpe.typeArgs.map(shouldMatchData) + // Only transform if at least one field is of interest + if (fieldsOfInterest.reduce(_ || _)) { + findUnapplyNames(rhs) match { + case Some(names) => + val onames: List[Option[String]] = fieldsOfInterest.zip(names).map { case (ok, name) => if (ok) Some(name) else None } + val named = q"chisel3.internal.plugin.autoNameRecursivelyProduct($onames)($rhs)" + treeCopy.ValDef(dd, mods, name, tpt, localTyper typed named) + case None => // It's not clear how this could happen but we don't want to crash + super.transform(tree) + } + } else { + super.transform(tree) + } + // Otherwise, continue + case _ => super.transform(tree) + } + } +} 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 59be7588..a0651e1d 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 @@ -1,223 +1,37 @@ -// See LICENSE for license details. +// SPDX-License-Identifier: Apache-2.0 package chisel3.internal.plugin import scala.tools.nsc -import nsc.{Global, Phase} -import nsc.plugins.Plugin -import nsc.plugins.PluginComponent -import scala.reflect.internal.Flags -import scala.tools.nsc.transform.TypingTransformers +import nsc.Global +import nsc.plugins.{Plugin, PluginComponent} -import scala.collection.mutable +private[plugin] case class ChiselPluginArguments(var useBundlePlugin: Boolean = false) { + def useBundlePluginOpt = "useBundlePlugin" + def useBundlePluginFullOpt = s"-P:chiselplugin:$useBundlePluginOpt" +} // The plugin to be run by the Scala compiler during compilation of Chisel code 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)) -} - -// The component of the chisel plugin. Not sure exactly what the difference is between -// a Plugin and a PluginComponent. -class ChiselComponent(val global: Global) extends PluginComponent with TypingTransformers { - import global._ - val runsAfter: List[String] = List[String]("typer") - val phaseName: String = "chiselcomponent" - def newPhase(_prev: Phase): ChiselComponentPhase = new ChiselComponentPhase(_prev) - 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, - // 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) { - unit.body = new MyTypingTransformer(unit).transform(unit.body) + private val arguments = ChiselPluginArguments() + val components: List[PluginComponent] = List[PluginComponent]( + new ChiselComponent(global), + new BundleComponent(global, arguments) + ) + + override def init(options: List[String], error: String => Unit): Boolean = { + for (option <- options) { + if (option == arguments.useBundlePluginOpt) { + arguments.useBundlePlugin = true + } else { + error(s"Option not understood: '$option'") } } + true } - class MyTypingTransformer(unit: CompilationUnit) - extends TypingTransformer(unit) { - - private def shouldMatchGen(bases: Tree*): Type => Boolean = { - val cache = mutable.HashMap.empty[Type, Boolean] - val baseTypes = bases.map(inferType) - - // If subtype of one of the base types, it's a match! - def terminate(t: Type): Boolean = baseTypes.exists(t <:< _) - // Recurse through subtype hierarchy finding containers - // Seen is only updated when we recurse into type parameters, thus it is typically small - def recShouldMatch(s: Type, seen: Set[Type]): Boolean = { - def outerMatches(t: Type): Boolean = { - val str = t.toString - str.startsWith("Option[") || str.startsWith("Iterable[") - } - if (terminate(s)) { - true - } else if (seen.contains(s)) { - false - } else if (outerMatches(s)) { - // These are type parameters, loops *are* possible here - recShouldMatch(s.typeArgs.head, seen + s) - } else { - // This is the standard inheritance hierarchy, Scalac catches loops here - s.parents.exists( p => recShouldMatch(p, seen) ) - } - } - - // If doesn't match container pattern, exit early - def earlyExit(t: Type): Boolean = { - !(t.matchesPattern(inferType(tq"Iterable[_]")) || t.matchesPattern(inferType(tq"Option[_]"))) - } - - // Return function so that it captures the cache - { q: Type => - cache.getOrElseUpdate(q, { - // First check if a match, then check early exit, then recurse - if(terminate(q)){ - true - } else if(earlyExit(q)) { - false - } else { - recShouldMatch(q, Set.empty) - } - }) - } - } - - - private val shouldMatchData : Type => Boolean = shouldMatchGen(tq"chisel3.Data") - private val shouldMatchDataOrMem : Type => Boolean = shouldMatchGen(tq"chisel3.Data", tq"chisel3.MemBase[_]") - private val shouldMatchModule : Type => Boolean = shouldMatchGen(tq"chisel3.experimental.BaseModule") - - // Given a type tree, infer the type and return it - private def inferType(t: Tree): Type = localTyper.typed(t, nsc.Mode.TYPEmode).tpe - - // Indicates whether a ValDef is properly formed to get name - private def okVal(dd: ValDef): Boolean = { - - // These were found through trial and error - def okFlags(mods: Modifiers): Boolean = { - val badFlags = Set( - Flag.PARAM, - Flag.SYNTHETIC, - Flag.DEFERRED, - Flags.TRIEDCOOKING, - Flags.CASEACCESSOR, - Flags.PARAMACCESSOR - ) - badFlags.forall{ x => !mods.hasFlag(x)} - } - - // Ensure expression isn't null, as you can't call `null.autoName("myname")` - val isNull = dd.rhs match { - case Literal(Constant(null)) => true - case _ => false - } - - okFlags(dd.mods) && !isNull && dd.rhs != EmptyTree - } - // TODO Unify with okVal - private def okUnapply(dd: ValDef): Boolean = { - - // These were found through trial and error - def okFlags(mods: Modifiers): Boolean = { - val badFlags = Set( - Flag.PARAM, - Flag.DEFERRED, - Flags.TRIEDCOOKING, - Flags.CASEACCESSOR, - Flags.PARAMACCESSOR - ) - val goodFlags = Set( - Flag.SYNTHETIC, - Flag.ARTIFACT - ) - goodFlags.forall(f => mods.hasFlag(f)) && badFlags.forall(f => !mods.hasFlag(f)) - } - - // Ensure expression isn't null, as you can't call `null.autoName("myname")` - val isNull = dd.rhs match { - case Literal(Constant(null)) => true - case _ => false - } - val tpe = inferType(dd.tpt) - definitions.isTupleType(tpe) && okFlags(dd.mods) && !isNull && dd.rhs != EmptyTree - } - - private def findUnapplyNames(tree: Tree): Option[List[String]] = { - val applyArgs: Option[List[Tree]] = tree match { - case Match(_, List(CaseDef(_, _, Apply(_, args)))) => Some(args) - case _ => None - } - applyArgs.flatMap { args => - var ok = true - val result = mutable.ListBuffer[String]() - args.foreach { - case Ident(TermName(name)) => result += name - // Anything unexpected and we abort - case _ => ok = false - } - if (ok) Some(result.toList) else None - } - } - - // Whether this val is directly enclosed by a Bundle type - private def inBundle(dd: ValDef): Boolean = { - dd.symbol.logicallyEnclosingMember.thisType <:< inferType(tq"chisel3.Bundle") - } - - private def stringFromTermName(name: TermName): String = - name.toString.trim() // Remove trailing space (Scalac implementation detail) - - // Method called by the compiler to modify source tree - override def transform(tree: Tree): Tree = tree match { - // Check if a subtree is a candidate - case dd @ ValDef(mods, name, tpt, rhs) if okVal(dd) => - val tpe = inferType(tpt) - // If a Data and in a Bundle, just get the name but not a prefix - if (shouldMatchData(tpe) && inBundle(dd)) { - val str = stringFromTermName(name) - val newRHS = transform(rhs) // chisel3.internal.plugin.autoNameRecursively - val named = q"chisel3.internal.plugin.autoNameRecursively($str)($newRHS)" - treeCopy.ValDef(dd, mods, name, tpt, localTyper typed named) - } - // If a Data or a Memory, get the name and a prefix - else if (shouldMatchDataOrMem(tpe)) { - val str = stringFromTermName(name) - val newRHS = transform(rhs) - val prefixed = q"chisel3.experimental.prefix.apply[$tpt](name=$str)(f=$newRHS)" - val named = q"chisel3.internal.plugin.autoNameRecursively($str)($prefixed)" - treeCopy.ValDef(dd, mods, name, tpt, localTyper typed named) - // If an instance, just get a name but no prefix - } else if (shouldMatchModule(tpe)) { - val str = stringFromTermName(name) - val newRHS = transform(rhs) - val named = q"chisel3.internal.plugin.autoNameRecursively($str)($newRHS)" - treeCopy.ValDef(dd, mods, name, tpt, localTyper typed named) - } else { - // Otherwise, continue - super.transform(tree) - } - case dd @ ValDef(mods, name, tpt, rhs @ Match(_, _)) if okUnapply(dd) => - val tpe = inferType(tpt) - val fieldsOfInterest: List[Boolean] = tpe.typeArgs.map(shouldMatchData) - // Only transform if at least one field is of interest - if (fieldsOfInterest.reduce(_ || _)) { - findUnapplyNames(rhs) match { - case Some(names) => - val onames: List[Option[String]] = fieldsOfInterest.zip(names).map { case (ok, name) => if (ok) Some(name) else None } - val named = q"chisel3.internal.plugin.autoNameRecursivelyProduct($onames)($rhs)" - treeCopy.ValDef(dd, mods, name, tpt, localTyper typed named) - case None => // It's not clear how this could happen but we don't want to crash - super.transform(tree) - } - } else { - super.transform(tree) - } - // Otherwise, continue - case _ => super.transform(tree) - } - } } + 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..a6e5562a 100644 --- a/src/test/scala/chiselTests/AutoClonetypeSpec.scala +++ b/src/test/scala/chiselTests/AutoClonetypeSpec.scala @@ -3,7 +3,8 @@ package chiselTests import chisel3._ -import chisel3.stage.ChiselStage +import chisel3.testers.TestUtils +import chisel3.util.QueueIO class BundleWithIntArg(val i: Int) extends Bundle { val out = UInt(i.W) @@ -65,10 +66,19 @@ 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 +// Needs to be top-level so that reflective autoclonetype works +class InheritingBundle extends QueueIO(UInt(8.W), 8) { + val error = Output(Bool()) +} + +// 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 +86,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 +97,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 +109,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 +127,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 +216,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 +230,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 +242,108 @@ 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)) + } + + behavior of "Compiler Plugin Autoclonetype" + + // Necessary test for 3.4.x, but we will break this (for non-plugin users) in 3.5 + it should "NOT break code that extends chisel3.util Bundles (whether they use the plugin or not)" in { + class MyModule extends MultiIOModule { + val io = IO(new InheritingBundle) + io.deq <> io.enq + io.count := 0.U + io.error := true.B + } + elaborate(new MyModule) + } + + // New tests from the plugin + if (usingPlugin) { + 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 + }) + } } } |
