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 /chiselFrontend/src/main/scala | |
| parent | 466ffbc9ca4fcca73d56f849df9e2753f68c53a8 (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')
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) +} + |
