summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdam Izraelevitz2019-08-12 15:49:42 -0700
committerGitHub2019-08-12 15:49:42 -0700
commitfddb5943b1d36925a5435d327c3312572e98ca58 (patch)
treeb22e3a544dbb265dead955544c75bf7abddb7c69
parent466ffbc9ca4fcca73d56f849df9e2753f68c53a8 (diff)
Aspect-Oriented Programming for Chisel (#1077)
Added Aspects to Chisel, enabling a mechanism for dependency injection to hardware modules.
-rw-r--r--chiselFrontend/src/main/scala/chisel3/Annotation.scala6
-rw-r--r--chiselFrontend/src/main/scala/chisel3/Data.scala4
-rw-r--r--chiselFrontend/src/main/scala/chisel3/Mem.scala2
-rw-r--r--chiselFrontend/src/main/scala/chisel3/Module.scala85
-rw-r--r--chiselFrontend/src/main/scala/chisel3/ModuleAspect.scala27
-rw-r--r--chiselFrontend/src/main/scala/chisel3/aop/Aspect.scala40
-rw-r--r--chiselFrontend/src/main/scala/chisel3/internal/Binding.scala10
-rw-r--r--chiselFrontend/src/main/scala/chisel3/internal/Builder.scala62
-rw-r--r--chiselFrontend/src/main/scala/chisel3/internal/firrtl/Converter.scala (renamed from src/main/scala/chisel3/internal/firrtl/Converter.scala)0
-rw-r--r--src/main/scala/chisel3/Driver.scala17
-rw-r--r--src/main/scala/chisel3/aop/Select.scala418
-rw-r--r--src/main/scala/chisel3/aop/injecting/InjectStatement.scala21
-rw-r--r--src/main/scala/chisel3/aop/injecting/InjectingAspect.scala63
-rw-r--r--src/main/scala/chisel3/aop/injecting/InjectingTransform.scala46
-rw-r--r--src/main/scala/chisel3/stage/ChiselAnnotations.scala15
-rw-r--r--src/main/scala/chisel3/stage/ChiselStage.scala1
-rw-r--r--src/main/scala/chisel3/stage/phases/AspectPhase.scala37
-rw-r--r--src/main/scala/chisel3/stage/phases/Convert.scala9
-rw-r--r--src/main/scala/chisel3/stage/phases/Elaborate.scala2
-rw-r--r--src/main/scala/chisel3/stage/phases/MaybeAspectPhase.scala18
-rw-r--r--src/main/scala/chisel3/testers/TesterDriver.scala24
-rw-r--r--src/test/scala/chiselTests/ChiselSpec.scala36
-rw-r--r--src/test/scala/chiselTests/aop/InjectionSpec.scala58
-rw-r--r--src/test/scala/chiselTests/aop/SelectSpec.scala144
-rw-r--r--src/test/scala/chiselTests/stage/ChiselAnnotationsSpec.scala12
25 files changed, 1072 insertions, 85 deletions
diff --git a/chiselFrontend/src/main/scala/chisel3/Annotation.scala b/chiselFrontend/src/main/scala/chisel3/Annotation.scala
index 08119f44..ec000d93 100644
--- a/chiselFrontend/src/main/scala/chisel3/Annotation.scala
+++ b/chiselFrontend/src/main/scala/chisel3/Annotation.scala
@@ -3,11 +3,11 @@
package chisel3.experimental
import scala.language.existentials
-
import chisel3.internal.{Builder, InstanceId}
import chisel3.{CompileOptions, Data}
import firrtl.Transform
-import firrtl.annotations.Annotation
+import firrtl.annotations._
+import firrtl.options.Unserializable
import firrtl.transforms.{DontTouchAnnotation, NoDedupAnnotation}
/** Interface for Annotations in Chisel
@@ -25,11 +25,11 @@ trait ChiselAnnotation {
* Automatic Transform instantiation is *not* supported when the Circuit and Annotations are serialized before invoking
* FIRRTL.
*/
-// TODO There should be a FIRRTL API for this instead
trait RunFirrtlTransform extends ChiselAnnotation {
def transformClass: Class[_ <: Transform]
}
+
// This exists for implementation reasons, we don't want people using this type directly
final case class ChiselLegacyAnnotation private[chisel3] (
component: InstanceId,
diff --git a/chiselFrontend/src/main/scala/chisel3/Data.scala b/chiselFrontend/src/main/scala/chisel3/Data.scala
index 37c9622f..c6d98b9c 100644
--- a/chiselFrontend/src/main/scala/chisel3/Data.scala
+++ b/chiselFrontend/src/main/scala/chisel3/Data.scala
@@ -385,7 +385,7 @@ abstract class Data extends HasId with NamedComponent with SourceInfoDoc { // sc
case _ => // fine
}
try {
- MonoConnect.connect(sourceInfo, connectCompileOptions, this, that, Builder.forcedUserModule)
+ MonoConnect.connect(sourceInfo, connectCompileOptions, this, that, Builder.referenceUserModule)
} catch {
case MonoConnectException(message) =>
throwException(
@@ -407,7 +407,7 @@ abstract class Data extends HasId with NamedComponent with SourceInfoDoc { // sc
case _ => // fine
}
try {
- BiConnect.connect(sourceInfo, connectCompileOptions, this, that, Builder.forcedUserModule)
+ BiConnect.connect(sourceInfo, connectCompileOptions, this, that, Builder.referenceUserModule)
} catch {
case BiConnectException(message) =>
throwException(
diff --git a/chiselFrontend/src/main/scala/chisel3/Mem.scala b/chiselFrontend/src/main/scala/chisel3/Mem.scala
index 26c4a0b0..7fbbaefd 100644
--- a/chiselFrontend/src/main/scala/chisel3/Mem.scala
+++ b/chiselFrontend/src/main/scala/chisel3/Mem.scala
@@ -41,7 +41,7 @@ object Mem {
do_apply(BigInt(size), t)(sourceInfo, compileOptions)
}
-sealed abstract class MemBase[T <: Data](t: T, val length: BigInt) extends HasId with NamedComponent with SourceInfoDoc {
+sealed abstract class MemBase[T <: Data](val t: T, val length: BigInt) extends HasId with NamedComponent with SourceInfoDoc {
// REVIEW TODO: make accessors (static/dynamic, read/write) combinations consistent.
/** Creates a read accessor into the memory with static addressing. See the
diff --git a/chiselFrontend/src/main/scala/chisel3/Module.scala b/chiselFrontend/src/main/scala/chisel3/Module.scala
index c8527f34..a6f682a8 100644
--- a/chiselFrontend/src/main/scala/chisel3/Module.scala
+++ b/chiselFrontend/src/main/scala/chisel3/Module.scala
@@ -14,8 +14,7 @@ import chisel3.internal.Builder._
import chisel3.internal.firrtl._
import chisel3.internal.sourceinfo.{InstTransform, SourceInfo}
import chisel3.experimental.BaseModule
-
-import _root_.firrtl.annotations.{CircuitName, ModuleName}
+import _root_.firrtl.annotations.{ModuleName, ModuleTarget, IsModule}
object Module extends SourceInfoDoc {
/** A wrapper method that all Module instantiations must be wrapped in
@@ -180,15 +179,21 @@ package experimental {
private[chisel3] val _namespace = Namespace.empty
private val _ids = ArrayBuffer[HasId]()
private[chisel3] def addId(d: HasId) {
- require(!_closed, "Can't write to module after module close")
- _ids += d
+ if (Builder.aspectModule(this).isDefined) {
+ aspectModule(this).get.addId(d)
+ } else {
+ require(!_closed, "Can't write to module after module close")
+ _ids += d
+ }
}
+
protected def getIds = {
require(_closed, "Can't get ids before module close")
_ids.toSeq
}
private val _ports = new ArrayBuffer[Data]()
+
// getPorts unfortunately already used for tester compatibility
protected[chisel3] def getModulePorts = {
require(_closed, "Can't get ports before module close")
@@ -198,6 +203,7 @@ package experimental {
// These methods allow checking some properties of ports before the module is closed,
// mainly for compatibility purposes.
protected def portsContains(elem: Data): Boolean = _ports contains elem
+
protected def portsSize: Int = _ports.size
/** Generates the FIRRTL Component (Module or Blackbox) of this Module.
@@ -227,19 +233,38 @@ package experimental {
}
/** Returns a FIRRTL ModuleName that references this object
+ *
* @note Should not be called until circuit elaboration is complete
*/
- final def toNamed: ModuleName = ModuleName(this.name, CircuitName(this.circuitName))
+ @deprecated("toNamed API is deprecated -- use toTarget instead", "3.2")
+ final def toNamed: ModuleName = toTarget.toNamed
+
+ /** Returns a FIRRTL ModuleTarget that references this object
+ *
+ * @note Should not be called until circuit elaboration is complete
+ */
+ final def toTarget: ModuleTarget = ModuleTarget(this.circuitName, this.name)
+
+ /** Returns a FIRRTL ModuleTarget that references this object
+ *
+ * @note Should not be called until circuit elaboration is complete
+ */
+ final def toAbsoluteTarget: IsModule = {
+ _parent match {
+ case Some(parent) => parent.toAbsoluteTarget.instOf(this.instanceName, toTarget.module)
+ case None => toTarget
+ }
+ }
/**
- * Internal API. Returns a list of this module's generated top-level ports as a map of a String
- * (FIRRTL name) to the IO object. Only valid after the module is closed.
- *
- * Note: for BlackBoxes (but not ExtModules), this returns the contents of the top-level io
- * object, consistent with what is emitted in FIRRTL.
- *
- * TODO: Use SeqMap/VectorMap when those data structures become available.
- */
+ * Internal API. Returns a list of this module's generated top-level ports as a map of a String
+ * (FIRRTL name) to the IO object. Only valid after the module is closed.
+ *
+ * Note: for BlackBoxes (but not ExtModules), this returns the contents of the top-level io
+ * object, consistent with what is emitted in FIRRTL.
+ *
+ * TODO: Use SeqMap/VectorMap when those data structures become available.
+ */
private[chisel3] def getChiselPorts: Seq[(String, Data)] = {
require(_closed, "Can't get ports before module close")
_component.get.ports.map { port =>
@@ -313,31 +338,33 @@ package experimental {
}
}
}
+
assignCompatDir(iodef, false)
iodef.bind(PortBinding(this))
_ports += iodef
}
+
/** Private accessor for _bindIoInPlace */
private[chisel3] def bindIoInPlace(iodef: Data): Unit = _bindIoInPlace(iodef)
/**
- * This must wrap the datatype used to set the io field of any Module.
- * i.e. All concrete modules must have defined io in this form:
- * [lazy] val io[: io type] = IO(...[: io type])
- *
- * Items in [] are optional.
- *
- * The granted iodef must be a chisel type and not be bound to hardware.
- *
- * Also registers a Data as a port, also performing bindings. Cannot be called once ports are
- * requested (so that all calls to ports will return the same information).
- * Internal API.
- *
- * TODO(twigg): Specifically walk the Data definition to call out which nodes
- * are problematic.
- */
- protected def IO[T<:Data](iodef: T): T = chisel3.experimental.IO.apply(iodef) // scalastyle:ignore method.name
+ * This must wrap the datatype used to set the io field of any Module.
+ * i.e. All concrete modules must have defined io in this form:
+ * [lazy] val io[: io type] = IO(...[: io type])
+ *
+ * Items in [] are optional.
+ *
+ * The granted iodef must be a chisel type and not be bound to hardware.
+ *
+ * Also registers a Data as a port, also performing bindings. Cannot be called once ports are
+ * requested (so that all calls to ports will return the same information).
+ * Internal API.
+ *
+ * TODO(twigg): Specifically walk the Data definition to call out which nodes
+ * are problematic.
+ */
+ protected def IO[T <: Data](iodef: T): T = chisel3.experimental.IO.apply(iodef) // scalastyle:ignore method.name
//
// Internal Functions
diff --git a/chiselFrontend/src/main/scala/chisel3/ModuleAspect.scala b/chiselFrontend/src/main/scala/chisel3/ModuleAspect.scala
new file mode 100644
index 00000000..3edf0a22
--- /dev/null
+++ b/chiselFrontend/src/main/scala/chisel3/ModuleAspect.scala
@@ -0,0 +1,27 @@
+// See LICENSE for license details.
+
+package chisel3
+
+import chisel3.internal.Builder
+import chisel3.experimental.RawModule
+
+/** Used by Chisel Aspects to inject Chisel code into modules, after they have been elaborated.
+ * This is an internal API - don't use!
+ *
+ * It adds itself as an aspect to the module, which allows proper checking of connection and binding legality.
+ *
+ * @param module Module for which this object is an aspect of
+ * @param moduleCompileOptions
+ */
+abstract class ModuleAspect private[chisel3] (module: RawModule)
+ (implicit moduleCompileOptions: CompileOptions) extends RawModule {
+
+ Builder.addAspect(module, this)
+
+ override def circuitName: String = module.toTarget.circuit
+
+ override def desiredName: String = module.name
+
+ override val _namespace = module._namespace
+}
+
diff --git a/chiselFrontend/src/main/scala/chisel3/aop/Aspect.scala b/chiselFrontend/src/main/scala/chisel3/aop/Aspect.scala
new file mode 100644
index 00000000..86f5b347
--- /dev/null
+++ b/chiselFrontend/src/main/scala/chisel3/aop/Aspect.scala
@@ -0,0 +1,40 @@
+// See LICENSE for license details.
+
+package chisel3.aop
+
+import chisel3.experimental.RawModule
+import firrtl.annotations.{Annotation, NoTargetAnnotation}
+import firrtl.options.Unserializable
+import firrtl.AnnotationSeq
+
+/** Represents an aspect of a Chisel module, by specifying
+ * what behavior should be done to instance, via the FIRRTL Annotation Mechanism
+ * @tparam T Type of top-level module
+ */
+abstract class Aspect[T <: RawModule] extends Annotation with Unserializable with NoTargetAnnotation {
+ /** Convert this Aspect to a seq of FIRRTL annotation
+ * @param top
+ * @return
+ */
+ def toAnnotation(top: T): AnnotationSeq
+
+ /** Called by [[chisel3.stage.phases.AspectPhase]] to resolve this Aspect into annotations
+ * @param top
+ * @return
+ */
+ private[chisel3] def resolveAspect(top: RawModule): AnnotationSeq = {
+ toAnnotation(top.asInstanceOf[T])
+ }
+}
+
+/** Holds utility functions for Aspect stuff */
+object Aspect {
+
+ /** Converts elaborated Chisel components to FIRRTL modules
+ * @param chiselIR
+ * @return
+ */
+ def getFirrtl(chiselIR: chisel3.internal.firrtl.Circuit): firrtl.ir.Circuit = {
+ chisel3.internal.firrtl.Converter.convert(chiselIR)
+ }
+}
diff --git a/chiselFrontend/src/main/scala/chisel3/internal/Binding.scala b/chiselFrontend/src/main/scala/chisel3/internal/Binding.scala
index 34de36a3..23e35f5c 100644
--- a/chiselFrontend/src/main/scala/chisel3/internal/Binding.scala
+++ b/chiselFrontend/src/main/scala/chisel3/internal/Binding.scala
@@ -74,7 +74,15 @@ sealed trait UnconstrainedBinding extends TopBinding {
// Location will track where this Module is, and the bound object can be referenced in FIRRTL
sealed trait ConstrainedBinding extends TopBinding {
def enclosure: BaseModule
- def location: Option[BaseModule] = Some(enclosure)
+ def location: Option[BaseModule] = {
+ // If an aspect is present, return the aspect module. Otherwise, return the enclosure module
+ // This allows aspect modules to pretend to be enclosed modules for connectivity checking,
+ // inside vs outside instance checking, etc.
+ Builder.aspectModule(enclosure) match {
+ case None => Some(enclosure)
+ case Some(aspect) => Some(aspect)
+ }
+ }
}
// A binding representing a data that cannot be (re)assigned to.
diff --git a/chiselFrontend/src/main/scala/chisel3/internal/Builder.scala b/chiselFrontend/src/main/scala/chisel3/internal/Builder.scala
index 664813f7..e03694a9 100644
--- a/chiselFrontend/src/main/scala/chisel3/internal/Builder.scala
+++ b/chiselFrontend/src/main/scala/chisel3/internal/Builder.scala
@@ -8,7 +8,9 @@ import chisel3._
import chisel3.experimental._
import chisel3.internal.firrtl._
import chisel3.internal.naming._
-import _root_.firrtl.annotations.{CircuitName, ComponentName, ModuleName, Named}
+import _root_.firrtl.annotations.{CircuitName, ComponentName, IsMember, ModuleName, Named, ReferenceTarget}
+
+import scala.collection.mutable
private[chisel3] class Namespace(keywords: Set[String]) {
private val names = collection.mutable.HashMap[String, Long]()
@@ -68,8 +70,12 @@ trait InstanceId {
def parentPathName: String
def parentModName: String
/** Returns a FIRRTL Named that refers to this object in the elaborated hardware graph */
+ @deprecated("toNamed API is deprecated -- use toTarget instead", "3.2")
def toNamed: Named
-
+ /** Returns a FIRRTL IsMember that refers to this object in the elaborated hardware graph */
+ def toTarget: IsMember
+ /** Returns a FIRRTL IsMember that refers to the absolute path to this object in the elaborated hardware graph */
+ def toAbsoluteTarget: IsMember
}
private[chisel3] trait HasId extends InstanceId {
@@ -163,8 +169,30 @@ private[chisel3] trait NamedComponent extends HasId {
/** Returns a FIRRTL ComponentName that references this object
* @note Should not be called until circuit elaboration is complete
*/
+ @deprecated("toNamed API is deprecated -- use toTarget instead", "3.2")
final def toNamed: ComponentName =
ComponentName(this.instanceName, ModuleName(this.parentModName, CircuitName(this.circuitName)))
+
+ /** Returns a FIRRTL ReferenceTarget that references this object
+ * @note Should not be called until circuit elaboration is complete
+ */
+ final def toTarget: ReferenceTarget = {
+ val name = this.instanceName
+ import _root_.firrtl.annotations.{Target, TargetToken}
+ Target.toTargetTokens(name).toList match {
+ case TargetToken.Ref(r) :: components => ReferenceTarget(this.circuitName, this.parentModName, Nil, r, components)
+ case other =>
+ throw _root_.firrtl.annotations.Target.NamedException(s"Cannot convert $name into [[ReferenceTarget]]: $other")
+ }
+ }
+
+ final def toAbsoluteTarget: ReferenceTarget = {
+ val localTarget = toTarget
+ _parent match {
+ case Some(parent) => parent.toAbsoluteTarget.ref(localTarget.ref).copy(component = localTarget.component)
+ case None => localTarget
+ }
+ }
}
// Mutable global state for chisel that can appear outside a Builder context
@@ -180,6 +208,12 @@ private[chisel3] class DynamicContext() {
val components = ArrayBuffer[Component]()
val annotations = ArrayBuffer[ChiselAnnotation]()
var currentModule: Option[BaseModule] = None
+
+ /** Contains a mapping from a elaborated module to their aspect
+ * Set by [[ModuleAspect]]
+ */
+ val aspectModule: mutable.HashMap[BaseModule, BaseModule] = mutable.HashMap.empty[BaseModule, BaseModule]
+
// Set by object Module.apply before calling class Module constructor
// Used to distinguish between no Module() wrapping, multiple wrappings, and rewrapping
var readyForModuleConstr: Boolean = false
@@ -229,6 +263,13 @@ private[chisel3] object Builder {
def currentModule_=(target: Option[BaseModule]): Unit = {
dynamicContext.currentModule = target
}
+ def aspectModule(module: BaseModule): Option[BaseModule] = dynamicContextVar.value match {
+ case Some(dynamicContext) => dynamicContext.aspectModule.get(module)
+ case _ => None
+ }
+ def addAspect(module: BaseModule, aspect: BaseModule): Unit = {
+ dynamicContext.aspectModule += ((module, aspect))
+ }
def forcedModule: BaseModule = currentModule match {
case Some(module) => module
case None => throwException(
@@ -236,6 +277,19 @@ private[chisel3] object Builder {
// A bare api call is, e.g. calling Wire() from the scala console).
)
}
+ def referenceUserModule: RawModule = {
+ currentModule match {
+ case Some(module: RawModule) =>
+ aspectModule(module) match {
+ case Some(aspect: RawModule) => aspect
+ case other => module
+ }
+ case _ => throwException(
+ "Error: Not in a RawModule. Likely cause: Missed Module() wrap, bare chisel API call, or attempting to construct hardware inside a BlackBox." // scalastyle:ignore line.size.limit
+ // A bare api call is, e.g. calling Wire() from the scala console).
+ )
+ }
+ }
def forcedUserModule: RawModule = currentModule match {
case Some(module: RawModule) => module
case _ => throwException(
@@ -345,7 +399,7 @@ private[chisel3] object Builder {
throwException(m)
}
- def build[T <: RawModule](f: => T): Circuit = {
+ def build[T <: RawModule](f: => T): (Circuit, T) = {
chiselContext.withValue(new ChiselContext) {
dynamicContextVar.withValue(Some(new DynamicContext())) {
errors.info("Elaborating design...")
@@ -354,7 +408,7 @@ private[chisel3] object Builder {
errors.checkpoint()
errors.info("Done elaborating.")
- Circuit(components.last.name, components, annotations)
+ (Circuit(components.last.name, components, annotations), mod)
}
}
}
diff --git a/src/main/scala/chisel3/internal/firrtl/Converter.scala b/chiselFrontend/src/main/scala/chisel3/internal/firrtl/Converter.scala
index cdc55b59..cdc55b59 100644
--- a/src/main/scala/chisel3/internal/firrtl/Converter.scala
+++ b/chiselFrontend/src/main/scala/chisel3/internal/firrtl/Converter.scala
diff --git a/src/main/scala/chisel3/Driver.scala b/src/main/scala/chisel3/Driver.scala
index 906ae7fc..66146755 100644
--- a/src/main/scala/chisel3/Driver.scala
+++ b/src/main/scala/chisel3/Driver.scala
@@ -3,18 +3,17 @@
package chisel3
import chisel3.internal.ErrorLog
-import chisel3.internal.firrtl._
-import chisel3.experimental.{RawModule, RunFirrtlTransform}
-import chisel3.stage.{ChiselCircuitAnnotation, ChiselGeneratorAnnotation, ChiselStage, ChiselExecutionResultView}
-import chisel3.stage.phases.DriverCompatibility
-
-import java.io._
-
+import chisel3.experimental.RawModule
+import internal.firrtl._
import firrtl._
-import firrtl.annotations.JsonProtocol
import firrtl.options.Phase
import firrtl.options.Viewer.view
+import firrtl.annotations.JsonProtocol
import firrtl.util.{BackendCompilationUtilities => FirrtlBackendCompilationUtilities}
+import chisel3.stage.{ChiselExecutionResultView, ChiselGeneratorAnnotation, ChiselStage}
+import chisel3.stage.phases.DriverCompatibility
+import java.io._
+
/**
* The Driver provides methods to invoke the chisel3 compiler and the firrtl compiler.
@@ -91,7 +90,7 @@ object Driver extends BackendCompilationUtilities {
* @param gen A function that creates a Module hierarchy.
* @return The resulting Chisel IR in the form of a Circuit. (TODO: Should be FIRRTL IR)
*/
- def elaborate[T <: RawModule](gen: () => T): Circuit = internal.Builder.build(Module(gen()))
+ def elaborate[T <: RawModule](gen: () => T): Circuit = internal.Builder.build(Module(gen()))._1
/**
* Convert the given Chisel IR Circuit to a FIRRTL Circuit.
diff --git a/src/main/scala/chisel3/aop/Select.scala b/src/main/scala/chisel3/aop/Select.scala
new file mode 100644
index 00000000..612cdcc7
--- /dev/null
+++ b/src/main/scala/chisel3/aop/Select.scala
@@ -0,0 +1,418 @@
+// See LICENSE for license details.
+
+package chisel3.aop
+
+import chisel3._
+import chisel3.experimental.{BaseModule, FixedPoint}
+import chisel3.internal.HasId
+import chisel3.internal.firrtl._
+import firrtl.annotations.ReferenceTarget
+
+import scala.collection.mutable
+
+/** Use to select Chisel components in a module, after that module has been constructed
+ * Useful for adding additional Chisel annotations or for use within an [[Aspect]]
+ */
+object Select {
+
+ /** Return just leaf components of expanded node
+ *
+ * @param d Component to find leafs if aggregate typed. Intermediate fields/indicies are not included
+ * @return
+ */
+ def getLeafs(d: Data): Seq[Data] = d match {
+ case b: Bundle => b.getElements.flatMap(getLeafs)
+ case v: Vec[_] => v.getElements.flatMap(getLeafs)
+ case other => Seq(other)
+ }
+
+ /** Return all expanded components, including intermediate aggregate nodes
+ *
+ * @param d Component to find leafs if aggregate typed. Intermediate fields/indicies ARE included
+ * @return
+ */
+ def getIntermediateAndLeafs(d: Data): Seq[Data] = d match {
+ case b: Bundle => b +: b.getElements.flatMap(getIntermediateAndLeafs)
+ case v: Vec[_] => v +: v.getElements.flatMap(getIntermediateAndLeafs)
+ case other => Seq(other)
+ }
+
+
+ /** Collects all components selected by collector within module and all children modules it instantiates
+ * directly or indirectly
+ * Accepts a collector function, rather than a collector partial function (see [[collectDeep]])
+ * @param module Module to collect components, as well as all children module it directly and indirectly instantiates
+ * @param collector Collector function to pick, given a module, which components to collect
+ * @param tag Required for generics to work, should ignore this
+ * @tparam T Type of the component that will be collected
+ * @return
+ */
+ def getDeep[T](module: BaseModule)(collector: BaseModule => Seq[T]): Seq[T] = {
+ check(module)
+ val myItems = collector(module)
+ val deepChildrenItems = instances(module).flatMap {
+ i => getDeep(i)(collector)
+ }
+ myItems ++ deepChildrenItems
+ }
+
+ /** Collects all components selected by collector within module and all children modules it instantiates
+ * directly or indirectly
+ * Accepts a collector partial function, rather than a collector function (see [[getDeep]])
+ * @param module Module to collect components, as well as all children module it directly and indirectly instantiates
+ * @param collector Collector partial function to pick, given a module, which components to collect
+ * @param tag Required for generics to work, should ignore this
+ * @tparam T Type of the component that will be collected
+ * @return
+ */
+ def collectDeep[T](module: BaseModule)(collector: PartialFunction[BaseModule, T]): Iterable[T] = {
+ check(module)
+ val myItems = collector.lift(module)
+ val deepChildrenItems = instances(module).flatMap {
+ i => collectDeep(i)(collector)
+ }
+ myItems ++ deepChildrenItems
+ }
+
+ /** Selects all instances directly instantiated within given module
+ * @param module
+ * @return
+ */
+ def instances(module: BaseModule): Seq[BaseModule] = {
+ check(module)
+ module._component.get.asInstanceOf[DefModule].commands.collect {
+ case i: DefInstance => i.id
+ }
+ }
+
+ /** Selects all registers directly instantiated within given module
+ * @param module
+ * @return
+ */
+ def registers(module: BaseModule): Seq[Data] = {
+ check(module)
+ module._component.get.asInstanceOf[DefModule].commands.collect {
+ case r: DefReg => r.id
+ case r: DefRegInit => r.id
+ }
+ }
+
+ /** Selects all ios directly contained within given module
+ * @param module
+ * @return
+ */
+ def ios(module: BaseModule): Seq[Data] = {
+ check(module)
+ module._component.get.asInstanceOf[DefModule].ports.map(_.id)
+ }
+
+ /** Selects all SyncReadMems directly contained within given module
+ * @param module
+ * @return
+ */
+ def syncReadMems(module: BaseModule): Seq[SyncReadMem[_]] = {
+ check(module)
+ module._component.get.asInstanceOf[DefModule].commands.collect {
+ case r: DefSeqMemory => r.id.asInstanceOf[SyncReadMem[_]]
+ }
+ }
+
+ /** Selects all Mems directly contained within given module
+ * @param module
+ * @return
+ */
+ def mems(module: BaseModule): Seq[Mem[_]] = {
+ check(module)
+ module._component.get.asInstanceOf[DefModule].commands.collect {
+ case r: DefMemory => r.id.asInstanceOf[Mem[_]]
+ }
+ }
+
+ /** Selects all arithmetic or logical operators directly instantiated within given module
+ * @param module
+ * @return
+ */
+ def ops(module: BaseModule): Seq[(String, Data)] = {
+ check(module)
+ module._component.get.asInstanceOf[DefModule].commands.collect {
+ case d: DefPrim[_] => (d.op.name, d.id)
+ }
+ }
+
+ /** Selects a kind of arithmetic or logical operator directly instantiated within given module
+ * The kind of operators are contained in [[chisel3.internal.firrtl.PrimOp]]
+ * @param opKind the kind of operator, e.g. "mux", "add", or "bits"
+ * @param module
+ * @return
+ */
+ def ops(opKind: String)(module: BaseModule): Seq[Data] = {
+ check(module)
+ module._component.get.asInstanceOf[DefModule].commands.collect {
+ case d: DefPrim[_] if d.op.name == opKind => d.id
+ }
+ }
+
+ /** Selects all wires in a module
+ * @param module
+ * @return
+ */
+ def wires(module: BaseModule): Seq[Data] = {
+ check(module)
+ module._component.get.asInstanceOf[DefModule].commands.collect {
+ case r: DefWire => r.id
+ }
+ }
+
+ /** Selects all memory ports, including their direction and memory
+ * @param module
+ * @return
+ */
+ def memPorts(module: BaseModule): Seq[(Data, MemPortDirection, MemBase[_])] = {
+ check(module)
+ module._component.get.asInstanceOf[DefModule].commands.collect {
+ case r: DefMemPort[_] => (r.id, r.dir, r.source.id.asInstanceOf[MemBase[_ <: Data]])
+ }
+ }
+
+ /** Selects all memory ports of a given direction, including their memory
+ * @param dir The direction of memory ports to select
+ * @param module
+ * @return
+ */
+ def memPorts(dir: MemPortDirection)(module: BaseModule): Seq[(Data, MemBase[_])] = {
+ check(module)
+ module._component.get.asInstanceOf[DefModule].commands.collect {
+ case r: DefMemPort[_] if r.dir == dir => (r.id, r.source.id.asInstanceOf[MemBase[_ <: Data]])
+ }
+ }
+
+ /** Selects all components who have been set to be invalid, even if they are later connected to
+ * @param module
+ * @return
+ */
+ def invalids(module: BaseModule): Seq[Data] = {
+ check(module)
+ module._component.get.asInstanceOf[DefModule].commands.collect {
+ case DefInvalid(_, arg) => getData(arg)
+ }
+ }
+
+ /** Selects all components who are attached to a given signal, within a module
+ * @param module
+ * @return
+ */
+ def attachedTo(module: BaseModule)(signal: Data): Set[Data] = {
+ check(module)
+ module._component.get.asInstanceOf[DefModule].commands.collect {
+ case Attach(_, seq) if seq.contains(signal) => seq
+ }.flatMap { seq => seq.map(_.id.asInstanceOf[Data]) }.toSet
+ }
+
+ /** Selects all connections to a signal or its parent signal(s) (if the signal is an element of an aggregate signal)
+ * The when predicates surrounding each connection are included in the returned values
+ *
+ * E.g. if signal = io.foo.bar, connectionsTo will return all connections to io, io.foo, and io.bar
+ * @param module
+ * @param signal
+ * @return
+ */
+ def connectionsTo(module: BaseModule)(signal: Data): Seq[PredicatedConnect] = {
+ check(module)
+ val sensitivitySignals = getIntermediateAndLeafs(signal).toSet
+ val predicatedConnects = mutable.ArrayBuffer[PredicatedConnect]()
+ val isPort = module._component.get.asInstanceOf[DefModule].ports.flatMap{ p => getIntermediateAndLeafs(p.id) }.contains(signal)
+ var prePredicates: Seq[Predicate] = Nil
+ var seenDef = isPort
+ searchWhens(module, (cmd: Command, preds) => {
+ cmd match {
+ case cmd: Definition if cmd.id.isInstanceOf[Data] =>
+ val x = getIntermediateAndLeafs(cmd.id.asInstanceOf[Data])
+ if(x.contains(signal)) prePredicates = preds
+ case Connect(_, loc@Node(d: Data), exp) =>
+ val effected = getEffected(loc).toSet
+ if(sensitivitySignals.intersect(effected).nonEmpty) {
+ val expData = getData(exp)
+ prePredicates.reverse.zip(preds.reverse).foreach(x => assert(x._1 == x._2, s"Prepredicates $x must match for signal $signal"))
+ predicatedConnects += PredicatedConnect(preds.dropRight(prePredicates.size), d, expData, isBulk = false)
+ }
+ case BulkConnect(_, loc@Node(d: Data), exp) =>
+ val effected = getEffected(loc).toSet
+ if(sensitivitySignals.intersect(effected).nonEmpty) {
+ val expData = getData(exp)
+ prePredicates.reverse.zip(preds.reverse).foreach(x => assert(x._1 == x._2, s"Prepredicates $x must match for signal $signal"))
+ predicatedConnects += PredicatedConnect(preds.dropRight(prePredicates.size), d, expData, isBulk = true)
+ }
+ case other =>
+ }
+ })
+ predicatedConnects
+ }
+
+ /** Selects all stop statements, and includes the predicates surrounding the stop statement
+ *
+ * @param module
+ * @return
+ */
+ def stops(module: BaseModule): Seq[Stop] = {
+ val stops = mutable.ArrayBuffer[Stop]()
+ searchWhens(module, (cmd: Command, preds: Seq[Predicate]) => {
+ cmd match {
+ case chisel3.internal.firrtl.Stop(_, clock, ret) => stops += Stop(preds, ret, getId(clock).asInstanceOf[Clock])
+ case other =>
+ }
+ })
+ stops
+ }
+
+ /** Selects all printf statements, and includes the predicates surrounding the printf statement
+ *
+ * @param module
+ * @return
+ */
+ def printfs(module: BaseModule): Seq[Printf] = {
+ val printfs = mutable.ArrayBuffer[Printf]()
+ searchWhens(module, (cmd: Command, preds: Seq[Predicate]) => {
+ cmd match {
+ case chisel3.internal.firrtl.Printf(_, clock, pable) => printfs += Printf(preds, pable, getId(clock).asInstanceOf[Clock])
+ case other =>
+ }
+ })
+ printfs
+ }
+
+ // Checks that a module has finished its construction
+ private def check(module: BaseModule): Unit = {
+ require(module.isClosed, "Can't use Selector on modules that have not finished construction!")
+ require(module._component.isDefined, "Can't use Selector on modules that don't have components!")
+ }
+
+ // Given a loc, return all subcomponents of id that could be assigned to in connect
+ private def getEffected(a: Arg): Seq[Data] = a match {
+ case Node(id: Data) => getIntermediateAndLeafs(id)
+ case Slot(imm, name) => Seq(imm.id.asInstanceOf[Record].elements(name))
+ case Index(imm, value) => getEffected(imm)
+ }
+
+ // Given an arg, return the corresponding id. Don't use on a loc of a connect.
+ private def getId(a: Arg): HasId = a match {
+ case Node(id) => id
+ case l: ULit => l.num.U(l.w)
+ case l: SLit => l.num.S(l.w)
+ case l: FPLit => FixedPoint(l.num, l.w, l.binaryPoint)
+ case other =>
+ sys.error(s"Something went horribly wrong! I was expecting ${other} to be a lit or a node!")
+ }
+
+ private def getData(a: Arg): Data = a match {
+ case Node(data: Data) => data
+ case other =>
+ sys.error(s"Something went horribly wrong! I was expecting ${other} to be Data!")
+ }
+
+ // Given an id, either get its name or its value, if its a lit
+ private def getName(i: HasId): String = try {
+ i.toTarget match {
+ case r: ReferenceTarget =>
+ val str = r.serialize
+ str.splitAt(str.indexOf('>'))._2.drop(1)
+ }
+ } catch {
+ case e: ChiselException => i.getOptionRef.get match {
+ case l: LitArg => l.num.intValue().toString
+ }
+ }
+
+ // Collects when predicates as it searches through a module, then applying processCommand to non-when related commands
+ private def searchWhens(module: BaseModule, processCommand: (Command, Seq[Predicate]) => Unit) = {
+ check(module)
+ module._component.get.asInstanceOf[DefModule].commands.foldLeft((Seq.empty[Predicate], Option.empty[Predicate])) {
+ (blah, cmd) =>
+ (blah, cmd) match {
+ case ((preds, o), cmd) => cmd match {
+ case WhenBegin(_, Node(pred: Bool)) => (When(pred) +: preds, None)
+ case WhenBegin(_, l: LitArg) if l.num == BigInt(1) => (When(true.B) +: preds, None)
+ case WhenBegin(_, l: LitArg) if l.num == BigInt(0) => (When(false.B) +: preds, None)
+ case other: WhenBegin =>
+ sys.error(s"Something went horribly wrong! I was expecting ${other.pred} to be a lit or a bool!")
+ case _: WhenEnd => (preds.tail, Some(preds.head))
+ case AltBegin(_) if o.isDefined => (o.get.not +: preds, o)
+ case _: AltBegin =>
+ sys.error(s"Something went horribly wrong! I was expecting ${o} to be nonEmpty!")
+ case OtherwiseEnd(_, _) => (preds.tail, None)
+ case other =>
+ processCommand(cmd, preds)
+ (preds, o)
+ }
+ }
+ }
+ }
+
+ trait Serializeable {
+ def serialize: String
+ }
+
+ /** Used to indicates a when's predicate (or its otherwise predicate)
+ */
+ trait Predicate extends Serializeable {
+ val bool: Bool
+ def not: Predicate
+ }
+
+ /** Used to represent [[chisel3.when]] predicate
+ *
+ * @param bool the when predicate
+ */
+ case class When(bool: Bool) extends Predicate {
+ def not: WhenNot = WhenNot(bool)
+ def serialize: String = s"${getName(bool)}"
+ }
+
+ /** Used to represent the `otherwise` predicate of a [[chisel3.when]]
+ *
+ * @param bool the when predicate corresponding to this otherwise predicate
+ */
+ case class WhenNot(bool: Bool) extends Predicate {
+ def not: When = When(bool)
+ def serialize: String = s"!${getName(bool)}"
+ }
+
+ /** Used to represent a connection or bulk connection
+ *
+ * Additionally contains the sequence of when predicates seen when the connection is declared
+ *
+ * @param preds
+ * @param loc
+ * @param exp
+ * @param isBulk
+ */
+ case class PredicatedConnect(preds: Seq[Predicate], loc: Data, exp: Data, isBulk: Boolean) extends Serializeable {
+ def serialize: String = {
+ val moduleTarget = loc.toTarget.moduleTarget.serialize
+ s"$moduleTarget: when(${preds.map(_.serialize).mkString(" & ")}): ${getName(loc)} ${if(isBulk) "<>" else ":="} ${getName(exp)}"
+ }
+ }
+
+ /** Used to represent a [[chisel3.stop]]
+ *
+ * @param preds
+ * @param ret
+ * @param clock
+ */
+ case class Stop(preds: Seq[Predicate], ret: Int, clock: Clock) extends Serializeable {
+ def serialize: String = {
+ s"stop when(${preds.map(_.serialize).mkString(" & ")}) on ${getName(clock)}: $ret"
+ }
+ }
+
+ /** Used to represent a [[chisel3.printf]]
+ *
+ * @param preds
+ * @param pable
+ * @param clock
+ */
+ case class Printf(preds: Seq[Predicate], pable: Printable, clock: Clock) extends Serializeable {
+ def serialize: String = {
+ s"printf when(${preds.map(_.serialize).mkString(" & ")}) on ${getName(clock)}: $pable"
+ }
+ }
+}
diff --git a/src/main/scala/chisel3/aop/injecting/InjectStatement.scala b/src/main/scala/chisel3/aop/injecting/InjectStatement.scala
new file mode 100644
index 00000000..c207454d
--- /dev/null
+++ b/src/main/scala/chisel3/aop/injecting/InjectStatement.scala
@@ -0,0 +1,21 @@
+// See LICENSE for license details.
+
+package chisel3.aop.injecting
+
+import chisel3.stage.phases.AspectPhase
+import firrtl.annotations.{Annotation, ModuleTarget, NoTargetAnnotation, SingleTargetAnnotation}
+
+/** Contains all information needed to inject statements into a module
+ *
+ * Generated when a [[InjectingAspect]] is consumed by a [[AspectPhase]]
+ * Consumed by [[InjectingTransform]]
+ *
+ * @param module Module to inject code into at the end of the module
+ * @param s Statements to inject
+ * @param modules Additional modules that may be instantiated by s
+ * @param annotations Additional annotations that should be passed down compiler
+ */
+case class InjectStatement(module: ModuleTarget, s: firrtl.ir.Statement, modules: Seq[firrtl.ir.DefModule], annotations: Seq[Annotation]) extends SingleTargetAnnotation[ModuleTarget] {
+ val target: ModuleTarget = module
+ override def duplicate(n: ModuleTarget): Annotation = this.copy(module = n)
+}
diff --git a/src/main/scala/chisel3/aop/injecting/InjectingAspect.scala b/src/main/scala/chisel3/aop/injecting/InjectingAspect.scala
new file mode 100644
index 00000000..74cd62f3
--- /dev/null
+++ b/src/main/scala/chisel3/aop/injecting/InjectingAspect.scala
@@ -0,0 +1,63 @@
+// See LICENSE for license details.
+
+package chisel3.aop.injecting
+
+import chisel3.{Module, ModuleAspect, experimental, withClockAndReset}
+import chisel3.aop._
+import chisel3.experimental.RawModule
+import chisel3.internal.Builder
+import chisel3.internal.firrtl.DefModule
+import chisel3.stage.DesignAnnotation
+import firrtl.annotations.ModuleTarget
+import firrtl.stage.RunFirrtlTransformAnnotation
+import firrtl.{ir, _}
+
+import scala.collection.mutable
+import scala.reflect.runtime.universe.TypeTag
+
+/** Aspect to inject Chisel code into a module of type M
+ *
+ * @param selectRoots Given top-level module, pick the instances of a module to apply the aspect (root module)
+ * @param injection Function to generate Chisel hardware that will be injected to the end of module m
+ * Signals in m can be referenced and assigned to as if inside m (yes, it is a bit magical)
+ * @param tTag Needed to prevent type-erasure of the top-level module type
+ * @tparam T Type of top-level module
+ * @tparam M Type of root module (join point)
+ */
+case class InjectingAspect[T <: RawModule,
+ M <: RawModule](selectRoots: T => Iterable[M],
+ injection: M => Unit
+ )(implicit tTag: TypeTag[T]) extends Aspect[T] {
+ final def toAnnotation(top: T): AnnotationSeq = {
+ toAnnotation(selectRoots(top), top.name)
+ }
+
+ final def toAnnotation(modules: Iterable[M], circuit: String): AnnotationSeq = {
+ RunFirrtlTransformAnnotation(new InjectingTransform) +: modules.map { module =>
+ val (chiselIR, _) = Builder.build(Module(new ModuleAspect(module) {
+ module match {
+ case x: experimental.MultiIOModule => withClockAndReset(x.clock, x.reset) { injection(module) }
+ case x: RawModule => injection(module)
+ }
+ }))
+ val comps = chiselIR.components.map {
+ case x: DefModule if x.name == module.name => x.copy(id = module)
+ case other => other
+ }
+
+ val annotations = chiselIR.annotations.map(_.toFirrtl).filterNot{ a => a.isInstanceOf[DesignAnnotation[_]] }
+
+ val stmts = mutable.ArrayBuffer[ir.Statement]()
+ val modules = Aspect.getFirrtl(chiselIR.copy(components = comps)).modules.flatMap {
+ case m: firrtl.ir.Module if m.name == module.name =>
+ stmts += m.body
+ Nil
+ case other =>
+ Seq(other)
+ }
+
+ InjectStatement(ModuleTarget(circuit, module.name), ir.Block(stmts), modules, annotations)
+ }.toSeq
+ }
+}
+
diff --git a/src/main/scala/chisel3/aop/injecting/InjectingTransform.scala b/src/main/scala/chisel3/aop/injecting/InjectingTransform.scala
new file mode 100644
index 00000000..c65bee38
--- /dev/null
+++ b/src/main/scala/chisel3/aop/injecting/InjectingTransform.scala
@@ -0,0 +1,46 @@
+// See LICENSE for license details.
+
+package chisel3.aop.injecting
+
+import firrtl.{ChirrtlForm, CircuitForm, CircuitState, Transform, ir}
+
+import scala.collection.mutable
+
+/** Appends statements contained in [[InjectStatement]] annotations to the end of their corresponding modules
+ *
+ * Implemented with Chisel Aspects and the [[chisel3.aop.injecting]] library
+ */
+class InjectingTransform extends Transform {
+ override def inputForm: CircuitForm = ChirrtlForm
+ override def outputForm: CircuitForm = ChirrtlForm
+
+ override def execute(state: CircuitState): CircuitState = {
+
+ val addStmtMap = mutable.HashMap[String, Seq[ir.Statement]]()
+ val addModules = mutable.ArrayBuffer[ir.DefModule]()
+
+ // Populate addStmtMap and addModules, return annotations in InjectStatements, and omit InjectStatement annotation
+ val newAnnotations = state.annotations.flatMap {
+ case InjectStatement(mt, s, addedModules, annotations) =>
+ addModules ++= addedModules
+ addStmtMap(mt.module) = s +: addStmtMap.getOrElse(mt.module, Nil)
+ annotations
+ case other => Seq(other)
+ }
+
+ // Append all statements to end of corresponding modules
+ val newModules = state.circuit.modules.map { m: ir.DefModule =>
+ m match {
+ case m: ir.Module if addStmtMap.contains(m.name) =>
+ m.copy(body = ir.Block(m.body +: addStmtMap(m.name)))
+ case m: _root_.firrtl.ir.ExtModule if addStmtMap.contains(m.name) =>
+ ir.Module(m.info, m.name, m.ports, ir.Block(addStmtMap(m.name)))
+ case other: ir.DefModule => other
+ }
+ }
+
+ // Return updated circuit and annotations
+ val newCircuit = state.circuit.copy(modules = newModules ++ addModules)
+ state.copy(annotations = newAnnotations, circuit = newCircuit)
+ }
+}
diff --git a/src/main/scala/chisel3/stage/ChiselAnnotations.scala b/src/main/scala/chisel3/stage/ChiselAnnotations.scala
index fb02173b..e722bac2 100644
--- a/src/main/scala/chisel3/stage/ChiselAnnotations.scala
+++ b/src/main/scala/chisel3/stage/ChiselAnnotations.scala
@@ -4,11 +4,11 @@ package chisel3.stage
import firrtl.annotations.{Annotation, NoTargetAnnotation}
import firrtl.options.{HasShellOptions, OptionsException, ShellOption, Unserializable}
-
import chisel3.{ChiselException, Module}
import chisel3.experimental.RawModule
import chisel3.internal.Builder
import chisel3.internal.firrtl.Circuit
+import firrtl.AnnotationSeq
/** Mixin that indicates that this is an [[firrtl.annotations.Annotation]] used to generate a [[ChiselOptions]] view.
*/
@@ -46,8 +46,9 @@ case class ChiselGeneratorAnnotation(gen: () => RawModule) extends NoTargetAnnot
/** Run elaboration on the Chisel module generator function stored by this [[firrtl.annotations.Annotation]]
*/
- def elaborate: ChiselCircuitAnnotation = try {
- ChiselCircuitAnnotation(Builder.build(Module(gen())))
+ def elaborate: AnnotationSeq = try {
+ val (circuit, dut) = Builder.build(Module(gen()))
+ Seq(ChiselCircuitAnnotation(circuit), DesignAnnotation(dut))
} catch {
case e @ (_: OptionsException | _: ChiselException) => throw e
case e: Throwable =>
@@ -103,3 +104,11 @@ object ChiselOutputFileAnnotation extends HasShellOptions {
helpValueName = Some("<file>") ) )
}
+
+/** Contains the top-level elaborated Chisel design.
+ *
+ * By default is created during Chisel elaboration and passed to the FIRRTL compiler.
+ * @param design top-level Chisel design
+ * @tparam DUT Type of the top-level Chisel design
+ */
+case class DesignAnnotation[DUT <: RawModule](design: DUT) extends NoTargetAnnotation with Unserializable
diff --git a/src/main/scala/chisel3/stage/ChiselStage.scala b/src/main/scala/chisel3/stage/ChiselStage.scala
index 1e92aaf6..0c6512af 100644
--- a/src/main/scala/chisel3/stage/ChiselStage.scala
+++ b/src/main/scala/chisel3/stage/ChiselStage.scala
@@ -14,6 +14,7 @@ class ChiselStage extends Stage {
new chisel3.stage.phases.Elaborate,
new chisel3.stage.phases.AddImplicitOutputFile,
new chisel3.stage.phases.AddImplicitOutputAnnotationFile,
+ new chisel3.stage.phases.MaybeAspectPhase,
new chisel3.stage.phases.Emitter,
new chisel3.stage.phases.Convert,
new chisel3.stage.phases.MaybeFirrtlStage )
diff --git a/src/main/scala/chisel3/stage/phases/AspectPhase.scala b/src/main/scala/chisel3/stage/phases/AspectPhase.scala
new file mode 100644
index 00000000..f8038a2c
--- /dev/null
+++ b/src/main/scala/chisel3/stage/phases/AspectPhase.scala
@@ -0,0 +1,37 @@
+// See LICENSE for license details.
+
+package chisel3.stage.phases
+
+import chisel3.aop.Aspect
+import chisel3.experimental.RawModule
+import chisel3.stage.DesignAnnotation
+import firrtl.AnnotationSeq
+import firrtl.options.Phase
+
+import scala.collection.mutable
+
+/** Phase that consumes all Aspects and calls their toAnnotationSeq methods.
+ *
+ * Consumes the [[chisel3.stage.DesignAnnotation]] and converts every [[Aspect]] into their annotations prior to executing FIRRTL
+ */
+class AspectPhase extends Phase {
+ def transform(annotations: AnnotationSeq): AnnotationSeq = {
+ var dut: Option[RawModule] = None
+ val aspects = mutable.ArrayBuffer[Aspect[_]]()
+
+ val remainingAnnotations = annotations.flatMap {
+ case DesignAnnotation(d) =>
+ dut = Some(d)
+ Nil
+ case a: Aspect[_] =>
+ aspects += a
+ Nil
+ case other => Seq(other)
+ }
+ if(dut.isDefined) {
+ val newAnnotations = aspects.flatMap { _.resolveAspect(dut.get) }
+ remainingAnnotations ++ newAnnotations
+ } else annotations
+ }
+}
+
diff --git a/src/main/scala/chisel3/stage/phases/Convert.scala b/src/main/scala/chisel3/stage/phases/Convert.scala
index 174030ae..f08367c6 100644
--- a/src/main/scala/chisel3/stage/phases/Convert.scala
+++ b/src/main/scala/chisel3/stage/phases/Convert.scala
@@ -5,7 +5,6 @@ package chisel3.stage.phases
import chisel3.experimental.RunFirrtlTransform
import chisel3.internal.firrtl.Converter
import chisel3.stage.ChiselCircuitAnnotation
-
import firrtl.{AnnotationSeq, Transform}
import firrtl.options.Phase
import firrtl.stage.{FirrtlCircuitAnnotation, RunFirrtlTransformAnnotation}
@@ -18,7 +17,7 @@ import firrtl.stage.{FirrtlCircuitAnnotation, RunFirrtlTransformAnnotation}
class Convert extends Phase {
def transform(annotations: AnnotationSeq): AnnotationSeq = annotations.flatMap {
- case a: ChiselCircuitAnnotation => {
+ case a: ChiselCircuitAnnotation =>
/* Convert this Chisel Circuit to a FIRRTL Circuit */
Some(FirrtlCircuitAnnotation(Converter.convert(a.circuit))) ++
/* Convert all Chisel Annotations to FIRRTL Annotations */
@@ -26,15 +25,15 @@ class Convert extends Phase {
.circuit
.annotations
.map(_.toFirrtl) ++
- /* Add requested FIRRTL Transforms for any Chisel Annotations which mixed in RunFirrtlTransform */
a
.circuit
.annotations
- .collect { case b: RunFirrtlTransform => b.transformClass }
+ .collect {
+ case anno: RunFirrtlTransform => anno.transformClass
+ }
.distinct
.filterNot(_ == classOf[firrtl.Transform])
.map { c: Class[_ <: Transform] => RunFirrtlTransformAnnotation(c.newInstance()) }
- }
case a => Some(a)
}
diff --git a/src/main/scala/chisel3/stage/phases/Elaborate.scala b/src/main/scala/chisel3/stage/phases/Elaborate.scala
index 0b0d71fb..2ec5f92c 100644
--- a/src/main/scala/chisel3/stage/phases/Elaborate.scala
+++ b/src/main/scala/chisel3/stage/phases/Elaborate.scala
@@ -21,7 +21,7 @@ class Elaborate extends Phase {
def transform(annotations: AnnotationSeq): AnnotationSeq = annotations.flatMap {
case a: ChiselGeneratorAnnotation =>
try {
- Some(a.elaborate)
+ a.elaborate
} catch {
case e: OptionsException => throw e
case e: ChiselException =>
diff --git a/src/main/scala/chisel3/stage/phases/MaybeAspectPhase.scala b/src/main/scala/chisel3/stage/phases/MaybeAspectPhase.scala
new file mode 100644
index 00000000..3e8b8feb
--- /dev/null
+++ b/src/main/scala/chisel3/stage/phases/MaybeAspectPhase.scala
@@ -0,0 +1,18 @@
+// See LICENSE for license details.
+
+package chisel3.stage.phases
+
+import chisel3.aop.Aspect
+import firrtl.AnnotationSeq
+import firrtl.options.Phase
+
+/** Run [[AspectPhase]] if a [[chisel3.aop.Aspect]] is present.
+ */
+class MaybeAspectPhase extends Phase {
+
+ def transform(annotations: AnnotationSeq): AnnotationSeq = {
+ if(annotations.collectFirst { case a: Aspect[_] => annotations }.isDefined) {
+ new AspectPhase().transform(annotations)
+ } else annotations
+ }
+}
diff --git a/src/main/scala/chisel3/testers/TesterDriver.scala b/src/main/scala/chisel3/testers/TesterDriver.scala
index df26e3c3..7e3730a3 100644
--- a/src/main/scala/chisel3/testers/TesterDriver.scala
+++ b/src/main/scala/chisel3/testers/TesterDriver.scala
@@ -5,7 +5,10 @@ package chisel3.testers
import chisel3._
import java.io._
+import chisel3.aop.Aspect
import chisel3.experimental.RunFirrtlTransform
+import chisel3.stage.phases.AspectPhase
+import chisel3.stage.{ChiselCircuitAnnotation, ChiselStage, DesignAnnotation}
import firrtl.{Driver => _, _}
import firrtl.transforms.BlackBoxSourceHelper.writeResourceToDirectory
@@ -14,9 +17,13 @@ object TesterDriver extends BackendCompilationUtilities {
/** For use with modules that should successfully be elaborated by the
* frontend, and which can be turned into executables with assertions. */
def execute(t: () => BasicTester,
- additionalVResources: Seq[String] = Seq()): Boolean = {
+ additionalVResources: Seq[String] = Seq(),
+ annotations: AnnotationSeq = Seq()
+ ): Boolean = {
// Invoke the chisel compiler to get the circuit's IR
- val circuit = Driver.elaborate(finishWrapper(t))
+ val (circuit, dut) = new chisel3.stage.ChiselGeneratorAnnotation(finishWrapper(t)).elaborate.toSeq match {
+ case Seq(ChiselCircuitAnnotation(cir), d:DesignAnnotation[_]) => (cir, d)
+ }
// Set up a bunch of file handlers based on a random temp filename,
// plus the quirks of Verilator's naming conventions
@@ -41,13 +48,16 @@ object TesterDriver extends BackendCompilationUtilities {
})
// Compile firrtl
- val transforms = circuit.annotations.collect { case anno: RunFirrtlTransform => anno.transformClass }.distinct
- .filterNot(_ == classOf[Transform])
- .map { transformClass: Class[_ <: Transform] => transformClass.newInstance() }
- val annotations = circuit.annotations.map(_.toFirrtl).toList
+ val transforms = circuit.annotations.collect {
+ case anno: RunFirrtlTransform => anno.transformClass
+ }.distinct
+ .filterNot(_ == classOf[Transform])
+ .map { transformClass: Class[_ <: Transform] => transformClass.newInstance() }
+ val newAnnotations = circuit.annotations.map(_.toFirrtl).toList ++ annotations ++ Seq(dut)
+ val resolvedAnnotations = new AspectPhase().transform(newAnnotations).toList
val optionsManager = new ExecutionOptionsManager("chisel3") with HasChiselExecutionOptions with HasFirrtlOptions {
commonOptions = CommonOptions(topName = target, targetDirName = path.getAbsolutePath)
- firrtlOptions = FirrtlExecutionOptions(compilerName = "verilog", annotations = annotations,
+ firrtlOptions = FirrtlExecutionOptions(compilerName = "verilog", annotations = resolvedAnnotations,
customTransforms = transforms,
firrtlCircuit = Some(firrtlCircuit))
}
diff --git a/src/test/scala/chiselTests/ChiselSpec.scala b/src/test/scala/chiselTests/ChiselSpec.scala
index 5973cb63..75fa68dd 100644
--- a/src/test/scala/chiselTests/ChiselSpec.scala
+++ b/src/test/scala/chiselTests/ChiselSpec.scala
@@ -8,25 +8,29 @@ import org.scalacheck._
import chisel3._
import chisel3.experimental.RawModule
import chisel3.testers._
-import firrtl.{
- CommonOptions,
- ExecutionOptionsManager,
- HasFirrtlOptions,
- FirrtlExecutionSuccess,
- FirrtlExecutionFailure
-}
+import firrtl.options.OptionsException
+import firrtl.{AnnotationSeq, CommonOptions, ExecutionOptionsManager, FirrtlExecutionFailure, FirrtlExecutionSuccess, HasFirrtlOptions}
import firrtl.util.BackendCompilationUtilities
/** Common utility functions for Chisel unit tests. */
trait ChiselRunners extends Assertions with BackendCompilationUtilities {
- def runTester(t: => BasicTester, additionalVResources: Seq[String] = Seq()): Boolean = {
- TesterDriver.execute(() => t, additionalVResources)
+ def runTester(t: => BasicTester,
+ additionalVResources: Seq[String] = Seq(),
+ annotations: AnnotationSeq = Seq()
+ ): Boolean = {
+ TesterDriver.execute(() => t, additionalVResources, annotations)
}
- def assertTesterPasses(t: => BasicTester, additionalVResources: Seq[String] = Seq()): Unit = {
- assert(runTester(t, additionalVResources))
+ def assertTesterPasses(t: => BasicTester,
+ additionalVResources: Seq[String] = Seq(),
+ annotations: AnnotationSeq = Seq()
+ ): Unit = {
+ assert(runTester(t, additionalVResources, annotations))
}
- def assertTesterFails(t: => BasicTester, additionalVResources: Seq[String] = Seq()): Unit = {
- assert(!runTester(t, additionalVResources))
+ def assertTesterFails(t: => BasicTester,
+ additionalVResources: Seq[String] = Seq(),
+ annotations: Seq[chisel3.aop.Aspect[_]] = Seq()
+ ): Unit = {
+ assert(!runTester(t, additionalVResources, annotations))
}
def elaborate(t: => RawModule): Unit = Driver.elaborate(() => t)
@@ -95,11 +99,12 @@ class ChiselTestUtilitiesSpec extends ChiselFlatSpec {
import org.scalatest.exceptions.TestFailedException
// Who tests the testers?
"assertKnownWidth" should "error when the expected width is wrong" in {
- a [TestFailedException] shouldBe thrownBy {
+ val caught = intercept[OptionsException] {
assertKnownWidth(7) {
Wire(UInt(8.W))
}
}
+ assert(caught.getCause.isInstanceOf[TestFailedException])
}
it should "error when the width is unknown" in {
@@ -117,11 +122,12 @@ class ChiselTestUtilitiesSpec extends ChiselFlatSpec {
}
"assertInferredWidth" should "error if the width is known" in {
- a [TestFailedException] shouldBe thrownBy {
+ val caught = intercept[OptionsException] {
assertInferredWidth(8) {
Wire(UInt(8.W))
}
}
+ assert(caught.getCause.isInstanceOf[TestFailedException])
}
it should "error if the expected width is wrong" in {
diff --git a/src/test/scala/chiselTests/aop/InjectionSpec.scala b/src/test/scala/chiselTests/aop/InjectionSpec.scala
new file mode 100644
index 00000000..6c022d60
--- /dev/null
+++ b/src/test/scala/chiselTests/aop/InjectionSpec.scala
@@ -0,0 +1,58 @@
+// See LICENSE for license details.
+
+package chiselTests.aop
+
+import chisel3.testers.BasicTester
+import chiselTests.ChiselFlatSpec
+import chisel3._
+import chisel3.aop.injecting.InjectingAspect
+
+class AspectTester(results: Seq[Int]) extends BasicTester {
+ val values = VecInit(results.map(_.U))
+ val counter = RegInit(0.U(results.length.W))
+ counter := counter + 1.U
+ when(counter >= values.length.U) {
+ stop()
+ }.otherwise {
+ when(reset.asBool() === false.B) {
+ printf("values(%d) = %d\n", counter, values(counter))
+ assert(counter === values(counter))
+ }
+ }
+}
+
+class InjectionSpec extends ChiselFlatSpec {
+ val correctValueAspect = InjectingAspect(
+ {dut: AspectTester => Seq(dut)},
+ {dut: AspectTester =>
+ for(i <- 0 until dut.values.length) {
+ dut.values(i) := i.U
+ }
+ }
+ )
+
+ val wrongValueAspect = InjectingAspect(
+ {dut: AspectTester => Seq(dut)},
+ {dut: AspectTester =>
+ for(i <- 0 until dut.values.length) {
+ dut.values(i) := (i + 1).U
+ }
+ }
+ )
+
+ "Test" should "pass if inserted the correct values" in {
+ assertTesterPasses{ new AspectTester(Seq(0, 1, 2)) }
+ }
+ "Test" should "fail if inserted the wrong values" in {
+ assertTesterFails{ new AspectTester(Seq(9, 9, 9)) }
+ }
+ "Test" should "pass if pass wrong values, but correct with aspect" in {
+ assertTesterPasses({ new AspectTester(Seq(9, 9, 9))} , Nil, Seq(correctValueAspect))
+ }
+ "Test" should "pass if pass wrong values, then wrong aspect, then correct aspect" in {
+ assertTesterPasses({ new AspectTester(Seq(9, 9, 9))} , Nil, Seq(wrongValueAspect, correctValueAspect))
+ }
+ "Test" should "fail if pass wrong values, then correct aspect, then wrong aspect" in {
+ assertTesterFails({ new AspectTester(Seq(9, 9, 9))} , Nil, Seq(correctValueAspect, wrongValueAspect))
+ }
+}
diff --git a/src/test/scala/chiselTests/aop/SelectSpec.scala b/src/test/scala/chiselTests/aop/SelectSpec.scala
new file mode 100644
index 00000000..d3f72551
--- /dev/null
+++ b/src/test/scala/chiselTests/aop/SelectSpec.scala
@@ -0,0 +1,144 @@
+// See LICENSE for license details.
+
+package chiselTests.aop
+
+import chisel3.testers.BasicTester
+import chiselTests.ChiselFlatSpec
+import chisel3._
+import chisel3.aop.Select.{PredicatedConnect, When, WhenNot}
+import chisel3.aop.{Aspect, Select}
+import chisel3.experimental.RawModule
+import firrtl.{AnnotationSeq}
+
+import scala.reflect.runtime.universe.TypeTag
+
+class SelectTester(results: Seq[Int]) extends BasicTester {
+ val values = VecInit(results.map(_.U))
+ val counter = RegInit(0.U(results.length.W))
+ val added = counter + 1.U
+ counter := added
+ val overflow = counter >= values.length.U
+ val nreset = reset.asBool() === false.B
+ val selected = values(counter)
+ val zero = 0.U + 0.U
+ when(overflow) {
+ counter := zero
+ stop()
+ }.otherwise {
+ when(nreset) {
+ assert(counter === values(counter))
+ printf("values(%d) = %d\n", counter, selected)
+ }
+ }
+}
+
+case class SelectAspect[T <: RawModule, X](selector: T => Seq[X], desired: T => Seq[X])(implicit tTag: TypeTag[T]) extends Aspect[T] {
+ override def toAnnotation(top: T): AnnotationSeq = {
+ val results = selector(top)
+ val desiredSeq = desired(top)
+ assert(results.length == desiredSeq.length, s"Failure! Results $results have different length than desired $desiredSeq!")
+ val mismatches = results.zip(desiredSeq).flatMap {
+ case (res, des) if res != des => Seq((res, des))
+ case other => Nil
+ }
+ assert(mismatches.isEmpty,s"Failure! The following selected items do not match their desired item:\n" + mismatches.map{
+ case (res: Select.Serializeable, des: Select.Serializeable) => s" ${res.serialize} does not match:\n ${des.serialize}"
+ case (res, des) => s" $res does not match:\n $des"
+ }.mkString("\n"))
+ Nil
+ }
+}
+
+class SelectSpec extends ChiselFlatSpec {
+
+ def execute[T <: RawModule, X](dut: () => T, selector: T => Seq[X], desired: T => Seq[X])(implicit tTag: TypeTag[T]): Unit = {
+ val ret = new chisel3.stage.ChiselStage().run(
+ Seq(
+ new chisel3.stage.ChiselGeneratorAnnotation(dut),
+ SelectAspect(selector, desired),
+ new chisel3.stage.ChiselOutputFileAnnotation("test_run_dir/Select.fir")
+ )
+ )
+ }
+
+ "Test" should "pass if selecting correct registers" in {
+ execute(
+ () => new SelectTester(Seq(0, 1, 2)),
+ { dut: SelectTester => Select.registers(dut) },
+ { dut: SelectTester => Seq(dut.counter) }
+ )
+ }
+
+ "Test" should "pass if selecting correct wires" in {
+ execute(
+ () => new SelectTester(Seq(0, 1, 2)),
+ { dut: SelectTester => Select.wires(dut) },
+ { dut: SelectTester => Seq(dut.values) }
+ )
+ }
+
+ "Test" should "pass if selecting correct printfs" in {
+ execute(
+ () => new SelectTester(Seq(0, 1, 2)),
+ { dut: SelectTester => Seq(Select.printfs(dut).last) },
+ { dut: SelectTester =>
+ Seq(Select.Printf(
+ Seq(
+ When(Select.ops("eq")(dut).last.asInstanceOf[Bool]),
+ When(dut.nreset),
+ WhenNot(dut.overflow)
+ ),
+ Printable.pack("values(%d) = %d\n", dut.counter, dut.selected),
+ dut.clock
+ ))
+ }
+ )
+ }
+
+ "Test" should "pass if selecting correct connections" in {
+ execute(
+ () => new SelectTester(Seq(0, 1, 2)),
+ { dut: SelectTester => Select.connectionsTo(dut)(dut.counter) },
+ { dut: SelectTester =>
+ Seq(PredicatedConnect(Nil, dut.counter, dut.added, false),
+ PredicatedConnect(Seq(When(dut.overflow)), dut.counter, dut.zero, false))
+ }
+ )
+ }
+
+ "Test" should "pass if selecting ops by kind" in {
+ execute(
+ () => new SelectTester(Seq(0, 1, 2)),
+ { dut: SelectTester => Select.ops("tail")(dut) },
+ { dut: SelectTester => Seq(dut.added, dut.zero) }
+ )
+ }
+
+ "Test" should "pass if selecting ops" in {
+ execute(
+ () => new SelectTester(Seq(0, 1, 2)),
+ { dut: SelectTester => Select.ops(dut).collect { case ("tail", d) => d} },
+ { dut: SelectTester => Seq(dut.added, dut.zero) }
+ )
+ }
+
+ "Test" should "pass if selecting correct stops" in {
+ execute(
+ () => new SelectTester(Seq(0, 1, 2)),
+ { dut: SelectTester => Seq(Select.stops(dut).last) },
+ { dut: SelectTester =>
+ Seq(Select.Stop(
+ Seq(
+ When(Select.ops("eq")(dut).dropRight(1).last.asInstanceOf[Bool]),
+ When(dut.nreset),
+ WhenNot(dut.overflow)
+ ),
+ 1,
+ dut.clock
+ ))
+ }
+ )
+ }
+
+}
+
diff --git a/src/test/scala/chiselTests/stage/ChiselAnnotationsSpec.scala b/src/test/scala/chiselTests/stage/ChiselAnnotationsSpec.scala
index c89955f2..63b1001f 100644
--- a/src/test/scala/chiselTests/stage/ChiselAnnotationsSpec.scala
+++ b/src/test/scala/chiselTests/stage/ChiselAnnotationsSpec.scala
@@ -3,11 +3,9 @@
package chiselTests.stage
import org.scalatest.{FlatSpec, Matchers}
-
import chisel3._
-import chisel3.stage.{ChiselCircuitAnnotation, ChiselGeneratorAnnotation}
+import chisel3.stage.{ChiselCircuitAnnotation, ChiselGeneratorAnnotation, DesignAnnotation}
import chisel3.experimental.RawModule
-
import firrtl.options.OptionsException
class ChiselAnnotationsSpecFoo extends RawModule {
@@ -33,7 +31,9 @@ class ChiselAnnotationsSpec extends FlatSpec with Matchers {
it should "elaborate to a ChiselCircuitAnnotation" in {
val annotation = ChiselGeneratorAnnotation(() => new ChiselAnnotationsSpecFoo)
- annotation.elaborate shouldBe a [ChiselCircuitAnnotation]
+ val res = annotation.elaborate
+ res(0) shouldBe a [ChiselCircuitAnnotation]
+ res(1) shouldBe a [DesignAnnotation[ChiselAnnotationsSpecFoo]]
}
it should "throw an exception if elaboration fails" in {
@@ -45,7 +45,9 @@ class ChiselAnnotationsSpec extends FlatSpec with Matchers {
it should "elaborate from a String" in {
val annotation = ChiselGeneratorAnnotation("chiselTests.stage.ChiselAnnotationsSpecFoo")
- annotation.elaborate shouldBe a [ChiselCircuitAnnotation]
+ val res = annotation.elaborate
+ res(0) shouldBe a [ChiselCircuitAnnotation]
+ res(1) shouldBe a [DesignAnnotation[ChiselAnnotationsSpecFoo]]
}
it should "throw an exception if elaboration from a String refers to nonexistant class" in {