From 03e43cc1d6bf4bf6cb36a81082bc7e78bb279aad Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Mon, 21 May 2018 17:24:23 -0700 Subject: [PATCH 01/10] Initial version of Chisel IR to FIRRTL converter --- .../main/scala/chisel3/core/Printable.scala | 2 +- .../chisel3/internal/firrtl/Converter.scala | 237 ++++++++++++++++++ .../scala/chiselTests/PrintableSpec.scala | 6 +- 3 files changed, 240 insertions(+), 5 deletions(-) create mode 100644 src/main/scala/chisel3/internal/firrtl/Converter.scala diff --git a/chiselFrontend/src/main/scala/chisel3/core/Printable.scala b/chiselFrontend/src/main/scala/chisel3/core/Printable.scala index 8c35c33a95a..7b3d8d4ff74 100644 --- a/chiselFrontend/src/main/scala/chisel3/core/Printable.scala +++ b/chiselFrontend/src/main/scala/chisel3/core/Printable.scala @@ -128,7 +128,7 @@ case class PString(str: String) extends Printable { (str replaceAll ("%", "%%"), List.empty) } /** Superclass for Firrtl format specifiers for Bits */ -sealed abstract class FirrtlFormat(specifier: Char) extends Printable { +sealed abstract class FirrtlFormat(private[chisel3] val specifier: Char) extends Printable { def bits: Bits def unpack(ctx: Component): (String, Iterable[String]) = { (s"%$specifier", List(bits.ref.fullName(ctx))) diff --git a/src/main/scala/chisel3/internal/firrtl/Converter.scala b/src/main/scala/chisel3/internal/firrtl/Converter.scala new file mode 100644 index 00000000000..dead3ee4905 --- /dev/null +++ b/src/main/scala/chisel3/internal/firrtl/Converter.scala @@ -0,0 +1,237 @@ +// See LICENSE for license details. + +package chisel3.internal.firrtl +import chisel3._ +import chisel3.core.SpecifiedDirection +import chisel3.experimental._ +import chisel3.internal.sourceinfo.{NoSourceInfo, SourceLine, SourceInfo} +import firrtl.{ir => fir} +import chisel3.internal.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? + // TODO move into the Chisel IR? + def convert(arg: Arg, ctx: Component): fir.Expression = arg match { + 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), idx.toInt, fir.UnknownType) + case Index(imm, value) => + fir.SubAccess(convert(imm, ctx), convert(value, ctx), fir.UnknownType) + case ModuleIO(mod, name) => + if (mod eq ctx.id) fir.Reference(name, fir.UnknownType) + else fir.SubField(fir.Reference(mod.getRef.name, fir.UnknownType), name, fir.UnknownType) + 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") + } + + // alt indicates to a WhenEnd whether we're closing an alt or just a regular when + // 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) + + // Whens markers are flat so scope must be inferred + // TODO refactor + def convert(cmds: Seq[Command], ctx: Component): fir.Statement = { + @tailrec + def rec(acc: Queue[fir.Statement], + scope: List[WhenFrame]) + (cmds: Seq[Command]): Seq[fir.Statement] = { + if (cmds.isEmpty) { + assert(scope.isEmpty) + acc + } else cmds.head match { + 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) + } + val node = fir.DefNode(convert(e.sourceInfo), e.name, expr) + rec(acc :+ node, scope)(cmds.tail) + case e @ DefWire(info, id) => + val wire = fir.DefWire(convert(info), e.name, extractType(id)) + rec(acc :+ wire, scope)(cmds.tail) + case e @ DefReg(info, id, clock) => + val reg = fir.DefRegister(convert(info), e.name, extractType(id), convert(clock, ctx), + firrtl.Utils.zero, convert(id.getRef, ctx)) + rec(acc :+ reg, scope)(cmds.tail) + case e @ DefRegInit(info, id, clock, reset, init) => + val reg = fir.DefRegister(convert(info), e.name, extractType(id), convert(clock, ctx), + convert(reset, ctx), convert(init, ctx)) + rec(acc :+ reg, scope)(cmds.tail) + case e @ DefMemory(info, id, t, size) => + val mem = firrtl.CDefMemory(convert(info), e.name, extractType(t), size, false) + rec(acc :+ mem, scope)(cmds.tail) + case e @ DefSeqMemory(info, id, t, size) => + val mem = firrtl.CDefMemory(convert(info), e.name, extractType(t), size, true) + rec(acc :+ mem, scope)(cmds.tail) + case e: DefMemPort[_] => + val port = 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)) + rec(acc :+ port, scope)(cmds.tail) + case Connect(info, loc, exp) => + val con = fir.Connect(convert(info), convert(loc, ctx), convert(exp, ctx)) + rec(acc :+ con, scope)(cmds.tail) + case BulkConnect(info, loc, exp) => + val con = fir.PartialConnect(convert(info), convert(loc, ctx), convert(exp, ctx)) + rec(acc :+ con, scope)(cmds.tail) + case Attach(info, locs) => + val att = fir.Attach(convert(info), locs.map(l => convert(l, ctx))) + rec(acc :+ att, scope)(cmds.tail) + case DefInvalid(info, arg) => + val inv = fir.IsInvalid(convert(info), convert(arg, ctx)) + rec(acc :+ inv, scope)(cmds.tail) + case e @ DefInstance(info, id, _) => + val inst = fir.DefInstance(convert(info), e.name, id.name) + rec(acc :+ inst, scope)(cmds.tail) + 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 end @ 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)) + cmds.tail.headOption match { + case Some(AltBegin(_)) => + assert(!frame.alt, "Internal Error! Unexpected when structure!") + rec(Queue.empty, frame.copy(when = when, alt = true) +: scope.tail)(cmds.drop(2)) + case _ => // Not followed by otherwise + 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)) + val cmdsx = if (depth > 1) OtherwiseEnd(info, depth - 1) +: cmds.tail else cmds.tail + rec(scope.head.outer :+ when, scope.tail)(cmdsx) + case Stop(info, clock, ret) => + val stop = fir.Stop(convert(info), ret, convert(clock, ctx), firrtl.Utils.one) + rec(acc :+ stop, scope)(cmds.tail) + case Printf(info, clock, pable) => + val (fmt, args) = unpack(pable, ctx) + val p = fir.Print(convert(info), fir.StringLit(fmt), + args.map(a => convert(a, ctx)), convert(clock, ctx), firrtl.Utils.one) + rec(acc :+ p, scope)(cmds.tail) + } + } + 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 { + case _: Clock => fir.ClockType + 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, 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) +} + diff --git a/src/test/scala/chiselTests/PrintableSpec.scala b/src/test/scala/chiselTests/PrintableSpec.scala index d733ab8c5a9..c6ba0a1d43c 100644 --- a/src/test/scala/chiselTests/PrintableSpec.scala +++ b/src/test/scala/chiselTests/PrintableSpec.scala @@ -8,10 +8,8 @@ import chisel3.testers.BasicTester /* Printable Tests */ class PrintableSpec extends FlatSpec with Matchers { - private val PrintfRegex = """\s*printf\((.*)\).*""".r - // This regex is brittle, it relies on the first two arguments of the printf - // not containing quotes, problematic if Chisel were to emit UInt<1>("h01") - // instead of the current UInt<1>(1) for the enable signal + // This regex is brittle, it specifically finds the clock and enable signals followed by commas + private val PrintfRegex = """\s*printf\(\w+, [^,]+,(.*)\).*""".r private val StringRegex = """([^"]*)"(.*?)"(.*)""".r private case class Printf(str: String, args: Seq[String]) private def getPrintfs(firrtl: String): Seq[Printf] = { From 9fb2233febe4d2557b301031d6fe878b00853c83 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Mon, 21 May 2018 17:25:00 -0700 Subject: [PATCH 02/10] Use Convert instead of Emitter --- src/main/scala/chisel3/Driver.scala | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/scala/chisel3/Driver.scala b/src/main/scala/chisel3/Driver.scala index 25d254256e0..94b5e5eaa68 100644 --- a/src/main/scala/chisel3/Driver.scala +++ b/src/main/scala/chisel3/Driver.scala @@ -2,7 +2,7 @@ package chisel3 -import chisel3.internal.firrtl.Emitter +import chisel3.internal.firrtl.Converter import chisel3.experimental.{RawModule, RunFirrtlTransform} import java.io._ @@ -92,9 +92,10 @@ object Driver extends BackendCompilationUtilities { */ def elaborate[T <: RawModule](gen: () => T): Circuit = internal.Builder.build(Module(gen())) - def emit[T <: RawModule](gen: () => T): String = Emitter.emit(elaborate(gen)) + def emit[T <: RawModule](gen: () => T): String = Driver.emit(elaborate(gen)) - def emit[T <: RawModule](ir: Circuit): String = Emitter.emit(ir) + def emit[T <: RawModule](ir: Circuit): String = + Converter.convert(ir).serialize /** Elaborates the Module specified in the gen function into Verilog * @@ -111,7 +112,7 @@ object Driver extends BackendCompilationUtilities { def dumpFirrtl(ir: Circuit, optName: Option[File]): File = { val f = optName.getOrElse(new File(ir.name + ".fir")) val w = new FileWriter(f) - w.write(Emitter.emit(ir)) + w.write(Driver.emit(ir)) w.close() f } @@ -146,7 +147,7 @@ object Driver extends BackendCompilationUtilities { val chiselOptions = optionsManager.chiselOptions // use input because firrtl will be reading this - val firrtlString = Emitter.emit(circuit) + val firrtlString = emit(circuit) val firrtlFileName = firrtlOptions.getInputFileName(optionsManager) val firrtlFile = new File(firrtlFileName) From 48bfeac73b2a1ca98943abea4d47dbd8ce69b7da Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Wed, 23 May 2018 14:54:10 -0700 Subject: [PATCH 03/10] ULits with UnknownWidths should give minWidth in Converter This works around an assumption in FIRRTL that Literals never have UnknownWidth --- src/main/scala/chisel3/internal/firrtl/Converter.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/scala/chisel3/internal/firrtl/Converter.scala b/src/main/scala/chisel3/internal/firrtl/Converter.scala index dead3ee4905..248619aa272 100644 --- a/src/main/scala/chisel3/internal/firrtl/Converter.scala +++ b/src/main/scala/chisel3/internal/firrtl/Converter.scala @@ -52,6 +52,7 @@ private[chisel3] object Converter { case ModuleIO(mod, name) => if (mod eq ctx.id) fir.Reference(name, fir.UnknownType) else fir.SubField(fir.Reference(mod.getRef.name, fir.UnknownType), name, fir.UnknownType) + 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 From abaac71eba8bb1a5402c53332b66c31636ee9ae7 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Wed, 23 May 2018 15:15:01 -0700 Subject: [PATCH 04/10] Change Driver.execute to pass the circuit to FIRRTL directly --- src/main/scala/chisel3/Driver.scala | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/scala/chisel3/Driver.scala b/src/main/scala/chisel3/Driver.scala index 94b5e5eaa68..fa809e39e5a 100644 --- a/src/main/scala/chisel3/Driver.scala +++ b/src/main/scala/chisel3/Driver.scala @@ -146,8 +146,10 @@ object Driver extends BackendCompilationUtilities { val firrtlOptions = optionsManager.firrtlOptions val chiselOptions = optionsManager.chiselOptions - // use input because firrtl will be reading this - val firrtlString = emit(circuit) + val firrtlCircuit = Converter.convert(circuit) + + // Still emit to leave an artifact (and because this always has been the behavior) + val firrtlString = firrtlCircuit.serialize val firrtlFileName = firrtlOptions.getInputFileName(optionsManager) val firrtlFile = new File(firrtlFileName) @@ -155,6 +157,7 @@ object Driver extends BackendCompilationUtilities { w.write(firrtlString) w.close() + // Emit the annotations because it has always been the behavior val annotationFile = new File(optionsManager.getBuildFileName("anno.json")) val af = new FileWriter(annotationFile) val firrtlAnnos = circuit.annotations.map(_.toFirrtl) @@ -175,7 +178,7 @@ object Driver extends BackendCompilationUtilities { } /* This passes the firrtl source and annotations directly to firrtl */ optionsManager.firrtlOptions = optionsManager.firrtlOptions.copy( - firrtlSource = Some(firrtlString), + firrtlCircuit = Some(firrtlCircuit), annotations = optionsManager.firrtlOptions.annotations ++ firrtlAnnos, customTransforms = optionsManager.firrtlOptions.customTransforms ++ transforms.toList) From a0ea81e4453ca5065ed2c2c3c763311f373a100c Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Wed, 6 Jun 2018 18:50:11 -0700 Subject: [PATCH 05/10] Make Driver a little DRYer --- src/main/scala/chisel3/Driver.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/scala/chisel3/Driver.scala b/src/main/scala/chisel3/Driver.scala index fa809e39e5a..4c1287b9f73 100644 --- a/src/main/scala/chisel3/Driver.scala +++ b/src/main/scala/chisel3/Driver.scala @@ -92,10 +92,11 @@ object Driver extends BackendCompilationUtilities { */ def elaborate[T <: RawModule](gen: () => T): Circuit = internal.Builder.build(Module(gen())) + def toFirrtl(ir: Circuit): firrtl.ir.Circuit = Converter.convert(ir) + def emit[T <: RawModule](gen: () => T): String = Driver.emit(elaborate(gen)) - def emit[T <: RawModule](ir: Circuit): String = - Converter.convert(ir).serialize + def emit[T <: RawModule](ir: Circuit): String = toFirrtl(ir).serialize /** Elaborates the Module specified in the gen function into Verilog * From 6a5ccfd7eddb3898d4143b87ef5a735a9f1b09e6 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Fri, 8 Jun 2018 18:00:46 -0700 Subject: [PATCH 06/10] Convert commands to List in Converter, tail is slow on ArrayBuffer --- src/main/scala/chisel3/internal/firrtl/Converter.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/chisel3/internal/firrtl/Converter.scala b/src/main/scala/chisel3/internal/firrtl/Converter.scala index 248619aa272..d5db8e7add0 100644 --- a/src/main/scala/chisel3/internal/firrtl/Converter.scala +++ b/src/main/scala/chisel3/internal/firrtl/Converter.scala @@ -226,7 +226,7 @@ private[chisel3] object Converter { 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, ctx)) + 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) From 78fc4fc74f361229ff14e45fdcf2451e9f3a7f22 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Wed, 27 Jun 2018 20:22:54 -0700 Subject: [PATCH 07/10] Add Driver.dumpProto to directly emit protobuf --- src/main/scala/chisel3/Driver.scala | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/scala/chisel3/Driver.scala b/src/main/scala/chisel3/Driver.scala index 4c1287b9f73..ad5adc133ee 100644 --- a/src/main/scala/chisel3/Driver.scala +++ b/src/main/scala/chisel3/Driver.scala @@ -118,6 +118,14 @@ object Driver extends BackendCompilationUtilities { f } + def dumpProto(c: Circuit, optName: Option[File]): File = { + val f = optName.getOrElse(new File(c.name + ".fir")) + val ostream = new java.io.FileOutputStream(f) + val modules = c.components.map(m => () => Converter.convert(m)) + firrtl.proto.ToProto.writeToStreamFast(ostream, ir.NoInfo, modules, c.name) + f + } + private var target_dir: Option[String] = None def parseArgs(args: Array[String]): Unit = { for (i <- 0 until args.size) { From d4acc34b513a5b9a8d32bec39722e046e7274402 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Mon, 25 Jun 2018 17:48:45 -0700 Subject: [PATCH 08/10] Revert to using Emitter.emit instead of serialize on FIRRTL Circuit At the moment, the Converter is very fast, but FIRRTL serialize is much slower than the Chisel Emitter --- src/main/scala/chisel3/Driver.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/scala/chisel3/Driver.scala b/src/main/scala/chisel3/Driver.scala index ad5adc133ee..bc3e84c8b05 100644 --- a/src/main/scala/chisel3/Driver.scala +++ b/src/main/scala/chisel3/Driver.scala @@ -96,7 +96,7 @@ object Driver extends BackendCompilationUtilities { def emit[T <: RawModule](gen: () => T): String = Driver.emit(elaborate(gen)) - def emit[T <: RawModule](ir: Circuit): String = toFirrtl(ir).serialize + def emit[T <: RawModule](ir: Circuit): String = Emitter.emit(ir) /** Elaborates the Module specified in the gen function into Verilog * @@ -158,7 +158,7 @@ object Driver extends BackendCompilationUtilities { val firrtlCircuit = Converter.convert(circuit) // Still emit to leave an artifact (and because this always has been the behavior) - val firrtlString = firrtlCircuit.serialize + val firrtlString = Driver.emit(circuit) val firrtlFileName = firrtlOptions.getInputFileName(optionsManager) val firrtlFile = new File(firrtlFileName) From 3842874802174f9d680c25d394bca5f654acf435 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Thu, 28 Jun 2018 14:30:11 -0700 Subject: [PATCH 09/10] Add ScalaDoc to dumpFirrtl and dumpProto Also rename optName to optFile in dumpProto (didn't change dumpFirrtl because it is an existing public API) --- src/main/scala/chisel3/Driver.scala | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/main/scala/chisel3/Driver.scala b/src/main/scala/chisel3/Driver.scala index bc3e84c8b05..642f7caa663 100644 --- a/src/main/scala/chisel3/Driver.scala +++ b/src/main/scala/chisel3/Driver.scala @@ -110,6 +110,15 @@ object Driver extends BackendCompilationUtilities { } } + /** Dumps the elaborated Circuit to FIRRTL + * + * If no File is given as input, it will dump to a default filename based on the name of the + * Top Module + * + * @param c Elaborated Chisel Circuit + * @param optName Optional File to dump to + * @return The File the circuit was dumped to + */ def dumpFirrtl(ir: Circuit, optName: Option[File]): File = { val f = optName.getOrElse(new File(ir.name + ".fir")) val w = new FileWriter(f) @@ -118,9 +127,19 @@ object Driver extends BackendCompilationUtilities { f } - def dumpProto(c: Circuit, optName: Option[File]): File = { - val f = optName.getOrElse(new File(c.name + ".fir")) + /** Dumps the elaborated Circuit to ProtoBuf + * + * If no File is given as input, it will dump to a default filename based on the name of the + * Top Module + * + * @param c Elaborated Chisel Circuit + * @param optFile Optional File to dump to + * @return The File the circuit was dumped to + */ + def dumpProto(c: Circuit, optFile: Option[File]): File = { + val f = optFile.getOrElse(new File(c.name + ".pb")) val ostream = new java.io.FileOutputStream(f) + // Lazily convert modules to make intermediate objects garbage collectable val modules = c.components.map(m => () => Converter.convert(m)) firrtl.proto.ToProto.writeToStreamFast(ostream, ir.NoInfo, modules, c.name) f From a250e6e5079f791a7b75a76537563ee2feadeb1a Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Thu, 28 Jun 2018 15:38:37 -0700 Subject: [PATCH 10/10] Refactor Converter for Commands Split out simple (1:1) Command -> Statement translations. Add comments to clarify how flat When Commands are converted to nested FIRRTL Conditionals --- .../chisel3/internal/firrtl/Converter.scala | 211 ++++++++++-------- 1 file changed, 118 insertions(+), 93 deletions(-) diff --git a/src/main/scala/chisel3/internal/firrtl/Converter.scala b/src/main/scala/chisel3/internal/firrtl/Converter.scala index d5db8e7add0..97504aba19a 100644 --- a/src/main/scala/chisel3/internal/firrtl/Converter.scala +++ b/src/main/scala/chisel3/internal/firrtl/Converter.scala @@ -39,12 +39,16 @@ private[chisel3] object Converter { case MemPortDirection.RDWR => firrtl.MReadWrite } - // TODO memoize? - // TODO move into the Chisel IR? + // TODO + // * Memoize? + // * Move into the Chisel IR? def convert(arg: Arg, ctx: Component): fir.Expression = arg match { - 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 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), idx.toInt, fir.UnknownType) case Index(imm, value) => @@ -52,8 +56,10 @@ private[chisel3] object Converter { case ModuleIO(mod, name) => if (mod eq ctx.id) fir.Reference(name, fir.UnknownType) else fir.SubField(fir.Reference(mod.getRef.name, fir.UnknownType), name, fir.UnknownType) - case u @ ULit(n, UnknownWidth()) => fir.UIntLiteral(n, fir.IntWidth(u.minWidth)) - case ULit(n, w) => fir.UIntLiteral(n, convert(w)) + 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) @@ -64,15 +70,81 @@ private[chisel3] object Converter { 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") + case lit: ILit => + throwException(s"Internal Error! Unexpected ILit: $lit") } - // alt indicates to a WhenEnd whether we're closing an alt or just a regular when + /** Convert Commands that map 1:1 to Statements */ + def convertSimpleCommand(cmd: Command, ctx: Component): Option[fir.Statement] = cmd match { + 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) - // Whens markers are flat so scope must be inferred - // TODO refactor + /** 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 = { @tailrec def rec(acc: Queue[fir.Statement], @@ -81,88 +153,41 @@ private[chisel3] object Converter { if (cmds.isEmpty) { assert(scope.isEmpty) acc - } else cmds.head match { - 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) - } - val node = fir.DefNode(convert(e.sourceInfo), e.name, expr) - rec(acc :+ node, scope)(cmds.tail) - case e @ DefWire(info, id) => - val wire = fir.DefWire(convert(info), e.name, extractType(id)) - rec(acc :+ wire, scope)(cmds.tail) - case e @ DefReg(info, id, clock) => - val reg = fir.DefRegister(convert(info), e.name, extractType(id), convert(clock, ctx), - firrtl.Utils.zero, convert(id.getRef, ctx)) - rec(acc :+ reg, scope)(cmds.tail) - case e @ DefRegInit(info, id, clock, reset, init) => - val reg = fir.DefRegister(convert(info), e.name, extractType(id), convert(clock, ctx), - convert(reset, ctx), convert(init, ctx)) - rec(acc :+ reg, scope)(cmds.tail) - case e @ DefMemory(info, id, t, size) => - val mem = firrtl.CDefMemory(convert(info), e.name, extractType(t), size, false) - rec(acc :+ mem, scope)(cmds.tail) - case e @ DefSeqMemory(info, id, t, size) => - val mem = firrtl.CDefMemory(convert(info), e.name, extractType(t), size, true) - rec(acc :+ mem, scope)(cmds.tail) - case e: DefMemPort[_] => - val port = 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)) - rec(acc :+ port, scope)(cmds.tail) - case Connect(info, loc, exp) => - val con = fir.Connect(convert(info), convert(loc, ctx), convert(exp, ctx)) - rec(acc :+ con, scope)(cmds.tail) - case BulkConnect(info, loc, exp) => - val con = fir.PartialConnect(convert(info), convert(loc, ctx), convert(exp, ctx)) - rec(acc :+ con, scope)(cmds.tail) - case Attach(info, locs) => - val att = fir.Attach(convert(info), locs.map(l => convert(l, ctx))) - rec(acc :+ att, scope)(cmds.tail) - case DefInvalid(info, arg) => - val inv = fir.IsInvalid(convert(info), convert(arg, ctx)) - rec(acc :+ inv, scope)(cmds.tail) - case e @ DefInstance(info, id, _) => - val inst = fir.DefInstance(convert(info), e.name, id.name) - rec(acc :+ inst, scope)(cmds.tail) - 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 end @ 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)) - cmds.tail.headOption match { - case Some(AltBegin(_)) => - assert(!frame.alt, "Internal Error! Unexpected when structure!") - rec(Queue.empty, frame.copy(when = when, alt = true) +: scope.tail)(cmds.drop(2)) - case _ => // Not followed by otherwise - 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)) - val cmdsx = if (depth > 1) OtherwiseEnd(info, depth - 1) +: cmds.tail else cmds.tail - rec(scope.head.outer :+ when, scope.tail)(cmdsx) - case Stop(info, clock, ret) => - val stop = fir.Stop(convert(info), ret, convert(clock, ctx), firrtl.Utils.one) - rec(acc :+ stop, scope)(cmds.tail) - case Printf(info, clock, pable) => - val (fmt, args) = unpack(pable, ctx) - val p = fir.Print(convert(info), fir.StringLit(fmt), - args.map(a => convert(a, ctx)), convert(clock, ctx), firrtl.Utils.one) - rec(acc :+ p, scope)(cmds.tail) + } 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) + } } } fir.Block(rec(Queue.empty, List.empty)(cmds))