From 8a4893dc6d9ce994ebbecfefe049e9f5cb8bd5b1 Mon Sep 17 00:00:00 2001 From: albertchen-sifive Date: Thu, 30 Aug 2018 13:51:44 -0700 Subject: Emit Verilog Comments (#874) add description nodes, transform; modify VerilogEmitter to emit comments--- src/main/scala/firrtl/AddDescriptionNodes.scala | 119 +++++++ src/main/scala/firrtl/Compiler.scala | 2 +- src/main/scala/firrtl/Emitter.scala | 343 +++++++++++++-------- .../scala/firrtlTests/VerilogEmitterTests.scala | 160 ++++++++++ 4 files changed, 498 insertions(+), 126 deletions(-) create mode 100644 src/main/scala/firrtl/AddDescriptionNodes.scala diff --git a/src/main/scala/firrtl/AddDescriptionNodes.scala b/src/main/scala/firrtl/AddDescriptionNodes.scala new file mode 100644 index 00000000..6bae6857 --- /dev/null +++ b/src/main/scala/firrtl/AddDescriptionNodes.scala @@ -0,0 +1,119 @@ +// See LICENSE for license details. + +package firrtl + +import firrtl.ir._ +import firrtl.annotations._ +import firrtl.Mappers._ + +case class DescriptionAnnotation(named: Named, description: String) extends Annotation { + def update(renames: RenameMap): Seq[DescriptionAnnotation] = { + renames.get(named) match { + case None => Seq(this) + case Some(seq) => seq.map(n => this.copy(named = n)) + } + } +} + +private sealed trait HasDescription { + def description: Description +} + +private abstract class Description extends FirrtlNode + +private case class DocString(string: StringLit) extends Description { + def serialize: String = "@[" + string.serialize + "]" +} + +private case object EmptyDescription extends Description { + def serialize: String = "" +} + +private case class DescribedStmt(description: Description, stmt: Statement) extends Statement with HasDescription { + def serialize: String = s"${description.serialize}\n${stmt.serialize}" + def mapStmt(f: Statement => Statement): Statement = f(stmt) + def mapExpr(f: Expression => Expression): Statement = this.copy(stmt = stmt.mapExpr(f)) + def mapType(f: Type => Type): Statement = this.copy(stmt = stmt.mapType(f)) + def mapString(f: String => String): Statement = this.copy(stmt = stmt.mapString(f)) + def mapInfo(f: Info => Info): Statement = this.copy(stmt = stmt.mapInfo(f)) +} + +private case class DescribedMod(description: Description, + portDescriptions: Map[String, Description], + mod: DefModule) extends DefModule with HasDescription { + val info = mod.info + val name = mod.name + val ports = mod.ports + def serialize: String = s"${description.serialize}\n${mod.serialize}" + def mapStmt(f: Statement => Statement): DefModule = this.copy(mod = mod.mapStmt(f)) + def mapPort(f: Port => Port): DefModule = this.copy(mod = mod.mapPort(f)) + def mapString(f: String => String): DefModule = this.copy(mod = mod.mapString(f)) + def mapInfo(f: Info => Info): DefModule = this.copy(mod = mod.mapInfo(f)) +} + +/** Wraps modules or statements with their respective described nodes. + * Descriptions come from [[DescriptionAnnotation]]. Describing a + * module or any of its ports will turn it into a [[DescribedMod]]. + * Describing a Statement will turn it into a [[DescribedStmt]] + * + * @note should only be used by VerilogEmitter, described nodes will + * break other transforms. + */ +class AddDescriptionNodes extends Transform { + def inputForm = LowForm + def outputForm = LowForm + + def onStmt(compMap: Map[String, Seq[String]])(stmt: Statement): Statement = { + stmt.map(onStmt(compMap)) match { + case d: IsDeclaration if compMap.contains(d.name) => + DescribedStmt(DocString(StringLit.unescape(compMap(d.name).mkString("\n\n"))), d) + case other => other + } + } + + def onModule(modMap: Map[String, Seq[String]], compMaps: Map[String, Map[String, Seq[String]]]) + (mod: DefModule): DefModule = { + val (newMod, portDesc: Map[String, Description]) = compMaps.get(mod.name) match { + case None => (mod, Map.empty) + case Some(compMap) => (mod.mapStmt(onStmt(compMap)), mod.ports.collect { + case p @ Port(_, name, _, _) if compMap.contains(name) => + name -> DocString(StringLit.unescape(compMap(name).mkString("\n\n"))) + }.toMap) + } + + val modDesc = modMap.get(newMod.name).map { + desc => DocString(StringLit.unescape(desc.mkString("\n\n"))) + } + + if (portDesc.nonEmpty || modDesc.nonEmpty) { + DescribedMod(modDesc.getOrElse(EmptyDescription), portDesc, newMod) + } else { + newMod + } + } + + def collectMaps(annos: Seq[Annotation]): (Map[String, Seq[String]], Map[String, Map[String, Seq[String]]]) = { + val modMap = annos.collect { + case DescriptionAnnotation(ModuleName(m, CircuitName(c)), desc) => (m, desc) + }.groupBy(_._1).mapValues(_.map(_._2)) + + val compMap = annos.collect { + case DescriptionAnnotation(ComponentName(comp, ModuleName(mod, CircuitName(circ))), desc) => + (mod, comp, desc) + }.groupBy(_._1).mapValues(_.groupBy(_._2).mapValues(_.map(_._3))) + + (modMap, compMap) + } + + def executeModule(module: DefModule, annos: Seq[Annotation]): DefModule = { + val (modMap, compMap) = collectMaps(annos) + + onModule(modMap, compMap)(module) + } + + override def execute(state: CircuitState): CircuitState = { + val (modMap, compMap) = collectMaps(state.annotations) + + state.copy(circuit = state.circuit.mapModule(onModule(modMap, compMap))) + } +} diff --git a/src/main/scala/firrtl/Compiler.scala b/src/main/scala/firrtl/Compiler.scala index b34782b5..9044c5a8 100644 --- a/src/main/scala/firrtl/Compiler.scala +++ b/src/main/scala/firrtl/Compiler.scala @@ -331,7 +331,7 @@ object CompilerUtils extends LazyLogging { /** Generates a sequence of [[Transform]]s to lower a Firrtl circuit * * @param inputForm [[CircuitForm]] to lower from - * @param outputForm [[CircuitForm to lower to + * @param outputForm [[CircuitForm]] to lower to * @return Sequence of transforms that will lower if outputForm is lower than inputForm */ def getLoweringTransforms(inputForm: CircuitForm, outputForm: CircuitForm): Seq[Transform] = { diff --git a/src/main/scala/firrtl/Emitter.scala b/src/main/scala/firrtl/Emitter.scala index accefffa..0a743321 100644 --- a/src/main/scala/firrtl/Emitter.scala +++ b/src/main/scala/firrtl/Emitter.scala @@ -319,6 +319,7 @@ class VerilogEmitter extends SeqTransform with Emitter { * Gets a reference to a verilog renderer. This is used by the current standard verilog emission process * but allows access to individual portions, in particular, this function can be used to generate * the header for a verilog file without generating anything else. + * * @param m the start module * @param moduleMap a way of finding other modules * @param writer where rendering will be placed @@ -328,15 +329,47 @@ class VerilogEmitter extends SeqTransform with Emitter { new VerilogRender(m, moduleMap)(writer) } + /** + * Gets a reference to a verilog renderer. This is used by the current standard verilog emission process + * but allows access to individual portions, in particular, this function can be used to generate + * the header for a verilog file without generating anything else. + * + * @param descriptions comments to be emitted + * @param m the start module + * @param moduleMap a way of finding other modules + * @param writer where rendering will be placed + * @return the render reference + */ + def getRenderer(descriptions: Seq[DescriptionAnnotation], + m: Module, + moduleMap: Map[String, DefModule])(implicit writer: Writer): VerilogRender = { + val newMod = new AddDescriptionNodes().executeModule(m, descriptions) + + newMod match { + case DescribedMod(d, pds, m: Module) => new VerilogRender(d, pds, m, moduleMap)(writer) + case m: Module => new VerilogRender(m, moduleMap)(writer) + } + } + /** * Used by getRenderer, it has machinery to produce verilog from IR. * Making this a class allows access to particular parts of the verilog emission. * - * @param m the start module - * @param moduleMap a map of modules so submodules can be discovered - * @param writer where rendered information is placed. + * @param description a description of the start module + * @param portDescriptions a map of port name to description + * @param m the start module + * @param moduleMap a map of modules so submodules can be discovered + * @param writer where rendered information is placed. */ - class VerilogRender(m: Module, moduleMap: Map[String, DefModule])(implicit writer: Writer) { + class VerilogRender(description: Description, + portDescriptions: Map[String, Description], + m: Module, + moduleMap: Map[String, DefModule])(implicit writer: Writer) { + + def this(m: Module, moduleMap: Map[String, DefModule])(implicit writer: Writer) { + this(EmptyDescription, Map.empty, m, moduleMap)(writer) + } + val netlist = mutable.LinkedHashMap[WrappedExpression, Expression]() val namespace = Namespace(m) namespace.newName("_RAND") // Start rand names at _RAND_0 @@ -484,6 +517,21 @@ class VerilogEmitter extends SeqTransform with Emitter { Seq("$fwrite(32'h80000002,", strx, ");") } + // turn strings into Seq[String] verilog comments + def build_comment(desc: String): Seq[Seq[String]] = { + val lines = desc.split("\n").toSeq + + if (lines.size > 1) { + val lineSeqs = lines.tail.map { + case "" => Seq(" *") + case nonEmpty => Seq(" * ", nonEmpty) + } + Seq("/* ", lines.head) +: lineSeqs :+ Seq(" */") + } else { + Seq(Seq("// ", lines(0))) + } + } + // Turn ports into Seq[String] and add to portdefs def build_ports(): Unit = { def padToMax(strs: Seq[String]): Seq[String] = { @@ -506,133 +554,163 @@ class VerilogEmitter extends SeqTransform with Emitter { } // dirs are already padded - portdefs ++= (dirs, padToMax(tpes), m.ports).zipped.toSeq.zipWithIndex.map { + (dirs, padToMax(tpes), m.ports).zipped.toSeq.zipWithIndex.foreach { case ((dir, tpe, Port(info, name, _, _)), i) => - if (i != m.ports.size - 1) Seq(dir, " ", tpe, " ", name, ",", info) - else Seq(dir, " ", tpe, " ", name, info) + portDescriptions.get(name) match { + case Some(DocString(s)) => + portdefs += Seq("") + portdefs ++= build_comment(s.string) + case other => + } + + if (i != m.ports.size - 1) { + portdefs += Seq(dir, " ", tpe, " ", name, ",", info) + } else { + portdefs += Seq(dir, " ", tpe, " ", name, info) + } } } - def build_streams(s: Statement): Statement = s map build_streams match { - case sx@Connect(info, loc@WRef(_, _, PortKind | WireKind | InstanceKind, _), expr) => - assign(loc, expr, info) - sx - case sx: DefWire => - declare("wire", sx.name, sx.tpe, sx.info) - sx - case sx: DefRegister => - declare("reg", sx.name, sx.tpe, sx.info) - val e = wref(sx.name, sx.tpe) - regUpdate(e, sx.clock) - initialize(e) - sx - case sx: DefNode => - declare("wire", sx.name, sx.value.tpe, sx.info) - assign(WRef(sx.name, sx.value.tpe, NodeKind, MALE), sx.value, sx.info) - sx - case sx: Stop => - simulate(sx.clk, sx.en, stop(sx.ret), Some("STOP_COND"), sx.info) - sx - case sx: Print => - simulate(sx.clk, sx.en, printf(sx.string, sx.args), Some("PRINTF_COND"), sx.info) - sx - // If we are emitting an Attach, it must not have been removable in VerilogPrep - case sx: Attach => - // For Synthesis - // Note that this is quadratic in the number of things attached - for (set <- sx.exprs.toSet.subsets(2)) { - val (a, b) = set.toSeq match { - case Seq(x, y) => (x, y) + def build_streams(s: Statement): Statement = { + val withoutDescription = s match { + case DescribedStmt(DocString(desc), stmt) => + val comment = Seq("") +: build_comment(desc.string) + stmt match { + case sx: IsDeclaration => + declares ++= comment + case sx => + } + stmt + case DescribedStmt(EmptyDescription, stmt) => stmt + case other => other + } + withoutDescription map build_streams match { + case sx@Connect(info, loc@WRef(_, _, PortKind | WireKind | InstanceKind, _), expr) => + assign(loc, expr, info) + sx + case sx: DefWire => + declare("wire", sx.name, sx.tpe, sx.info) + sx + case sx: DefRegister => + declare("reg", sx.name, sx.tpe, sx.info) + val e = wref(sx.name, sx.tpe) + regUpdate(e, sx.clock) + initialize(e) + sx + case sx: DefNode => + declare("wire", sx.name, sx.value.tpe, sx.info) + assign(WRef(sx.name, sx.value.tpe, NodeKind, MALE), sx.value, sx.info) + sx + case sx: Stop => + simulate(sx.clk, sx.en, stop(sx.ret), Some("STOP_COND"), sx.info) + sx + case sx: Print => + simulate(sx.clk, sx.en, printf(sx.string, sx.args), Some("PRINTF_COND"), sx.info) + sx + // If we are emitting an Attach, it must not have been removable in VerilogPrep + case sx: Attach => + // For Synthesis + // Note that this is quadratic in the number of things attached + for (set <- sx.exprs.toSet.subsets(2)) { + val (a, b) = set.toSeq match { + case Seq(x, y) => (x, y) + } + // Synthesizable ones as well + attachSynAssigns += Seq("assign ", a, " = ", b, ";", sx.info) + attachSynAssigns += Seq("assign ", b, " = ", a, ";", sx.info) + } + // alias implementation for everything else + attachAliases += Seq("alias ", sx.exprs.flatMap(e => Seq(e, " = ")).init, ";", sx.info) + sx + case sx: WDefInstanceConnector => + val (module, params) = moduleMap(sx.module) 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) + } + val ps = if (params.nonEmpty) params map stringify mkString("#(", ", ", ") ") else "" + instdeclares += Seq(module, " ", ps, sx.name, " (", sx.info) + for (((port, ref), i) <- sx.portCons.zipWithIndex) { + val line = Seq(tab, ".", remove_root(port), "(", ref, ")") + if (i != sx.portCons.size - 1) instdeclares += Seq(line, ",") + else instdeclares += line + } + instdeclares += Seq(");") + sx + case sx: DefMemory => + val fullSize = sx.depth * (sx.dataType match { + case GroundType(IntWidth(width)) => width + }) + val decl = if (fullSize > (1 << 29)) "reg /* sparse */" else "reg" + declare(decl, sx.name, VectorType(sx.dataType, sx.depth), sx.info) + initialize_mem(sx) + if (sx.readLatency != 0 || sx.writeLatency != 1) + throw EmitterException("All memories should be transformed into " + + "blackboxes or combinational by previous passses") + for (r <- sx.readers) { + val data = memPortField(sx, r, "data") + val addr = memPortField(sx, r, "addr") + val en = memPortField(sx, r, "en") + // Ports should share an always@posedge, so can't have intermediary wire + val clk = netlist(memPortField(sx, r, "clk")) + + declare("wire", LowerTypes.loweredName(data), data.tpe, sx.info) + declare("wire", LowerTypes.loweredName(addr), addr.tpe, sx.info) + // declare("wire", LowerTypes.loweredName(en), en.tpe) + + //; Read port + assign(addr, netlist(addr), NoInfo) // Info should come from addr connection + // assign(en, netlist(en)) //;Connects value to m.r.en + val mem = WRef(sx.name, memType(sx), MemKind, UNKNOWNGENDER) + val memPort = WSubAccess(mem, addr, sx.dataType, UNKNOWNGENDER) + val depthValue = UIntLiteral(sx.depth, IntWidth(BigInt(sx.depth).bitLength)) + val garbageGuard = DoPrim(Geq, Seq(addr, depthValue), Seq(), UnknownType) + + if ((sx.depth & (sx.depth - 1)) == 0) + assign(data, memPort, sx.info) + else + garbageAssign(data, memPort, garbageGuard, sx.info) } - // Synthesizable ones as well - attachSynAssigns += Seq("assign ", a, " = ", b, ";", sx.info) - attachSynAssigns += Seq("assign ", b, " = ", a, ";", sx.info) - } - // alias implementation for everything else - attachAliases += Seq("alias ", sx.exprs.flatMap(e => Seq(e, " = ")).init, ";", sx.info) - sx - case sx: WDefInstanceConnector => - val (module, params) = moduleMap(sx.module) match { - case ExtModule(_, _, _, extname, params) => (extname, params) - case Module(_, name, _, _) => (name, Seq.empty) - } - val ps = if (params.nonEmpty) params map stringify mkString("#(", ", ", ") ") else "" - instdeclares += Seq(module, " ", ps, sx.name, " (", sx.info) - for (((port, ref), i) <- sx.portCons.zipWithIndex) { - val line = Seq(tab, ".", remove_root(port), "(", ref, ")") - if (i != sx.portCons.size - 1) instdeclares += Seq(line, ",") - else instdeclares += line - } - instdeclares += Seq(");") - sx - case sx: DefMemory => - val fullSize = sx.depth * (sx.dataType match { - case GroundType(IntWidth(width)) => width - }) - val decl = if (fullSize > (1 << 29)) "reg /* sparse */" else "reg" - declare(decl, sx.name, VectorType(sx.dataType, sx.depth), sx.info) - initialize_mem(sx) - if (sx.readLatency != 0 || sx.writeLatency != 1) - throw EmitterException("All memories should be transformed into " + - "blackboxes or combinational by previous passses") - for (r <- sx.readers) { - val data = memPortField(sx, r, "data") - val addr = memPortField(sx, r, "addr") - val en = memPortField(sx, r, "en") - // Ports should share an always@posedge, so can't have intermediary wire - val clk = netlist(memPortField(sx, r, "clk")) - - declare("wire", LowerTypes.loweredName(data), data.tpe, sx.info) - declare("wire", LowerTypes.loweredName(addr), addr.tpe, sx.info) - // declare("wire", LowerTypes.loweredName(en), en.tpe) - - //; Read port - assign(addr, netlist(addr), NoInfo) // Info should come from addr connection - // assign(en, netlist(en)) //;Connects value to m.r.en - val mem = WRef(sx.name, memType(sx), MemKind, UNKNOWNGENDER) - val memPort = WSubAccess(mem, addr, sx.dataType, UNKNOWNGENDER) - val depthValue = UIntLiteral(sx.depth, IntWidth(BigInt(sx.depth).bitLength)) - val garbageGuard = DoPrim(Geq, Seq(addr, depthValue), Seq(), UnknownType) - - if ((sx.depth & (sx.depth - 1)) == 0) - assign(data, memPort, sx.info) - else - garbageAssign(data, memPort, garbageGuard, sx.info) - } - for (w <- sx.writers) { - val data = memPortField(sx, w, "data") - val addr = memPortField(sx, w, "addr") - val mask = memPortField(sx, w, "mask") - val en = memPortField(sx, w, "en") - //Ports should share an always@posedge, so can't have intermediary wire - val clk = netlist(memPortField(sx, w, "clk")) - - declare("wire", LowerTypes.loweredName(data), data.tpe, sx.info) - declare("wire", LowerTypes.loweredName(addr), addr.tpe, sx.info) - declare("wire", LowerTypes.loweredName(mask), mask.tpe, sx.info) - declare("wire", LowerTypes.loweredName(en), en.tpe, sx.info) - - // Write port - // Info should come from netlist - assign(data, netlist(data), NoInfo) - assign(addr, netlist(addr), NoInfo) - assign(mask, netlist(mask), NoInfo) - assign(en, netlist(en), NoInfo) - - val mem = WRef(sx.name, memType(sx), MemKind, UNKNOWNGENDER) - val memPort = WSubAccess(mem, addr, sx.dataType, UNKNOWNGENDER) - update(memPort, data, clk, AND(en, mask), sx.info) - } + for (w <- sx.writers) { + val data = memPortField(sx, w, "data") + val addr = memPortField(sx, w, "addr") + val mask = memPortField(sx, w, "mask") + val en = memPortField(sx, w, "en") + //Ports should share an always@posedge, so can't have intermediary wire + val clk = netlist(memPortField(sx, w, "clk")) + + declare("wire", LowerTypes.loweredName(data), data.tpe, sx.info) + declare("wire", LowerTypes.loweredName(addr), addr.tpe, sx.info) + declare("wire", LowerTypes.loweredName(mask), mask.tpe, sx.info) + declare("wire", LowerTypes.loweredName(en), en.tpe, sx.info) + + // Write port + // Info should come from netlist + assign(data, netlist(data), NoInfo) + assign(addr, netlist(addr), NoInfo) + assign(mask, netlist(mask), NoInfo) + assign(en, netlist(en), NoInfo) + + val mem = WRef(sx.name, memType(sx), MemKind, UNKNOWNGENDER) + val memPort = WSubAccess(mem, addr, sx.dataType, UNKNOWNGENDER) + update(memPort, data, clk, AND(en, mask), sx.info) + } - if (sx.readwriters.nonEmpty) - throw EmitterException("All readwrite ports should be transformed into " + - "read & write ports by previous passes") - sx - case sx => sx + if (sx.readwriters.nonEmpty) + throw EmitterException("All readwrite ports should be transformed into " + + "read & write ports by previous passes") + sx + case sx => sx + } } def emit_streams() { + description match { + case DocString(s) => build_comment(s.string).foreach(emit(_)) + case other => + } emit(Seq("module ", m.name, "(", m.info)) for (x <- portdefs) emit(Seq(tab, x)) emit(Seq(");")) @@ -721,6 +799,12 @@ class VerilogEmitter extends SeqTransform with Emitter { def emitVerilogBind(overrideName: String, body: String): DefModule = { build_netlist(m.body) build_ports() + + description match { + case DocString(s) => build_comment(s.string).foreach(emit(_)) + case other => + } + emit(Seq("module ", overrideName, "(", m.info)) for (x <- portdefs) emit(Seq(tab, x)) @@ -729,7 +813,7 @@ class VerilogEmitter extends SeqTransform with Emitter { emit(Seq("endmodule"), top = 0) m } - } + } /** Preamble for every emitted Verilog file */ def transforms = Seq( @@ -739,16 +823,20 @@ class VerilogEmitter extends SeqTransform with Emitter { new DeadCodeElimination, passes.VerilogModulusCleanup, passes.VerilogRename, - passes.VerilogPrep) + passes.VerilogPrep, + new AddDescriptionNodes) def emit(state: CircuitState, writer: Writer): Unit = { val circuit = runTransforms(state).circuit val moduleMap = circuit.modules.map(m => m.name -> m).toMap circuit.modules.foreach { + case dm @ DescribedMod(d, pds, m: Module) => + val renderer = new VerilogRender(d, pds, m, moduleMap)(writer) + renderer.emit_verilog() case m: Module => val renderer = new VerilogRender(m, moduleMap)(writer) renderer.emit_verilog() - case _: ExtModule => // do nothing + case _ => // do nothing } } @@ -764,12 +852,17 @@ class VerilogEmitter extends SeqTransform with Emitter { val moduleMap = circuit.modules.map(m => m.name -> m).toMap circuit.modules flatMap { + case dm @ DescribedMod(d, pds, module: Module) => + val writer = new java.io.StringWriter + val renderer = new VerilogRender(d, pds, module, moduleMap)(writer) + renderer.emit_verilog() + Some(EmittedVerilogModuleAnnotation(EmittedVerilogModule(module.name, writer.toString))) case module: Module => val writer = new java.io.StringWriter val renderer = new VerilogRender(module, moduleMap)(writer) renderer.emit_verilog() Some(EmittedVerilogModuleAnnotation(EmittedVerilogModule(module.name, writer.toString))) - case _: ExtModule => None + case _ => None } case _ => Seq() } diff --git a/src/test/scala/firrtlTests/VerilogEmitterTests.scala b/src/test/scala/firrtlTests/VerilogEmitterTests.scala index 46f3a711..b5ad2f1a 100644 --- a/src/test/scala/firrtlTests/VerilogEmitterTests.scala +++ b/src/test/scala/firrtlTests/VerilogEmitterTests.scala @@ -233,3 +233,163 @@ class VerilogEmitterSpec extends FirrtlFlatSpec { } } + +class VerilogDescriptionEmitterSpec extends FirrtlFlatSpec { + "Port descriptions" should "emit aligned comments on the line above" in { + val compiler = new VerilogCompiler + val input = + """circuit Test : + | module Test : + | input a : UInt<1> + | input b : UInt<1> + | output c : UInt<1> + | c <= add(a, b) + |""".stripMargin + val check = Seq( + """ /* multi + | * line + | */ + | input a,""".stripMargin, + """ // single line + | input b,""".stripMargin + ) + // We don't use executeTest because we care about the spacing in the result + val modName = ModuleName("Test", CircuitName("Test")) + val annos = Seq( + DescriptionAnnotation(ComponentName("a", modName), "multi\nline"), + DescriptionAnnotation(ComponentName("b", modName), "single line")) + val finalState = compiler.compileAndEmit(CircuitState(parse(input), ChirrtlForm, annos), Seq.empty) + val output = finalState.getEmittedCircuit.value + for (c <- check) { + assert(output.contains(c)) + } + } + + "Declaration descriptions" should "emit aligned comments on the line above" in { + val compiler = new VerilogCompiler + val input = + """circuit Test : + | module Test : + | input clock : Clock + | input a : UInt<1> + | input b : UInt<1> + | output c : UInt<1> + | + | wire d : UInt<1> + | d <= add(a, b) + | + | reg e : UInt<1>, clock + | e <= or(a, b) + | + | node f = and(a, b) + | c <= add(d, add(e, f)) + |""".stripMargin + val check = Seq( + """ /* multi + | * line + | */ + | wire d;""".stripMargin, + """ /* multi + | * line + | */ + | reg e;""".stripMargin, + """ // single line + | wire f;""".stripMargin + ) + // We don't use executeTest because we care about the spacing in the result + val modName = ModuleName("Test", CircuitName("Test")) + val annos = Seq( + DescriptionAnnotation(ComponentName("d", modName), "multi\nline"), + DescriptionAnnotation(ComponentName("e", modName), "multi\nline"), + DescriptionAnnotation(ComponentName("f", modName), "single line")) + val finalState = compiler.compileAndEmit(CircuitState(parse(input), ChirrtlForm, annos), Seq.empty) + val output = finalState.getEmittedCircuit.value + for (c <- check) { + assert(output.contains(c)) + } + } + + "Module descriptions" should "emit aligned comments on the line above" in { + val compiler = new VerilogCompiler + val input = + """circuit Test : + | module Test : + | input clock : Clock + | input a : UInt<1> + | input b : UInt<1> + | output c : UInt<1> + | + | wire d : UInt<1> + | d <= add(a, b) + | + | reg e : UInt<1>, clock + | e <= or(a, b) + | + | node f = and(a, b) + | c <= add(d, add(e, f)) + |""".stripMargin + val check = Seq( + """/* multi + | * line + | */ + |module Test(""".stripMargin + ) + // We don't use executeTest because we care about the spacing in the result + val modName = ModuleName("Test", CircuitName("Test")) + val annos = Seq(DescriptionAnnotation(modName, "multi\nline")) + val finalState = compiler.compileAndEmit(CircuitState(parse(input), ChirrtlForm, annos), Seq.empty) + val output = finalState.getEmittedCircuit.value + for (c <- check) { + assert(output.contains(c)) + } + } + + "Multiple descriptions" should "be combined" in { + val compiler = new VerilogCompiler + val input = + """circuit Test : + | module Test : + | input a : UInt<1> + | input b : UInt<1> + | output c : UInt<1> + | + | wire d : UInt<1> + | d <= add(a, b) + | + | c <= add(a, d) + |""".stripMargin + val check = Seq( + """/* line1 + | * + | * line2 + | */ + |module Test(""".stripMargin, + """ /* line3 + | * + | * line4 + | */ + | input a,""".stripMargin, + """ /* line5 + | * + | * line6 + | */ + | wire d;""".stripMargin + ) + // We don't use executeTest because we care about the spacing in the result + val modName = ModuleName("Test", CircuitName("Test")) + val annos = Seq( + DescriptionAnnotation(modName, "line1"), + DescriptionAnnotation(modName, "line2"), + DescriptionAnnotation(ComponentName("a", modName), "line3"), + DescriptionAnnotation(ComponentName("a", modName), "line4"), + DescriptionAnnotation(ComponentName("d", modName), "line5"), + DescriptionAnnotation(ComponentName("d", modName), "line6") + ) + val writer = new java.io.StringWriter + val finalState = compiler.compileAndEmit(CircuitState(parse(input), ChirrtlForm, annos), Seq.empty) + val output = finalState.getEmittedCircuit.value + for (c <- check) { + assert(output.contains(c)) + } + } +} -- cgit v1.2.3