summaryrefslogtreecommitdiff
path: root/chiselFrontend/src/main/scala
diff options
context:
space:
mode:
authorAdam Izraelevitz2019-08-12 15:49:42 -0700
committerGitHub2019-08-12 15:49:42 -0700
commitfddb5943b1d36925a5435d327c3312572e98ca58 (patch)
treeb22e3a544dbb265dead955544c75bf7abddb7c69 /chiselFrontend/src/main/scala
parent466ffbc9ca4fcca73d56f849df9e2753f68c53a8 (diff)
Aspect-Oriented Programming for Chisel (#1077)
Added Aspects to Chisel, enabling a mechanism for dependency injection to hardware modules.
Diffstat (limited to 'chiselFrontend/src/main/scala')
-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.scala267
9 files changed, 463 insertions, 40 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/chiselFrontend/src/main/scala/chisel3/internal/firrtl/Converter.scala b/chiselFrontend/src/main/scala/chisel3/internal/firrtl/Converter.scala
new file mode 100644
index 00000000..cdc55b59
--- /dev/null
+++ b/chiselFrontend/src/main/scala/chisel3/internal/firrtl/Converter.scala
@@ -0,0 +1,267 @@
+// See LICENSE for license details.
+
+package chisel3.internal.firrtl
+import chisel3._
+import chisel3.experimental._
+import chisel3.internal.sourceinfo.{NoSourceInfo, SourceLine, SourceInfo}
+import firrtl.{ir => fir}
+import chisel3.internal.{castToInt, throwException}
+
+import scala.annotation.tailrec
+import scala.collection.immutable.Queue
+
+private[chisel3] object Converter {
+ // TODO modeled on unpack method on Printable, refactor?
+ def unpack(pable: Printable, ctx: Component): (String, Seq[Arg]) = pable match {
+ case Printables(pables) =>
+ val (fmts, args) = pables.map(p => unpack(p, ctx)).unzip
+ (fmts.mkString, args.flatten.toSeq)
+ case PString(str) => (str.replaceAll("%", "%%"), List.empty)
+ case format: FirrtlFormat =>
+ ("%" + format.specifier, List(format.bits.ref))
+ case Name(data) => (data.ref.name, List.empty)
+ case FullName(data) => (data.ref.fullName(ctx), List.empty)
+ case Percent => ("%%", List.empty)
+ }
+
+ def convert(info: SourceInfo): fir.Info = info match {
+ case _: NoSourceInfo => fir.NoInfo
+ case SourceLine(fn, line, col) => fir.FileInfo(fir.StringLit(s"$fn $line:$col"))
+ }
+
+ def convert(op: PrimOp): fir.PrimOp = firrtl.PrimOps.fromString(op.name)
+
+ def convert(dir: MemPortDirection): firrtl.MPortDir = dir match {
+ case MemPortDirection.INFER => firrtl.MInfer
+ case MemPortDirection.READ => firrtl.MRead
+ case MemPortDirection.WRITE => firrtl.MWrite
+ case MemPortDirection.RDWR => firrtl.MReadWrite
+ }
+
+ // TODO
+ // * Memoize?
+ // * Move into the Chisel IR?
+ def convert(arg: Arg, ctx: Component): fir.Expression = arg match { // scalastyle:ignore cyclomatic.complexity
+ case Node(id) =>
+ convert(id.getRef, ctx)
+ case Ref(name) =>
+ fir.Reference(name, fir.UnknownType)
+ case Slot(imm, name) =>
+ fir.SubField(convert(imm, ctx), name, fir.UnknownType)
+ case Index(imm, ILit(idx)) =>
+ fir.SubIndex(convert(imm, ctx), castToInt(idx, "Index"), fir.UnknownType)
+ case Index(imm, value) =>
+ fir.SubAccess(convert(imm, ctx), convert(value, ctx), fir.UnknownType)
+ case ModuleIO(mod, name) =>
+ // scalastyle:off if.brace
+ if (mod eq ctx.id) fir.Reference(name, fir.UnknownType)
+ else fir.SubField(fir.Reference(mod.getRef.name, fir.UnknownType), name, fir.UnknownType)
+ // scalastyle:on if.brace
+ case u @ ULit(n, UnknownWidth()) =>
+ fir.UIntLiteral(n, fir.IntWidth(u.minWidth))
+ case ULit(n, w) =>
+ fir.UIntLiteral(n, convert(w))
+ case slit @ SLit(n, w) => fir.SIntLiteral(n, convert(w))
+ val unsigned = if (n < 0) (BigInt(1) << slit.width.get) + n else n
+ val uint = convert(ULit(unsigned, slit.width), ctx)
+ fir.DoPrim(firrtl.PrimOps.AsSInt, Seq(uint), Seq.empty, fir.UnknownType)
+ // TODO Simplify
+ case fplit @ FPLit(n, w, bp) =>
+ val unsigned = if (n < 0) (BigInt(1) << fplit.width.get) + n else n
+ val uint = convert(ULit(unsigned, fplit.width), ctx)
+ val lit = bp.asInstanceOf[KnownBinaryPoint].value
+ fir.DoPrim(firrtl.PrimOps.AsFixedPoint, Seq(uint), Seq(lit), fir.UnknownType)
+ case lit: ILit =>
+ throwException(s"Internal Error! Unexpected ILit: $lit")
+ }
+
+ /** Convert Commands that map 1:1 to Statements */
+ def convertSimpleCommand(cmd: Command, ctx: Component): Option[fir.Statement] = cmd match { // scalastyle:ignore cyclomatic.complexity line.size.limit
+ case e: DefPrim[_] =>
+ val consts = e.args.collect { case ILit(i) => i }
+ val args = e.args.flatMap {
+ case _: ILit => None
+ case other => Some(convert(other, ctx))
+ }
+ val expr = e.op.name match {
+ case "mux" =>
+ assert(args.size == 3, s"Mux with unexpected args: $args")
+ fir.Mux(args(0), args(1), args(2), fir.UnknownType)
+ case _ =>
+ fir.DoPrim(convert(e.op), args, consts, fir.UnknownType)
+ }
+ Some(fir.DefNode(convert(e.sourceInfo), e.name, expr))
+ case e @ DefWire(info, id) =>
+ Some(fir.DefWire(convert(info), e.name, extractType(id)))
+ case e @ DefReg(info, id, clock) =>
+ Some(fir.DefRegister(convert(info), e.name, extractType(id), convert(clock, ctx),
+ firrtl.Utils.zero, convert(id.getRef, ctx)))
+ case e @ DefRegInit(info, id, clock, reset, init) =>
+ Some(fir.DefRegister(convert(info), e.name, extractType(id), convert(clock, ctx),
+ convert(reset, ctx), convert(init, ctx)))
+ case e @ DefMemory(info, id, t, size) =>
+ Some(firrtl.CDefMemory(convert(info), e.name, extractType(t), size, false))
+ case e @ DefSeqMemory(info, id, t, size) =>
+ Some(firrtl.CDefMemory(convert(info), e.name, extractType(t), size, true))
+ case e: DefMemPort[_] =>
+ Some(firrtl.CDefMPort(convert(e.sourceInfo), e.name, fir.UnknownType,
+ e.source.fullName(ctx), Seq(convert(e.index, ctx), convert(e.clock, ctx)), convert(e.dir)))
+ case Connect(info, loc, exp) =>
+ Some(fir.Connect(convert(info), convert(loc, ctx), convert(exp, ctx)))
+ case BulkConnect(info, loc, exp) =>
+ Some(fir.PartialConnect(convert(info), convert(loc, ctx), convert(exp, ctx)))
+ case Attach(info, locs) =>
+ Some(fir.Attach(convert(info), locs.map(l => convert(l, ctx))))
+ case DefInvalid(info, arg) =>
+ Some(fir.IsInvalid(convert(info), convert(arg, ctx)))
+ case e @ DefInstance(info, id, _) =>
+ Some(fir.DefInstance(convert(info), e.name, id.name))
+ case Stop(info, clock, ret) =>
+ Some(fir.Stop(convert(info), ret, convert(clock, ctx), firrtl.Utils.one))
+ case Printf(info, clock, pable) =>
+ val (fmt, args) = unpack(pable, ctx)
+ Some(fir.Print(convert(info), fir.StringLit(fmt),
+ args.map(a => convert(a, ctx)), convert(clock, ctx), firrtl.Utils.one))
+ case _ => None
+ }
+
+ /** Internal datastructure to help translate Chisel's flat Command structure to FIRRTL's AST
+ *
+ * In particular, when scoping is translated from flat with begin end to a nested datastructure
+ *
+ * @param when Current when Statement, holds info, condition, and consequence as they are
+ * available
+ * @param outer Already converted Statements that precede the current when block in the scope in
+ * which the when is defined (ie. 1 level up from the scope inside the when)
+ * @param alt Indicates if currently processing commands in the alternate (else) of the when scope
+ */
+ // TODO we should probably have a different structure in the IR to close elses
+ private case class WhenFrame(when: fir.Conditionally, outer: Queue[fir.Statement], alt: Boolean)
+
+ /** Convert Chisel IR Commands into FIRRTL Statements
+ *
+ * @note ctx is needed because references to ports translate differently when referenced within
+ * the module in which they are defined vs. parent modules
+ * @param cmds Chisel IR Commands to convert
+ * @param ctx Component (Module) context within which we are translating
+ * @return FIRRTL Statement that is equivalent to the input cmds
+ */
+ def convert(cmds: Seq[Command], ctx: Component): fir.Statement = { // scalastyle:ignore cyclomatic.complexity
+ @tailrec
+ // scalastyle:off if.brace
+ def rec(acc: Queue[fir.Statement],
+ scope: List[WhenFrame])
+ (cmds: Seq[Command]): Seq[fir.Statement] = {
+ if (cmds.isEmpty) {
+ assert(scope.isEmpty)
+ acc
+ } else convertSimpleCommand(cmds.head, ctx) match {
+ // Most Commands map 1:1
+ case Some(stmt) =>
+ rec(acc :+ stmt, scope)(cmds.tail)
+ // When scoping logic does not map 1:1 and requires pushing/popping WhenFrames
+ // Please see WhenFrame for more details
+ case None => cmds.head match {
+ case WhenBegin(info, pred) =>
+ val when = fir.Conditionally(convert(info), convert(pred, ctx), fir.EmptyStmt, fir.EmptyStmt)
+ val frame = WhenFrame(when, acc, false)
+ rec(Queue.empty, frame +: scope)(cmds.tail)
+ case WhenEnd(info, depth, _) =>
+ val frame = scope.head
+ val when = if (frame.alt) frame.when.copy(alt = fir.Block(acc))
+ else frame.when.copy(conseq = fir.Block(acc))
+ // Check if this when has an else
+ cmds.tail.headOption match {
+ case Some(AltBegin(_)) =>
+ assert(!frame.alt, "Internal Error! Unexpected when structure!") // Only 1 else per when
+ rec(Queue.empty, frame.copy(when = when, alt = true) +: scope.tail)(cmds.drop(2))
+ case _ => // Not followed by otherwise
+ // If depth > 0 then we need to close multiple When scopes so we add a new WhenEnd
+ // If we're nested we need to add more WhenEnds to ensure each When scope gets
+ // properly closed
+ val cmdsx = if (depth > 0) WhenEnd(info, depth - 1, false) +: cmds.tail else cmds.tail
+ rec(frame.outer :+ when, scope.tail)(cmdsx)
+ }
+ case OtherwiseEnd(info, depth) =>
+ val frame = scope.head
+ val when = frame.when.copy(alt = fir.Block(acc))
+ // TODO For some reason depth == 1 indicates the last closing otherwise whereas
+ // depth == 0 indicates last closing when
+ val cmdsx = if (depth > 1) OtherwiseEnd(info, depth - 1) +: cmds.tail else cmds.tail
+ rec(scope.head.outer :+ when, scope.tail)(cmdsx)
+ }
+ }
+ }
+ // scalastyle:on if.brace
+ fir.Block(rec(Queue.empty, List.empty)(cmds))
+ }
+
+ def convert(width: Width): fir.Width = width match {
+ case UnknownWidth() => fir.UnknownWidth
+ case KnownWidth(value) => fir.IntWidth(value)
+ }
+
+ def convert(bp: BinaryPoint): fir.Width = bp match {
+ case UnknownBinaryPoint => fir.UnknownWidth
+ case KnownBinaryPoint(value) => fir.IntWidth(value)
+ }
+
+ private def firrtlUserDirOf(d: Data): SpecifiedDirection = d match {
+ case d: Vec[_] =>
+ SpecifiedDirection.fromParent(d.specifiedDirection, firrtlUserDirOf(d.sample_element))
+ case d => d.specifiedDirection
+ }
+
+ def extractType(data: Data, clearDir: Boolean = false): fir.Type = data match { // scalastyle:ignore cyclomatic.complexity line.size.limit
+ case _: Clock => fir.ClockType
+ case d: EnumType => fir.UIntType(convert(d.width))
+ case d: UInt => fir.UIntType(convert(d.width))
+ case d: SInt => fir.SIntType(convert(d.width))
+ case d: FixedPoint => fir.FixedType(convert(d.width), convert(d.binaryPoint))
+ case d: Analog => fir.AnalogType(convert(d.width))
+ case d: Vec[_] => fir.VectorType(extractType(d.sample_element, clearDir), d.length)
+ case d: Record =>
+ val childClearDir = clearDir ||
+ d.specifiedDirection == SpecifiedDirection.Input || d.specifiedDirection == SpecifiedDirection.Output
+ def eltField(elt: Data): fir.Field = (childClearDir, firrtlUserDirOf(elt)) match {
+ case (true, _) => fir.Field(elt.getRef.name, fir.Default, extractType(elt, true))
+ case (false, SpecifiedDirection.Unspecified | SpecifiedDirection.Output) =>
+ fir.Field(elt.getRef.name, fir.Default, extractType(elt, false))
+ case (false, SpecifiedDirection.Flip | SpecifiedDirection.Input) =>
+ fir.Field(elt.getRef.name, fir.Flip, extractType(elt, false))
+ }
+ fir.BundleType(d.elements.toIndexedSeq.reverse.map { case (_, e) => eltField(e) })
+ }
+
+ def convert(name: String, param: Param): fir.Param = param match {
+ case IntParam(value) => fir.IntParam(name, value)
+ case DoubleParam(value) => fir.DoubleParam(name, value)
+ case StringParam(value) => fir.StringParam(name, fir.StringLit(value))
+ case RawParam(value) => fir.RawStringParam(name, value)
+ }
+ def convert(port: Port, topDir: SpecifiedDirection = SpecifiedDirection.Unspecified): fir.Port = {
+ val resolvedDir = SpecifiedDirection.fromParent(topDir, port.dir)
+ val dir = resolvedDir match {
+ case SpecifiedDirection.Unspecified | SpecifiedDirection.Output => fir.Output
+ case SpecifiedDirection.Flip | SpecifiedDirection.Input => fir.Input
+ }
+ val clearDir = resolvedDir match {
+ case SpecifiedDirection.Input | SpecifiedDirection.Output => true
+ case SpecifiedDirection.Unspecified | SpecifiedDirection.Flip => false
+ }
+ val tpe = extractType(port.id, clearDir)
+ fir.Port(fir.NoInfo, port.id.getRef.name, dir, tpe)
+ }
+
+ def convert(component: Component): fir.DefModule = component match {
+ case ctx @ DefModule(_, name, ports, cmds) =>
+ fir.Module(fir.NoInfo, name, ports.map(p => convert(p)), convert(cmds.toList, ctx))
+ case ctx @ DefBlackBox(id, name, ports, topDir, params) =>
+ fir.ExtModule(fir.NoInfo, name, ports.map(p => convert(p, topDir)), id.desiredName,
+ params.map { case (name, p) => convert(name, p) }.toSeq)
+ }
+
+ def convert(circuit: Circuit): fir.Circuit =
+ fir.Circuit(fir.NoInfo, circuit.components.map(convert), circuit.name)
+}
+