diff options
| author | Adam Izraelevitz | 2019-08-12 15:49:42 -0700 |
|---|---|---|
| committer | GitHub | 2019-08-12 15:49:42 -0700 |
| commit | fddb5943b1d36925a5435d327c3312572e98ca58 (patch) | |
| tree | b22e3a544dbb265dead955544c75bf7abddb7c69 | |
| parent | 466ffbc9ca4fcca73d56f849df9e2753f68c53a8 (diff) | |
Aspect-Oriented Programming for Chisel (#1077)
Added Aspects to Chisel, enabling a mechanism for dependency injection to hardware modules.
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 { |
