diff options
| author | Nicolas Machado | 2021-09-28 20:18:48 -0700 |
|---|---|---|
| committer | GitHub | 2021-09-29 03:18:48 +0000 |
| commit | e70ee5367c864e55ff16637430b712666b7dbd2b (patch) | |
| tree | a448d6cb6f232f31ef05573f7c5fd43f950060a9 /src/main | |
| parent | a921e1230b389be87d993f3016cb46174b1ebfad (diff) | |
Add RTLIL Backend. (#2331)
* Added RTLIL Backend.
* Add test for Rtlil Backend, fix per-module file emission, scalafmt, and apply bugfixes for inconsistencies found during testing.
* Fix build on scala 2.13
* Add additional equivalence test, make some bugfixes and perf opts to the emitter.
* Final changes as requested by Kevin, code cleanup, add support for formal cells.
Diffstat (limited to 'src/main')
| -rw-r--r-- | src/main/scala/firrtl/Emitter.scala | 5 | ||||
| -rw-r--r-- | src/main/scala/firrtl/backends/experimental/rtlil/RtlilEmitter.scala | 1077 | ||||
| -rw-r--r-- | src/main/scala/firrtl/stage/Forms.scala | 4 | ||||
| -rw-r--r-- | src/main/scala/firrtl/transforms/CombineCats.scala | 4 |
4 files changed, 1087 insertions, 3 deletions
diff --git a/src/main/scala/firrtl/Emitter.scala b/src/main/scala/firrtl/Emitter.scala index 0814c268..40321621 100644 --- a/src/main/scala/firrtl/Emitter.scala +++ b/src/main/scala/firrtl/Emitter.scala @@ -5,6 +5,7 @@ package firrtl import java.io.File import firrtl.annotations.NoTargetAnnotation import firrtl.backends.experimental.smt.{Btor2Emitter, SMTLibEmitter} +import firrtl.backends.experimental.rtlil.RtlilEmitter import firrtl.backends.proto.{Emitter => ProtoEmitter} import firrtl.options.Viewer.view import firrtl.options.{CustomFileEmission, Dependency, HasShellOptions, PhaseException, ShellOption} @@ -60,6 +61,8 @@ object EmitCircuitAnnotation extends HasShellOptions { Seq(RunFirrtlTransformAnnotation(Dependency(Btor2Emitter)), EmitCircuitAnnotation(Btor2Emitter.getClass)) case "experimental-smt2" | "smt2" => Seq(RunFirrtlTransformAnnotation(Dependency(SMTLibEmitter)), EmitCircuitAnnotation(SMTLibEmitter.getClass)) + case "experimental-rtlil" => + Seq(RunFirrtlTransformAnnotation(Dependency[RtlilEmitter]), EmitCircuitAnnotation(classOf[RtlilEmitter])) case _ => throw new PhaseException(s"Unknown emitter '$a'! (Did you misspell it?)") }, helpText = "Run the specified circuit emitter (all modules in one file)", @@ -146,6 +149,8 @@ object EmitAllModulesAnnotation extends HasShellOptions { RunFirrtlTransformAnnotation(new SystemVerilogEmitter), EmitAllModulesAnnotation(classOf[SystemVerilogEmitter]) ) + case "experimental-rtlil" => + Seq(RunFirrtlTransformAnnotation(Dependency[RtlilEmitter]), EmitAllModulesAnnotation(classOf[RtlilEmitter])) case _ => throw new PhaseException(s"Unknown emitter '$a'! (Did you misspell it?)") }, helpText = "Run the specified module emitter (one file per module)", diff --git a/src/main/scala/firrtl/backends/experimental/rtlil/RtlilEmitter.scala b/src/main/scala/firrtl/backends/experimental/rtlil/RtlilEmitter.scala new file mode 100644 index 00000000..97603dbb --- /dev/null +++ b/src/main/scala/firrtl/backends/experimental/rtlil/RtlilEmitter.scala @@ -0,0 +1,1077 @@ +// SPDX-License-Identifier: Apache-2.0 + +package firrtl.backends.experimental.rtlil + +import java.io.Writer +import firrtl._ +import firrtl.PrimOps._ +import firrtl.ir._ +import firrtl.Utils.{throwInternalError, _} +import firrtl.WrappedExpression._ +import firrtl.traversals.Foreachers._ +import firrtl.annotations._ +import firrtl.options.Viewer.view +import firrtl.options.{CustomFileEmission, Dependency} +import firrtl.passes.LowerTypes +import firrtl.passes.MemPortUtils.memPortField +import firrtl.stage.{FirrtlOptions, TransformManager} + +import scala.annotation.tailrec +import scala.collection.mutable +import scala.collection.mutable.ArrayBuffer +import scala.language.postfixOps + +case class EmittedRtlilCircuitAnnotation(name: String, value: String, outputSuffix: String) + extends NoTargetAnnotation + with CustomFileEmission { + override protected def baseFileName(annotations: AnnotationSeq): String = + view[FirrtlOptions](annotations).outputFileName.getOrElse(name) + override protected def suffix: Option[String] = Some(outputSuffix) + override def getBytes: Iterable[Byte] = value.getBytes +} +case class EmittedRtlilModuleAnnotation(name: String, value: String, outputSuffix: String) + extends NoTargetAnnotation + with CustomFileEmission { + override protected def baseFileName(annotations: AnnotationSeq): String = + view[FirrtlOptions](annotations).outputFileName.getOrElse(name) + override protected def suffix: Option[String] = Some(outputSuffix) + override def getBytes: Iterable[Byte] = value.getBytes +} + +private[firrtl] class RtlilEmitter extends SeqTransform with Emitter with DependencyAPIMigration { + + override def prerequisites: Seq[TransformManager.TransformDependency] = + Seq( + Dependency[firrtl.transforms.CombineCats], + Dependency(firrtl.passes.memlib.VerilogMemDelays) + ) ++: firrtl.stage.Forms.LowFormOptimized + + override def outputSuffix: String = ".il" + val tab = " " + + override def transforms: Seq[Transform] = new TransformManager(prerequisites).flattenedTransformOrder + + def emit(state: CircuitState, writer: Writer): Unit = { + val cs = runTransforms(state) + val emissionOptions = new EmissionOptions(cs.annotations) + val moduleMap = cs.circuit.modules.map(m => m.name -> m).toMap + cs.circuit.modules.foreach { + case DescribedMod(d, pds, m: Module) => + val renderer = new RtlilRender(d, pds, m, moduleMap, cs.circuit.main, emissionOptions)(writer) + renderer.emit_rtlil() + case m: Module => + val renderer = new RtlilRender(m, moduleMap, cs.circuit.main, emissionOptions)(writer) + renderer.emit_rtlil() + case _ => // do nothing + } + } + + override def execute(state: CircuitState): CircuitState = { + val writerToString = + (writer: java.io.StringWriter) => writer.toString.replaceAll("""(?m) +$""", "") // trim trailing whitespace + + val newAnnos = state.annotations.flatMap { + case EmitCircuitAnnotation(a) if this.getClass == a => + val writer = new java.io.StringWriter + emit(state, writer) + Seq( + EmittedRtlilModuleAnnotation(state.circuit.main, writerToString(writer), outputSuffix) + ) + + case EmitAllModulesAnnotation(a) if this.getClass == a => + val cs = runTransforms(state) + val emissionOptions = new EmissionOptions(cs.annotations) + val moduleMap = cs.circuit.modules.map(m => m.name -> m).toMap + + cs.circuit.modules.flatMap { + case DescribedMod(d, pds, module: Module) => + val writer = new java.io.StringWriter + val renderer = new RtlilRender(d, pds, module, moduleMap, cs.circuit.main, emissionOptions)(writer) + renderer.emit_rtlil() + Some( + EmittedRtlilModuleAnnotation(module.name, writerToString(writer), outputSuffix) + ) + case module: Module => + val writer = new java.io.StringWriter + val renderer = new RtlilRender(module, moduleMap, cs.circuit.main, emissionOptions)(writer) + renderer.emit_rtlil() + Some( + EmittedRtlilModuleAnnotation(module.name, writerToString(writer), outputSuffix) + ) + case _ => None + } + case _ => Seq() + } + state.copy(annotations = newAnnos ++ state.annotations) + } + + private class RtlilRender( + description: Seq[Description], + portDescriptions: Map[String, Seq[Description]], + m: Module, + moduleMap: Map[String, DefModule], + circuitName: String, + emissionOptions: EmissionOptions + )( + implicit writer: Writer) { + def this( + m: Module, + moduleMap: Map[String, DefModule], + circuitName: String, + emissionOptions: EmissionOptions + )( + implicit writer: Writer + ) = { + this(Seq(), Map.empty, m, moduleMap, circuitName, emissionOptions)(writer) + } + + private val netlist: mutable.LinkedHashMap[WrappedExpression, InfoExpr] = mutable.LinkedHashMap() + private val namespace: Namespace = Namespace(m) + + private val portdefs: ArrayBuffer[Seq[Any]] = ArrayBuffer[Seq[Any]]() + private val declares: ArrayBuffer[Seq[Any]] = ArrayBuffer() + private val instdeclares: mutable.Map[String, InstInfo] = mutable.Map() + private val assigns: ArrayBuffer[Seq[Any]] = ArrayBuffer() + private val attachSynAssigns: ArrayBuffer[Seq[Any]] = ArrayBuffer() + private val processes: ArrayBuffer[Seq[Any]] = ArrayBuffer() + // Used to determine type of initvar for initializing memories + private val initials: ArrayBuffer[Seq[Any]] = ArrayBuffer() + private val formals: ArrayBuffer[Seq[Any]] = ArrayBuffer() + private val moduleTarget: ModuleTarget = CircuitTarget(circuitName).module(m.name) + + private def getLeadingTabs(x: Any): String = { + x match { + case seq: Seq[_] => + val head = seq.takeWhile(_ == tab).mkString + val tail = seq.dropWhile(_ == tab).headOption.map(getLeadingTabs).getOrElse(tab) + head + tail + case _ => tab + } + } + + private def emit(x: Any)(implicit w: Writer): Unit = { + this.emitCol(x, 0, getLeadingTabs(x))(writer) + } + + private def emit(x: Any, top: Int)(implicit w: Writer): Unit = { + emitCol(x, top, "")(writer) + } + + private def emitCol(x: Any, top: Int, tabs: String)(implicit w: Writer): Unit = { + x match { + case e: SrcInfo => w.write(e.str_rep) + case e: Reference => w.write(ref_to_name(e)) + case e: ValidIf => emitCol(Seq(e.value), top + 1, tabs)(writer) + case e: WSubField => w.write(SrcInfo(e).str_rep) + case e: WSubAccess => + w.write("\\" + s"${LowerTypes.loweredName(e.expr)} [ ${LowerTypes.loweredName(e.index)} ]") + case e: Literal => w.write(bigint_to_str_rep(e.value, get_type_width(e.tpe))) + case t: GroundType => w.write(stringify(t)) + case t: VectorType => + emit(t.tpe, top + 1)(writer) + w.write(s"[${t.size - 1}:0]") + case s: String => w.write(s) + case i: Int => w.write(i.toString) + case i: Long => w.write(i.toString) + case i: BigInt => w.write(bigint_to_str_rep(i, if (i > 0) i.bitLength else i.bitLength + 1)) + case i: Info => + infos_to_attr(i) match { + case Some(attr) => + w.write(attr) + case None => + } + case s: Seq[Any] => + s.foreach { e => emitCol(e, top + 1, tabs)(writer) } + if (top == 0) + w.write("\n") + case x => throwInternalError(s"trying to emit unsupported operation: $x") + } + } + + private def build_netlist(s: Statement): Unit = { + s.foreach(build_netlist) + s match { + case sx: Connect => netlist(sx.loc) = InfoExpr(sx.info, sx.expr) + case _: IsInvalid => error("Should have removed these!") + // TODO Since only register update and memories use the netlist anymore, I think nodes are unnecessary + case sx: DefNode => + val e = WRef(sx.name, sx.value.tpe, NodeKind, SourceFlow) + netlist(e) = InfoExpr(sx.info, sx.value) + case _ => + } + } + + @tailrec + private def remove_root(ex: Expression): Expression = ex match { + case ex: WSubField => + ex.expr match { + case e: WSubField => remove_root(e) + case _: WRef => WRef(ex.name, ex.tpe, InstanceKind, UnknownFlow) + } + case _ => throwInternalError(s"shouldn't be here: remove_root($ex)") + } + + private def stringify(tpe: GroundType): String = tpe match { + case _: UIntType | _: AnalogType => + val wx = bitWidth(tpe) + if (wx > 1) s"width $wx" else "" + case _: SIntType => + val wx = bitWidth(tpe) + if (wx > 1) s"signed width $wx" else "signed" + case ClockType | AsyncResetType => "" + case _ => throwInternalError(s"trying to write unsupported type in the Rtlil Emitter: $tpe") + } + + private def stringify(param: Param): String = param match { + case IntParam(name, value) => + val lit = + if (value.isValidInt) { + s"$value" + } else { + val blen = value.bitLength + if (value > 0) s"$blen'd$value" else s"-${blen + 1}'sd${value.abs}" + } + s"parameter \\$name $lit" + case DoubleParam(name, value) => s"parameter \\$name $value" + case StringParam(name, value) => s"parameter \\$name ${value.verilogEscape}" + case RawStringParam(name, value) => s"parameter \\$name $value" + } + + // turn strings into Seq[String] verilog comments + private def build_comment(desc: String): Seq[Seq[String]] = { + val lines = desc.split("\n").toSeq + lines.tail.map { + case "" => Seq("#") + case nonEmpty => Seq("#", nonEmpty) + } + } + private def build_attribute(attr: String): Seq[Seq[String]] = { + Seq(Seq("attribute \\") ++ Seq(attr)) + } + + private def build_description(d: Seq[Description]): Seq[Seq[String]] = d.flatMap { + case DocString(desc) => build_comment(desc.string) + case Attribute(attr) => build_attribute(attr.string) + } + + // Turn ports into Seq[String] and add to portdefs + private def build_ports(): Unit = { + def padToMax(strs: Seq[String]): Seq[String] = { + val len = if (strs.nonEmpty) strs.map(_.length).max else 0 + strs.map(_.padTo(len, ' ')) + } + + // Turn directions into strings (and AnalogType into inout) + val dirs = m.ports.map { + case Port(_, _, dir, tpe) => + (dir, tpe) match { + case (_, AnalogType(_)) => "inout " // padded to length of output + case (Input, _) => "input " + case (Output, _) => "output" + } + } + // Turn types into strings, all ports must be GroundTypes + val tpes = m.ports.map { + case Port(_, _, _, tpe: GroundType) => stringify(tpe) + case port: Port => error(s"Trying to emit non-GroundType Port $port") + } + + // dirs are already padded + (dirs, padToMax(tpes), m.ports).zipped.toSeq.zipWithIndex.foreach { + case ((dir, tpe, Port(info, name, _, _)), i) => + portDescriptions.get(name).map { d => + portdefs += Seq("") + portdefs ++= build_description(d) + } + portdefs += Seq("wire ", tpe, " ", dir, " ", i + 1, " \\", name, info) + } + } + + private def infos_to_attr(info: Info): Option[String] = { + def info_extract(info: Info, prev: Seq[String] = Seq()): Seq[String] = info match { + case FileInfo(str) => + val (file, line, col) = FileInfo(str).split + prev :+ (file + ":" + line + "." + col) + case MultiInfo(infos) => + infos.foldLeft(prev)((a, b) => { + info_extract(b, a) + }) + case NoInfo => + prev + } + val srcinfo = info_extract(info) + if (srcinfo.isEmpty) + Option.empty + else + Option("attribute \\src \"" + srcinfo.mkString("|") + "\"") + } + + private def string_to_rtlil_name(name: String): String = { + if (name.head == '_') { + "$" + name + } else { + "\\" + name + } + } + + private def ref_to_name(ref: Reference): String = { + string_to_rtlil_name(ref.name) + } + + private def regUpdate(r: Expression, clk: Expression, reset: Expression, init: Expression) = { + val procName = namespace.newName("$process$" + this.m.name) + val regTempName = "\\" + r.serialize + procName + val loweredReset = SrcInfo(reset) + val loweredClk = SrcInfo(clk) + val loweredInit = SrcInfo(init) + val loweredReg = SrcInfo(r) + def addUpdate(info: Info, expr: Expression, tabs: Seq[String]): Seq[Seq[Any]] = expr match { + case m: Mux => + if (m.tpe == ClockType) throw EmitterException("Cannot emit clock muxes directly") + if (m.tpe == AsyncResetType) throw EmitterException("Cannot emit async reset muxes directly") + + val (eninfo, tinfo, finfo) = MultiInfo.demux(info) + lazy val _if: Seq[Seq[Any]] = + Seq(Seq(tabs, eninfo), Seq(tabs, "switch ", SrcInfo(m.cond, eninfo).str_rep)) ++ ( + if (infos_to_attr(tinfo).nonEmpty) + Seq(Seq(tabs, tab, tinfo), Seq(tabs, tab, "case 1'1")) + else + Seq(Seq(tabs, tab, "case 1'1")) + ) + lazy val _else: Seq[Seq[Any]] = infos_to_attr(finfo) match { + case Some(_) => + Seq(Seq(tabs, tab, finfo), Seq(tabs, tab, "case")) + case None => + Seq(Seq(tabs, tab, "case")) + } + lazy val _ifNot: Seq[Seq[Any]] = + Seq(Seq(tabs, eninfo), Seq(tabs, "switch ", SrcInfo(m.cond, eninfo).str_rep)) ++ ( + if (infos_to_attr(finfo).nonEmpty) + Seq(Seq(tabs, tab, finfo), Seq(tabs, tab, "case 1'0")) + else + Seq(Seq(tabs, tab, "case 1'0")) + ) + lazy val _end = Seq(Seq(tabs, "end")) + lazy val _true = addUpdate(tinfo, m.tval, Seq(tab, tab) ++ tabs) + lazy val _false = addUpdate(finfo, m.fval, Seq(tab, tab) ++ tabs) + /* For a Mux assignment, there are five possibilities, with one subcase for asynchronous reset: + * 1. Both the true and false condition are self-assignments; do nothing + * 2. The true condition is a self-assignment; invert the false condition and use that only + * 3. The false condition is a self-assignment + * a) The reset is asynchronous; emit both 'if' and a trivial 'else' to avoid latches + * b) The reset is synchronous; skip the false condition + * 4. The false condition is a Mux; use the true condition and use 'else if' for the false condition + * 5. Default; use both the true and false conditions + */ + (m.tval, m.fval) match { + case (t, f) if weq(t, r) && weq(f, r) => Nil + case (t, _) if weq(t, r) => _ifNot ++ _false ++ _end + case (_, f) if weq(f, r) => + m.cond.tpe match { + case AsyncResetType => (_if ++ _true ++ _else) ++ _true ++ _end + case _ => _if ++ _true ++ _end + } + case _ => (_if ++ _true ++ _else) ++ _false ++ _end + } + case e => + Seq(Seq(tabs, "assign ", regTempName, " ", SrcInfo(e, info).str_rep)) + } + if (weq(init, r)) { // Synchronous Reset + val InfoExpr(info, e) = netlist(r) + processes += Seq(info) + processes += Seq("wire ", r.tpe, " ", regTempName) + processes += Seq("process ", procName) + processes += Seq("assign ", regTempName, " ", loweredInit.str_rep) + processes ++= addUpdate(info, e, Seq(tab)) + processes += Seq(tab, "sync posedge ", clk) + processes += Seq(tab, tab, "update ", SrcInfo(r).str_rep, " ", regTempName) + processes += Seq("end") + } else { // Asynchronous Reset + assert(reset.tpe == AsyncResetType, "Error! Synchronous reset should have been removed!") + val tv = init + val InfoExpr(finfo, fv) = netlist(r) + processes += Seq(finfo) + processes += Seq("wire ", r.tpe, " ", regTempName) + processes += Seq("process ", procName) + processes += Seq("assign ", regTempName, " ", loweredInit.str_rep) + processes ++= addUpdate(NoInfo, Mux(reset, tv, fv, mux_type_and_widths(tv, fv)), Seq.empty) + processes += Seq("sync posedge ", loweredClk.str_rep) + processes += Seq(tab, "update ", loweredReset.str_rep, " ", regTempName) + processes += Seq("sync posedge ", reset) + processes += Seq(tab, "update ", loweredReg.str_rep, " ", regTempName) + processes += Seq("end") + } + } + + private def bigint_to_str_rep(bigInt: BigInt, width: BigInt): String = { + if (width > 31) { + var bigboi = bigInt + var widthcnt = width + var concatlist: Seq[String] = List() + + while (widthcnt > 32) { + val lowbits = bigboi & 0xffffffff + concatlist = concatlist :+ "%d'%s".format(32, lowbits.toString(2)) + bigboi >>= 32 + widthcnt -= 32 + } + concatlist = concatlist :+ "%d'%s".format(widthcnt, bigboi.toString(2)) + "{ " + concatlist.reverse.mkString(" ") + " }" + } else + "%d'%s".format(width, bigInt.toString(2)) + } + + private case class InstInfo(inst_name: String, mod_name: String, info: Info) { + val conns: mutable.Map[String, String] = mutable.Map() + var params: Seq[String] = Seq() + def getConnection(port: String): Option[String] = { + conns.get(port) + } + def addConnection(port: String, targetValue: String): Unit = { + conns(port) = targetValue + } + } + + private case class SrcInfo(str_rep: String, signed: Boolean, width: BigInt) + private object SrcInfo { + def apply(e: Expression, i: Info = NoInfo): SrcInfo = e match { + case InfoExpr(info, expr) => + SrcInfo(expr, MultiInfo(info, i)) + case x: Reference => + SrcInfo(ref_to_name(x), x.tpe.isInstanceOf[SIntType], get_type_width(x.tpe)) + case x: Literal => + val width = x.width.asInstanceOf[IntWidth].width + SrcInfo(bigint_to_str_rep(x.value, width), x.isInstanceOf[SIntLiteral], width) + case x @ DoPrim(op, args, consts, tpe) => + op match { + case Cat => + SrcInfo( + Seq(" { ", args.map(SrcInfo(_).str_rep).mkString(" "), " }").mkString, + tpe.isInstanceOf[SIntType], + get_type_width(tpe) + ) + case Head => + val src0 = SrcInfo(args.head) + SrcInfo( + Seq(src0.str_rep, " [", (src0.width - 1).toInt, ":", consts.head.toInt, "]").mkString, + tpe.isInstanceOf[SIntType], + get_type_width(tpe) + ) + case Tail => + val src0 = SrcInfo(args.head) + SrcInfo( + Seq(src0.str_rep, " [", (src0.width - 1 - consts.head).toInt, ":0]").mkString, + tpe.isInstanceOf[SIntType], + get_type_width(tpe) + ) + case Pad => + val src0 = SrcInfo(args.head) + if (src0.width >= consts.head) + SrcInfo( + Seq(src0.str_rep, " [", (consts.head - 1).toInt, ":0]").mkString, + tpe.isInstanceOf[SIntType], + get_type_width(tpe) + ) + else if (src0.signed) + SrcInfo( + Seq( + " { ", + s"${src0.str_rep} [${src0.width - 1}] " * (consts.head - src0.width).toInt, + src0.str_rep, + " }" + ).mkString, + tpe.isInstanceOf[SIntType], + get_type_width(tpe) + ) + else + SrcInfo( + Seq(" { ", (consts.head - src0.width).toInt, "'0 ", src0.str_rep, " }").mkString, + tpe.isInstanceOf[SIntType], + get_type_width(tpe) + ) + case _ => + val tempNetName = namespace.newName("$_PRIM_EX") + if (infos_to_attr(i).nonEmpty) declares += Seq(i) + declares += Seq("wire ", x.tpe, " ", tempNetName) + assigns ++= output_expr(tempNetName, x, i) + SrcInfo(tempNetName, x.tpe.isInstanceOf[SIntType], get_type_width(x.tpe)) + } + case x @ SubField(Reference(modname, _, InstanceKind, _), portname, _, _) => + val currentPortConn = instdeclares(modname).getConnection(portname) + if (currentPortConn.isEmpty) { + val tempNetName = "\\" + LowerTypes.loweredName(x) + if (infos_to_attr(i).nonEmpty) declares += Seq(i) + declares += Seq("wire ", x.tpe, " ", tempNetName) + instdeclares(modname).addConnection(portname, tempNetName) + SrcInfo(tempNetName, x.tpe.isInstanceOf[SIntType], get_type_width(x.tpe)) + } else { + SrcInfo(currentPortConn.get, x.tpe.isInstanceOf[SIntType], get_type_width(x.tpe)) + } + case x: SubField => + SrcInfo("\\" + LowerTypes.loweredName(x), x.tpe.isInstanceOf[SIntType], get_type_width(x.tpe)) + case x: Mux => + val tempNetName = namespace.newName("$_MUX_EX") + if (infos_to_attr(i).nonEmpty) declares += Seq(i) + declares += Seq("wire ", x.tpe, " ", tempNetName) + assigns ++= output_expr(tempNetName, e, i) + SrcInfo(tempNetName, x.tpe.isInstanceOf[SIntType], get_type_width(x.tpe)) + case x => + throw EmitterException(s"Internal error! unhandled value $x passed to SrcInfo()") + } + } + + private def emit_streams(): Unit = { + build_description(description).foreach(emit(_)) + emit(Seq("# Generated by firrtl.RtlilEmitter (FIRRTL Version ", BuildInfo.version + ")")) + emit(Seq("autoidx 1")) + emit(Seq("attribute \\cells_not_processed 1")) + emit(Seq("module \\", m.name, m.info)) + for (x <- portdefs) emit(Seq(tab, x)) + for (x <- declares) emit(Seq(tab, x)) + for ((_, x) <- instdeclares) { + emit(Seq(tab, "attribute \\module_not_derived 1")) + emit(Seq(tab, x.info)) + emit(Seq(tab, "cell \\", x.mod_name, " \\", x.inst_name)) + for (p <- x.params) emit(Seq(tab, tab, p)) + for ((a, b) <- x.conns) emit(Seq(tab, tab, "connect \\", a, " ", b)) + emit(Seq(tab, "end")) + } + for (x <- assigns) emit(Seq(tab, x)) + for (x <- processes) emit(Seq(tab, x)) + for (x <- attachSynAssigns) emit(Seq(tab, x)) + for (x <- initials) emit(Seq(tab, x)) + emit(Seq("end")) + emit(Seq()) + } + + private def primop_to_cell(p: PrimOp): String = p match { + case Not => "$not" + case Neg => "$neg" + case Andr => "$reduce_and" + case Orr => "$reduce_or" + case Xorr => "$reduce_xor" + case And => "$and" + case Or => "$or" + case Xor => "$xor" + case Shl => "$shl" + case Dshl => "$shl" + case Eq => "$eq" + case Lt => "$lt" + case Leq => "$le" + case Neq => "$ne" + case Geq => "$ge" + case Gt => "$gt" + case Add => "$add" + case Addw => "$add" + case Sub => "$sub" + case Subw => "$sub" + case Mul => "$mul" + case Div => "$div" + case Rem => "$rem" + case _ => + throwInternalError( + "Internal Error! primop %s shouldn't have propagated this far!".format(p.serialize) + ); + } + + private def unary_cells = List("$not", "$neg", "$reduce_and", "$reduce_or", "$reduce_xor") + private def get_type_width(e: Type): BigInt = { // just trust me bro, its lofirrtl + e.asInstanceOf[GroundType].width.asInstanceOf[IntWidth].width + } + + private def emit_cell( + i: Info, + name: String, + params: Seq[(String, String)], + connections: Seq[(String, String)] + ): Seq[Seq[Any]] = { + Seq(Seq(i), Seq("cell ", name, " ", namespace.newName(name + "$" + m.name))) ++ + params.map { p => Seq(tab, "parameter \\", p._1, " ", p._2) } ++ + connections.map { c => Seq(tab, "connect \\", c._1, " ", c._2) } ++ + Seq(Seq("end")) + } + + private def emit_unary_cell(cell: String, src: SrcInfo, target: String, tgt_width: BigInt): Seq[Seq[Any]] = { + emit_cell( + NoInfo, + cell, + Seq( + ( + "A_SIGNED", + if (src.signed) { "1" } + else { "0" } + ), + ("A_WIDTH", src.width.toString), + ("Y_WIDTH", tgt_width.toString) + ), + Seq(("A", src.str_rep), ("Y", target)) + ) + } + + private def emit_binary_cell( + cell: String, + src_a: SrcInfo, + src_b: SrcInfo, + target: String, + tgt_width: BigInt + ): Seq[Seq[Any]] = { + emit_cell( + NoInfo, + cell, + Seq( + ( + "A_SIGNED", + if (src_a.signed) "1" else "0" + ), + ("A_WIDTH", src_a.width.toString), + ( + "B_SIGNED", + if (src_b.signed) "1" else "0" + ), + ("B_WIDTH", src_b.width.toString), + ("Y_WIDTH", tgt_width.toString) + ), + Seq(("A", src_a.str_rep), ("B", src_b.str_rep), ("Y", target)) + ) + } + + @tailrec + private def output_expr(n: String, d: Expression, i: Info): Seq[Seq[Any]] = d match { + case UIntLiteral(_, _) | SIntLiteral(_, _) | Reference(_, _, _, _) | SubField(_, _, _, _) => + Seq(Seq("connect ", n, " ", SrcInfo(d, i).str_rep)) + case InfoExpr(info, expr) => + output_expr(n, expr, MultiInfo(Seq(i, info))) + case Mux(cond, tval, fval, tpe) => + val (eninfo, tinfo, finfo) = MultiInfo.demux(i) + val csrc = SrcInfo(cond, eninfo) + val tsrc = SrcInfo(tval, tinfo) + val fsrc = SrcInfo(fval, finfo) + emit_cell( + i, + "$mux", + Seq(("WIDTH", get_type_width(tpe).toString)), + Seq(("A", fsrc.str_rep), ("B", tsrc.str_rep), ("S", csrc.str_rep), ("Y", n)) + ) + case DoPrim(op, args, consts, _) => + val sources = args.map(SrcInfo(_, i)) + val src0 = sources.head + if (sources.map(_.width).contains(-1)) return Seq() + op match { + case AsSInt | AsUInt | AsClock | AsAsyncReset => + Seq(Seq("connect ", n, " ", src0)) + case Cvt => + if (src0.signed) + Seq(Seq("connect ", n, " ", src0)) + else + Seq(Seq("connect ", n, " { 1'0 ", src0, " }")) + case Bits => + if (consts.head == consts.last) + Seq(Seq("connect ", n, " ", src0, " [", consts.head.toInt, "]")) + else + Seq(Seq("connect ", n, " ", src0, " [", consts.head.toInt, ":", consts.last.toInt, "]")) + case Shr | Shl => + val prim = if (op == Shr) (if (src0.signed) "$sshr" else "$shr") else "$shl" + emit_binary_cell( + prim, + src0, + SrcInfo(bigint_to_str_rep(consts.head, consts.head.bitLength), signed = false, consts.head.bitLength), + n, + get_type_width(d.tpe) + ) + case Add => + if (src0.signed && sources(1).signed) { + val src0_ext = SrcInfo(s"{ ${src0.str_rep} [${src0.width - 1}] ${src0.str_rep} }", true, src0.width + 1) + val src1_ext = SrcInfo( + s"{ ${sources(1).str_rep} [${sources(1).width - 1}] ${sources(1).str_rep} }", + true, + sources(1).width + 1 + ) + emit_binary_cell("$add", src0_ext, src1_ext, n, get_type_width(d.tpe)) + } else { + emit_binary_cell("$add", src0, sources(1), n, get_type_width(d.tpe)) + } + case Dshr | Dshl => + val prim = if (op == Dshr) (if (src0.signed) "$sshr" else "$shr") else "$shl" + emit_binary_cell(prim, src0, sources(1), n, get_type_width(d.tpe)) + case Cat => + Seq(Seq("connect ", n, " { ", sources.map(_.str_rep).mkString(" "), " }")) + case Head => + Seq(Seq("connect ", n, " ", src0, " [", (src0.width - 1).toInt, ":", consts.head.toInt, "]")) + case Tail => + Seq(Seq("connect ", n, " ", src0, " [", (src0.width - 1 - consts.head).toInt, ":0]")) + case Pad => + if (src0.width >= consts.head) + Seq(Seq("connect ", n, " ", src0, " [", (consts.head - 1).toInt, ":0]")) + else if (src0.signed) + Seq( + Seq("connect ", n) ++ + Seq( + " { ", + s"${src0.str_rep} [${src0.width - 1}] " * (consts.head - src0.width).toInt, + src0.str_rep, + " }" + ) + ) + else + Seq(Seq("connect ", n, " { ", (consts.head - src0.width).toInt, "'0 ", src0, " }")) + case _ => + val cell = primop_to_cell(op) + if (unary_cells.contains(cell)) + Seq(i) +: emit_unary_cell(cell, src0, n, get_type_width(d.tpe)) + else + Seq(i) +: emit_binary_cell(cell, src0, sources(1), n, get_type_width(d.tpe)) + } + case unk => + throw EmitterException(s"Internal error! unhandled output expression $unk passed to output_expr()") + } + + private def build_streams(s: Statement): Unit = { + val withoutDescription = s match { + case DescribedStmt(d, stmt) => + stmt match { + case _: IsDeclaration => + declares ++= build_description(d) + case _ => + } + stmt + case stmt => stmt + } + withoutDescription.foreach(build_streams) + withoutDescription match { + case DefInstance(info, name, mdle, _) => + val (module, params) = moduleMap(mdle) match { + case DescribedMod(_, _, ExtModule(_, _, _, extname, params)) => (extname, params) + case DescribedMod(_, _, Module(_, name, _, _)) => (name, Seq.empty) + case ExtModule(_, _, _, extname, params) => (extname, params) + case Module(_, name, _, _) => (name, Seq.empty) + } + instdeclares(name) = InstInfo(name, module, info) + instdeclares(name).params = if (params.nonEmpty) params.map(stringify) else Seq() + case WDefInstanceConnector(info, name, mdle, _, portCons) => + val (_, params) = moduleMap(mdle) match { + case DescribedMod(_, _, ExtModule(_, _, _, extname, params)) => (extname, params) + case DescribedMod(_, _, Module(_, name, _, _)) => (name, Seq.empty) + case ExtModule(_, _, _, extname, params) => (extname, params) + case Module(_, name, _, _) => (name, Seq.empty) + } + instdeclares(name) = InstInfo(name, mdle, info) + instdeclares(name).params = if (params.nonEmpty) params.map(stringify) else Seq() + for ((port, ref) <- portCons) { + val portName = SrcInfo(remove_root(port)).str_rep.tail + if (instdeclares(name).getConnection(portName).nonEmpty) { + assigns ++= output_expr(instdeclares(name).getConnection(portName).get, ref, NoInfo) + } else { + instdeclares(name).addConnection(SrcInfo(remove_root(port)).str_rep.tail, SrcInfo(ref).str_rep) + } + } + case Connect(info, loc @ WRef(_, _, PortKind | WireKind | InstanceKind, _), expr) => + assigns ++= output_expr(ref_to_name(loc), expr, info) + case Connect(info, SubField(Reference(modname, _, InstanceKind, _), portname, _, _), expr) => + if (instdeclares(modname).getConnection(portname).nonEmpty) { + assigns ++= output_expr(instdeclares(modname).getConnection(portname).get, expr, NoInfo) + } else { + instdeclares(modname).addConnection(portname, SrcInfo(expr, info).str_rep) + } + case sx: DefWire => + declares += Seq(sx.info) + declares += Seq("wire ", sx.tpe, " ", string_to_rtlil_name(sx.name)) + case sx: DefRegister => + val options = emissionOptions.getRegisterEmissionOption(moduleTarget.ref(sx.name)) + val e = WRef(sx.name, sx.tpe, ExpKind, UnknownFlow) + declares += Seq(sx.info) + declares += Seq("wire ", sx.tpe, " ", string_to_rtlil_name(sx.name)) + if (options.useInitAsPreset) + regUpdate(e, sx.clock, sx.reset, e) + else + regUpdate(e, sx.clock, sx.reset, sx.init) + case sx: DefNode => + declares += Seq(sx.info) + declares += Seq("wire ", sx.value.tpe, " ", string_to_rtlil_name(sx.name)) + assigns ++= output_expr(string_to_rtlil_name(sx.name), sx.value, sx.info) + case x @ Verification(value, info, _, pred, en, _) => + value match { + case Formal.Assert => + formals += emit_cell( + info, + "$assert", + Seq(), + Seq(("A", SrcInfo(pred).str_rep), ("EN", SrcInfo(en).str_rep)) + ) + case Formal.Assume => + formals += emit_cell( + info, + "$assume", + Seq(), + Seq(("A", SrcInfo(pred).str_rep), ("EN", SrcInfo(en).str_rep)) + ) + case Formal.Cover => + formals += emit_cell( + info, + "$cover", + Seq(), + Seq(("A", SrcInfo(pred).str_rep), ("EN", SrcInfo(en).str_rep)) + ) + } + case x @ DefMemory(i, name, tpe, depth, wlat, rlat, rd, wr, rdwr, runderw) => + val options = emissionOptions.getMemoryEmissionOption(moduleTarget.ref(name)) + val hasComplexRW = rdwr.nonEmpty && (rlat != 1) + if (rlat > 1 || wlat != 1 || hasComplexRW) + throw EmitterException( + Seq( + s"Memory $name is too complex to emit directly.", + "Consider running VerilogMemDelays to simplify complex memories.", + "Alternatively, add the --repl-seq-mem flag to replace memories with blackboxes." + ).mkString(" ") + ) + val dataWidth = bitWidth(tpe) + val maxDataValue = (BigInt(1) << dataWidth.toInt) - 1 + + def checkValueRange(value: BigInt, at: String): Unit = { + if (value > maxDataValue) + throw EmitterException( + s"Memory $at cannot be initialized with value: $value. Too large (> $maxDataValue)!" + ) + } + declares += Seq("memory width ", dataWidth.toString, " size ", depth.toString, " \\", name) + options.initValue match { + case MemoryArrayInit(values) => + values.zipWithIndex.foreach { + case (value, addr) => + checkValueRange(value, s"$name[$addr]") + initials ++= emit_cell( + i, + "$meminit_v2", + Seq( + ("MEMID", "\"\\\\" + name + "\""), + ("ABITS", "32"), + ("WIDTH", dataWidth.toString), + ("WORDS", "1"), + ("PRIORITY", addr.toString) + ), + Seq( + ("ADDR", addr.toString), + ("DATA", bigint_to_str_rep(value, dataWidth)), + ("EN", bigint_to_str_rep(BigInt(2).pow(dataWidth.toInt) - BigInt(1), dataWidth)) + ) + ) + } + + case MemoryScalarInit(value) => + for (addr <- 0 until depth.intValue) { + initials ++= emit_cell( + i, + "$meminit_v2", + Seq( + ("MEMID", "\"\\\\" + name + "\""), + ("ABITS", "32"), + ("WIDTH", dataWidth.toString), + ("WORDS", "1"), + ("PRIORITY", addr.toString) + ), + Seq( + ("ADDR", addr.toString), + ("DATA", bigint_to_str_rep(value, dataWidth)), + ("EN", bigint_to_str_rep(BigInt(2).pow(dataWidth.toInt) - BigInt(1), dataWidth)) + ) + ) + } + case MemoryRandomInit => + println(s"Memory $name cannot be initialized with random data, RTLIL cannot express this.") + println("Leaving memory uninitialized.") + case MemoryFileInlineInit(_, _) => + throw EmitterException(s"Memory $name cannot be initialized from a file, RTLIL cannot express this.") + } + for (r <- rd) { + val data = memPortField(x, r, "data") + val addr = memPortField(x, r, "addr") + val en = memPortField(x, r, "en") + val hasClk = if (rlat == 1) { "1'1" } + else { "1'0" } + val clkSrc = netlist(memPortField(x, r, "clk")).expr + val transparent = runderw match { + case ReadUnderWrite.New => "1'1" + case ReadUnderWrite.Old => "1'0" + case ReadUnderWrite.Undefined => "1'x" + } + declares += Seq("wire ", data.tpe, " ", SrcInfo(data).str_rep) + assigns ++= emit_cell( + i, + "$memrd", + Seq( + ("ABITS", get_type_width(addr.tpe).toString), + ("MEMID", "\"\\\\" + name + "\""), + ("WIDTH", get_type_width(data.tpe).toString), + ("CLK_ENABLE", hasClk), + ("CLK_POLARITY", "1'1"), + ("TRANSPARENT", transparent) + ), + Seq( + ("CLK", SrcInfo(clkSrc, i).str_rep), + ("EN", if (rlat == 1) SrcInfo(netlist(en), i).str_rep else "1'1"), + ("ADDR", SrcInfo(netlist(addr), i).str_rep), + ("DATA", SrcInfo(data, i).str_rep) + ) + ) + } + for (w <- wr) { + val data = memPortField(x, w, "data") + val addr = memPortField(x, w, "addr") + val en = memPortField(x, w, "en") + val mask = memPortField(x, w, "mask") + val enSrc = SrcInfo(netlist(en)) + val maskSrc = SrcInfo(netlist(mask)) + if (maskSrc.width > 1) { + throw EmitterException("Compound type memory write ports arent fully supported yet.") + } + var memwr_enmask = enSrc.str_rep + if (bitWidth(data.tpe) != 1) { + memwr_enmask = namespace.newName("$memwr_enmask$" + m.name) + declares += Seq("wire signed width ", bitWidth(data.tpe).toInt, " ", memwr_enmask) + assigns ++= emit_cell( + i, + "$and", + Seq( + ("A_SIGNED", "1"), + ("B_SIGNED", "1"), + ("A_WIDTH", bitWidth(en.tpe).toString()), + ("B_WIDTH", maskSrc.width.toString()), + ("Y_WIDTH", bitWidth(data.tpe).toString()) + ), + Seq(("A", enSrc.str_rep), ("B", maskSrc.str_rep), ("Y", memwr_enmask)) + ) + } + val hasClk = if (wlat == 1) { "1'1" } + else { "1'0" } + val clkSrc = netlist(memPortField(x, w, "clk")).expr + assigns ++= emit_cell( + i, + "$memwr", + Seq( + ("ABITS", get_type_width(addr.tpe).toString), + ("MEMID", "\"\\\\" + name + "\""), + ("WIDTH", get_type_width(data.tpe).toString), + ("CLK_ENABLE", hasClk), + ("CLK_POLARITY", "1'1"), + ("PRIORITY", "32'1") + ), + Seq( + ("CLK", SrcInfo(clkSrc).str_rep), + ("EN", memwr_enmask), + ("ADDR", SrcInfo(netlist(addr)).str_rep), + ("DATA", SrcInfo(netlist(data)).str_rep) + ) + ) + } + case sx: Attach => + for (set <- sx.exprs.toSet.subsets(2)) { + val (a, b) = set.toSeq match { + case Seq(x, y) => (x, y) + } + attachSynAssigns += Seq("connect ", SrcInfo(a, sx.info).str_rep, " ", SrcInfo(b, sx.info).str_rep) + } + case _ => + } + } + + def emit_rtlil(): DefModule = { + build_netlist(m.body) + build_ports() + build_streams(m.body) + emit_streams() + m + } + } +} + +private[firrtl] class EmissionOptionMap[V <: EmissionOption](val df: V) { + private val m = collection.mutable.HashMap[ReferenceTarget, V]().withDefaultValue(df) + def +=(elem: (ReferenceTarget, V)): EmissionOptionMap.this.type = { + if (m.contains(elem._1)) + throw EmitterException(s"Multiple EmissionOption for the target ${elem._1} (${m(elem._1)} ; ${elem._2})") + m += elem + this + } + def apply(key: ReferenceTarget): V = m.apply(key) +} + +private[firrtl] class EmissionOptions(annotations: AnnotationSeq) { + // Private so that we can present an immutable API + private val memoryEmissionOption = new EmissionOptionMap[MemoryEmissionOption](MemoryEmissionOptionDefault) + private val registerEmissionOption = new EmissionOptionMap[RegisterEmissionOption](RegisterEmissionOptionDefault) + private val wireEmissionOption = new EmissionOptionMap[WireEmissionOption](WireEmissionOptionDefault) + private val portEmissionOption = new EmissionOptionMap[PortEmissionOption](PortEmissionOptionDefault) + private val nodeEmissionOption = new EmissionOptionMap[NodeEmissionOption](NodeEmissionOptionDefault) + private val connectEmissionOption = new EmissionOptionMap[ConnectEmissionOption](ConnectEmissionOptionDefault) + + def getMemoryEmissionOption(target: ReferenceTarget): MemoryEmissionOption = + memoryEmissionOption(target) + + def getRegisterEmissionOption(target: ReferenceTarget): RegisterEmissionOption = + registerEmissionOption(target) + + def getWireEmissionOption(target: ReferenceTarget): WireEmissionOption = + wireEmissionOption(target) + + def getPortEmissionOption(target: ReferenceTarget): PortEmissionOption = + portEmissionOption(target) + + def getNodeEmissionOption(target: ReferenceTarget): NodeEmissionOption = + nodeEmissionOption(target) + + def getConnectEmissionOption(target: ReferenceTarget): ConnectEmissionOption = + connectEmissionOption(target) + + def emitMemoryInitAsNoSynth: Boolean = { + val annos = annotations.collect { case a @ (MemoryNoSynthInit | MemorySynthInit) => a } + annos match { + case Seq() => true + case Seq(MemoryNoSynthInit) => true + case Seq(MemorySynthInit) => false + case _ => + throw new FirrtlUserException( + "There should only be at most one memory initialization option annotation, got $other" + ) + } + } + + private val emissionAnnos = annotations.collect { + case m: SingleTargetAnnotation[ReferenceTarget] @unchecked with EmissionOption => m + } + + annotations.foreach { + case a: Annotation if a.dedup.nonEmpty => + val (_, _, target) = a.dedup.get + if (!target.isLocal) { + throw new FirrtlUserException( + "At least one dedupable annotation did not deduplicate: got non-local annotation $a from [[DedupAnnotationsTransform]]" + ) + } + case _ => + } + + // using multiple foreach instead of a single partial function as an Annotation can gather multiple EmissionOptions for simplicity + emissionAnnos.foreach { + case a: MemoryEmissionOption => memoryEmissionOption += ((a.target, a)) + case _ => + } + emissionAnnos.foreach { + case a: RegisterEmissionOption => registerEmissionOption += ((a.target, a)) + case _ => + } + emissionAnnos.foreach { + case a: WireEmissionOption => wireEmissionOption += ((a.target, a)) + case _ => + } + emissionAnnos.foreach { + case a: PortEmissionOption => portEmissionOption += ((a.target, a)) + case _ => + } + emissionAnnos.foreach { + case a: NodeEmissionOption => nodeEmissionOption += ((a.target, a)) + case _ => + } + emissionAnnos.foreach { + case a: ConnectEmissionOption => connectEmissionOption += ((a.target, a)) + case _ => + } +} diff --git a/src/main/scala/firrtl/stage/Forms.scala b/src/main/scala/firrtl/stage/Forms.scala index 4348ec32..44ad25cd 100644 --- a/src/main/scala/firrtl/stage/Forms.scala +++ b/src/main/scala/firrtl/stage/Forms.scala @@ -3,6 +3,7 @@ package firrtl.stage import firrtl._ +import firrtl.backends.experimental.rtlil.RtlilEmitter import firrtl.backends.experimental.smt.{Btor2Emitter, SMTLibEmitter} import firrtl.options.Dependency import firrtl.stage.TransformManager.TransformDependency @@ -136,7 +137,8 @@ object Forms { Dependency[MinimumVerilogEmitter], Dependency[SystemVerilogEmitter], Dependency(SMTLibEmitter), - Dependency(Btor2Emitter) + Dependency(Btor2Emitter), + Dependency[RtlilEmitter] ) val LowEmitters = Dependency[LowFirrtlEmitter] +: BackendEmitters diff --git a/src/main/scala/firrtl/transforms/CombineCats.scala b/src/main/scala/firrtl/transforms/CombineCats.scala index 71ef34bf..f89d03ce 100644 --- a/src/main/scala/firrtl/transforms/CombineCats.scala +++ b/src/main/scala/firrtl/transforms/CombineCats.scala @@ -63,11 +63,11 @@ class CombineCats extends Transform with DependencyAPIMigration { override def prerequisites = firrtl.stage.Forms.LowForm ++ Seq( Dependency(passes.RemoveValidIf), - Dependency(firrtl.passes.memlib.VerilogMemDelays), Dependency(firrtl.passes.SplitExpressions) ) - override def optionalPrerequisites = Seq(Dependency[firrtl.transforms.ConstantPropagation]) + override def optionalPrerequisites = + Seq(Dependency(firrtl.passes.memlib.VerilogMemDelays), Dependency[firrtl.transforms.ConstantPropagation]) override def optionalPrerequisiteOf = Seq(Dependency[SystemVerilogEmitter], Dependency[VerilogEmitter]) |
