summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJack Koenig2021-03-12 16:40:31 -0800
committerGitHub2021-03-12 16:40:31 -0800
commitb3592a4973a50098f71e0e64bf3f0f53a82a82e9 (patch)
tree0d2df183b022697a496d58a3be13b85d141cf7fc
parent9ea57e031f834841609a30031ddab08fe4f47029 (diff)
parent3bea6167159737b379f37031c3beef27337be06d (diff)
Merge pull request #1804 from chipsalliance/autoclonetype2
Compiler plugin implemented autoclonetype
-rw-r--r--build.sbt2
-rw-r--r--core/src/main/scala/chisel3/Aggregate.scala62
-rw-r--r--core/src/main/scala/chisel3/internal/Builder.scala8
l---------no-plugin-tests/src/test/scala/chiselTests/AutoClonetypeSpec.scala1
l---------no-plugin-tests/src/test/scala/chiselTests/AutoNestedCloneSpec.scala1
-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.scala136
-rw-r--r--plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselComponent.scala215
-rw-r--r--plugin/src/main/scala-2.12/chisel3/internal/plugin/ChiselPlugin.scala228
-rw-r--r--src/test/scala/chisel3/testers/TestUtils.scala12
-rw-r--r--src/test/scala/chiselTests/AutoClonetypeSpec.scala196
-rw-r--r--src/test/scala/chiselTests/AutoNestedCloneSpec.scala73
12 files changed, 649 insertions, 285 deletions
diff --git a/build.sbt b/build.sbt
index 5c051ff5..42fcece3 100644
--- a/build.sbt
+++ b/build.sbt
@@ -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
+ })
+ }
}
}