diff options
| author | Chick Markley | 2018-07-26 11:18:46 -0700 |
|---|---|---|
| committer | GitHub | 2018-07-26 11:18:46 -0700 |
| commit | 1dcf9907eaa2f5fd2bc1e5a7dafeb1ae4b8e1434 (patch) | |
| tree | 40ac6b81e978a74df69b69dc29ea48608d7d2040 | |
| parent | 7dff927840a30893facae957595a8e88ea62509a (diff) | |
Support for load memory annotations in chisel (#833)
* Support for load memory annotations in chisel
This PR
* Delays the BlackBoxSourceHelper transformation to the Emitter stage of the VerilogCompiler
* remove from VerilogCompiler
* move to VerilogEmitter
* Changes the verilog emitter to allow programmatic access to the verilog module declaration
* Creating a bindable module requires headers to match
* Provides a unit test that shows how to generate a bindable module.
* Binding support
Treadle needed LoadMemoryAnnotation to be in firrtl instead of chisel in order to recognize the annotations and use them for memory loading
* Binding support
- Fixed bug that handled suffixes on memory initializing files
* Binding support
- Add a bit more doc to the API provided by the VerilogRenderer
5 files changed, 503 insertions, 312 deletions
diff --git a/src/main/scala/firrtl/Emitter.scala b/src/main/scala/firrtl/Emitter.scala index 6ffa942c..3dcf3a33 100644 --- a/src/main/scala/firrtl/Emitter.scala +++ b/src/main/scala/firrtl/Emitter.scala @@ -315,350 +315,414 @@ class VerilogEmitter extends SeqTransform with Emitter { } } - def emit_verilog(m: Module, moduleMap: Map[String, DefModule])(implicit w: Writer): DefModule = { - val netlist = mutable.LinkedHashMap[WrappedExpression, Expression]() - val namespace = Namespace(m) - namespace.newName("_RAND") // Start rand names at _RAND_0 - def build_netlist(s: Statement): Statement = s map build_netlist match { - case sx: Connect => - netlist(sx.loc) = sx.expr - sx - case sx: IsInvalid => error("Should have removed these!") - case sx: DefNode => - val e = WRef(sx.name, sx.value.tpe, NodeKind, MALE) - netlist(e) = sx.value - sx - case sx => sx - } + /** + * 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 + * @return the render reference + */ + def getRenderer(m: Module, moduleMap: Map[String, DefModule])(implicit writer: Writer): VerilogRender = { + new VerilogRender(m, moduleMap)(writer) + } - val portdefs = ArrayBuffer[Seq[Any]]() - val declares = ArrayBuffer[Seq[Any]]() - val instdeclares = ArrayBuffer[Seq[Any]]() - val assigns = ArrayBuffer[Seq[Any]]() - val attachSynAssigns = ArrayBuffer.empty[Seq[Any]] - val attachAliases = ArrayBuffer.empty[Seq[Any]] - val at_clock = mutable.LinkedHashMap[Expression,ArrayBuffer[Seq[Any]]]() - val initials = ArrayBuffer[Seq[Any]]() - val simulates = ArrayBuffer[Seq[Any]]() - def declare(b: String, n: String, t: Type, info: Info) = t match { - case tx: VectorType => - declares += Seq(b, " ", tx.tpe, " ", n, " [0:", tx.size - 1, "];",info) - case tx => - declares += Seq(b, " ", tx, " ", n,";",info) - } - def assign(e: Expression, value: Expression, info: Info) { - assigns += Seq("assign ", e, " = ", value, ";", info) - } + /** + * 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. + */ + class VerilogRender(m: Module, moduleMap: Map[String, DefModule])(implicit writer: Writer) { + val netlist = mutable.LinkedHashMap[WrappedExpression, Expression]() + val namespace = Namespace(m) + namespace.newName("_RAND") // Start rand names at _RAND_0 + def build_netlist(s: Statement): Statement = s map build_netlist match { + case sx: Connect => + netlist(sx.loc) = sx.expr + sx + case sx: IsInvalid => error("Should have removed these!") + case sx: DefNode => + val e = WRef(sx.name, sx.value.tpe, NodeKind, MALE) + netlist(e) = sx.value + sx + case sx => sx + } - // In simulation, assign garbage under a predicate - def garbageAssign(e: Expression, syn: Expression, garbageCond: Expression, info: Info) = { - assigns += Seq("`ifndef RANDOMIZE_GARBAGE_ASSIGN") - assigns += Seq("assign ", e, " = ", syn, ";", info) - assigns += Seq("`else") - assigns += Seq("assign ", e, " = ", garbageCond, " ? ", rand_string(syn.tpe), " : ", syn, - ";", info) - assigns += Seq("`endif // RANDOMIZE_GARBAGE_ASSIGN") - } - def invalidAssign(e: Expression) = { - assigns += Seq("`ifdef RANDOMIZE_INVALID_ASSIGN") - assigns += Seq("assign ", e, " = ", rand_string(e.tpe), ";") - assigns += Seq("`endif // RANDOMIZE_INVALID_ASSIGN") - } - def regUpdate(r: Expression, clk: Expression) = { - def addUpdate(expr: Expression, tabs: String): Seq[Seq[Any]] = { - if (weq(expr, r)) Nil // Don't bother emitting connection of register to itself - else expr match { - case m: Mux => - if (m.tpe == ClockType) throw EmitterException("Cannot emit clock muxes directly") - def ifStatement = Seq(tabs, "if (", m.cond, ") begin") - val trueCase = addUpdate(m.tval, tabs + tab) - val elseStatement = Seq(tabs, "end else begin") - def ifNotStatement = Seq(tabs, "if (!(", m.cond, ")) begin") - val falseCase = addUpdate(m.fval, tabs + tab) - val endStatement = Seq(tabs, "end") - - ((trueCase.nonEmpty, falseCase.nonEmpty): @ unchecked) match { - case (true, true) => - ifStatement +: trueCase ++: elseStatement +: falseCase :+ endStatement - case (true, false) => - ifStatement +: trueCase :+ endStatement - case (false, true) => - ifNotStatement +: falseCase :+ endStatement - } - case e => Seq(Seq(tabs, r, " <= ", e, ";")) - } + val portdefs = ArrayBuffer[Seq[Any]]() + val declares = ArrayBuffer[Seq[Any]]() + val instdeclares = ArrayBuffer[Seq[Any]]() + val assigns = ArrayBuffer[Seq[Any]]() + val attachSynAssigns = ArrayBuffer.empty[Seq[Any]] + val attachAliases = ArrayBuffer.empty[Seq[Any]] + val at_clock = mutable.LinkedHashMap[Expression, ArrayBuffer[Seq[Any]]]() + val initials = ArrayBuffer[Seq[Any]]() + val simulates = ArrayBuffer[Seq[Any]]() + + def declare(b: String, n: String, t: Type, info: Info) = t match { + case tx: VectorType => + declares += Seq(b, " ", tx.tpe, " ", n, " [0:", tx.size - 1, "];", info) + case tx => + declares += Seq(b, " ", tx, " ", n, ";", info) + } + + def assign(e: Expression, value: Expression, info: Info) { + assigns += Seq("assign ", e, " = ", value, ";", info) + } + + // In simulation, assign garbage under a predicate + def garbageAssign(e: Expression, syn: Expression, garbageCond: Expression, info: Info) = { + assigns += Seq("`ifndef RANDOMIZE_GARBAGE_ASSIGN") + assigns += Seq("assign ", e, " = ", syn, ";", info) + assigns += Seq("`else") + assigns += Seq("assign ", e, " = ", garbageCond, " ? ", rand_string(syn.tpe), " : ", syn, + ";", info) + assigns += Seq("`endif // RANDOMIZE_GARBAGE_ASSIGN") + } + + def invalidAssign(e: Expression) = { + assigns += Seq("`ifdef RANDOMIZE_INVALID_ASSIGN") + assigns += Seq("assign ", e, " = ", rand_string(e.tpe), ";") + assigns += Seq("`endif // RANDOMIZE_INVALID_ASSIGN") + } + + def regUpdate(r: Expression, clk: Expression) = { + def addUpdate(expr: Expression, tabs: String): Seq[Seq[Any]] = { + if (weq(expr, r)) Nil // Don't bother emitting connection of register to itself + else expr match { + case m: Mux => + if (m.tpe == ClockType) throw EmitterException("Cannot emit clock muxes directly") + + def ifStatement = Seq(tabs, "if (", m.cond, ") begin") + + val trueCase = addUpdate(m.tval, tabs + tab) + val elseStatement = Seq(tabs, "end else begin") + + def ifNotStatement = Seq(tabs, "if (!(", m.cond, ")) begin") + + val falseCase = addUpdate(m.fval, tabs + tab) + val endStatement = Seq(tabs, "end") + + ((trueCase.nonEmpty, falseCase.nonEmpty): @unchecked) match { + case (true, true) => + ifStatement +: trueCase ++: elseStatement +: falseCase :+ endStatement + case (true, false) => + ifStatement +: trueCase :+ endStatement + case (false, true) => + ifNotStatement +: falseCase :+ endStatement + } + case e => Seq(Seq(tabs, r, " <= ", e, ";")) } - at_clock.getOrElseUpdate(clk, ArrayBuffer[Seq[Any]]()) ++= addUpdate(netlist(r), "") } - def update(e: Expression, value: Expression, clk: Expression, en: Expression, info: Info) = { - if (!at_clock.contains(clk)) at_clock(clk) = ArrayBuffer[Seq[Any]]() - if (weq(en,one)) at_clock(clk) += Seq(e," <= ",value,";") - else { - at_clock(clk) += Seq("if(",en,") begin") - at_clock(clk) += Seq(tab,e," <= ",value,";",info) - at_clock(clk) += Seq("end") - } - } + at_clock.getOrElseUpdate(clk, ArrayBuffer[Seq[Any]]()) ++= addUpdate(netlist(r), "") + } - // Declares an intermediate wire to hold a large enough random number. - // Then, return the correct number of bits selected from the random value - def rand_string(t: Type) : Seq[Any] = { - val nx = namespace.newName("_RAND") - val rand = VRandom(bitWidth(t)) - val tx = SIntType(IntWidth(rand.realWidth)) - declare("reg",nx, tx, NoInfo) - initials += Seq(wref(nx, tx), " = ", VRandom(bitWidth(t)), ";") - Seq(nx, "[", bitWidth(t) - 1, ":0]") + def update(e: Expression, value: Expression, clk: Expression, en: Expression, info: Info) = { + if (!at_clock.contains(clk)) at_clock(clk) = ArrayBuffer[Seq[Any]]() + if (weq(en, one)) at_clock(clk) += Seq(e, " <= ", value, ";") + else { + at_clock(clk) += Seq("if(", en, ") begin") + at_clock(clk) += Seq(tab, e, " <= ", value, ";", info) + at_clock(clk) += Seq("end") } + } - def initialize(e: Expression) = { - initials += Seq("`ifdef RANDOMIZE_REG_INIT") - initials += Seq(e, " = ", rand_string(e.tpe), ";") - initials += Seq("`endif // RANDOMIZE_REG_INIT") + // Declares an intermediate wire to hold a large enough random number. + // Then, return the correct number of bits selected from the random value + def rand_string(t: Type): Seq[Any] = { + val nx = namespace.newName("_RAND") + val rand = VRandom(bitWidth(t)) + val tx = SIntType(IntWidth(rand.realWidth)) + declare("reg", nx, tx, NoInfo) + initials += Seq(wref(nx, tx), " = ", VRandom(bitWidth(t)), ";") + Seq(nx, "[", bitWidth(t) - 1, ":0]") + } + + def initialize(e: Expression) = { + initials += Seq("`ifdef RANDOMIZE_REG_INIT") + initials += Seq(e, " = ", rand_string(e.tpe), ";") + initials += Seq("`endif // RANDOMIZE_REG_INIT") + } + + def initialize_mem(s: DefMemory) { + val index = wref("initvar", s.dataType) + val rstring = rand_string(s.dataType) + initials += Seq("`ifdef RANDOMIZE_MEM_INIT") + initials += Seq("for (initvar = 0; initvar < ", s.depth, "; initvar = initvar+1)") + initials += Seq(tab, WSubAccess(wref(s.name, s.dataType), index, s.dataType, FEMALE), + " = ", rstring, ";") + initials += Seq("`endif // RANDOMIZE_MEM_INIT") + } + + def simulate(clk: Expression, en: Expression, s: Seq[Any], cond: Option[String], info: Info) = { + if (!at_clock.contains(clk)) at_clock(clk) = ArrayBuffer[Seq[Any]]() + at_clock(clk) += Seq("`ifndef SYNTHESIS") + if (cond.nonEmpty) { + at_clock(clk) += Seq(s"`ifdef ${cond.get}") + at_clock(clk) += Seq(tab, s"if (`${cond.get}) begin") + at_clock(clk) += Seq("`endif") } + at_clock(clk) += Seq(tab, tab, "if (", en, ") begin") + at_clock(clk) += Seq(tab, tab, tab, s, info) + at_clock(clk) += Seq(tab, tab, "end") + if (cond.nonEmpty) { + at_clock(clk) += Seq(s"`ifdef ${cond.get}") + at_clock(clk) += Seq(tab, "end") + at_clock(clk) += Seq("`endif") + } + at_clock(clk) += Seq("`endif // SYNTHESIS") + } - def initialize_mem(s: DefMemory) { - val index = wref("initvar", s.dataType) - val rstring = rand_string(s.dataType) - initials += Seq("`ifdef RANDOMIZE_MEM_INIT") - initials += Seq("for (initvar = 0; initvar < ", s.depth, "; initvar = initvar+1)") - initials += Seq(tab, WSubAccess(wref(s.name, s.dataType), index, s.dataType, FEMALE), - " = ", rstring,";") - initials += Seq("`endif // RANDOMIZE_MEM_INIT") + def stop(ret: Int): Seq[Any] = Seq(if (ret == 0) "$finish;" else "$fatal;") + + def printf(str: StringLit, args: Seq[Expression]): Seq[Any] = { + val strx = str.verilogEscape +: args.flatMap(Seq(",", _)) + Seq("$fwrite(32'h80000002,", strx, ");") + } + + // Turn ports into Seq[String] and add to portdefs + 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, ' ')) } - def simulate(clk: Expression, en: Expression, s: Seq[Any], cond: Option[String], info: Info) = { - if (!at_clock.contains(clk)) at_clock(clk) = ArrayBuffer[Seq[Any]]() - at_clock(clk) += Seq("`ifndef SYNTHESIS") - if (cond.nonEmpty) { - at_clock(clk) += Seq(s"`ifdef ${cond.get}") - at_clock(clk) += Seq(tab, s"if (`${cond.get}) begin") - at_clock(clk) += Seq("`endif") - } - at_clock(clk) += Seq(tab,tab,"if (",en,") begin") - at_clock(clk) += Seq(tab,tab,tab,s,info) - at_clock(clk) += Seq(tab,tab,"end") - if (cond.nonEmpty) { - at_clock(clk) += Seq(s"`ifdef ${cond.get}") - at_clock(clk) += Seq(tab,"end") - at_clock(clk) += Seq("`endif") + // Turn directions into strings (and AnalogType into inout) + val dirs = m.ports map { case Port(_, name, dir, tpe) => + (dir, tpe) match { + case (_, AnalogType(_)) => "inout " // padded to length of output + case (Input, _) => "input " + case (Output, _) => "output" } - at_clock(clk) += Seq("`endif // SYNTHESIS") + } + // Turn types into strings, all ports must be GroundTypes + val tpes = m.ports map { + case Port(_, _, _, tpe: GroundType) => stringify(tpe) + case port: Port => error("Trying to emit non-GroundType Port $port") } - def stop(ret: Int): Seq[Any] = Seq(if (ret == 0) "$finish;" else "$fatal;") - - def printf(str: StringLit, args: Seq[Expression]): Seq[Any] = { - val strx = str.verilogEscape +: args.flatMap(Seq(",",_)) - Seq("$fwrite(32'h80000002,", strx, ");") + // dirs are already padded + portdefs ++= (dirs, padToMax(tpes), m.ports).zipped.toSeq.zipWithIndex.map { + case ((dir, tpe, Port(info, name, _, _)), i) => + if (i != m.ports.size - 1) Seq(dir, " ", tpe, " ", name, ",", info) + else Seq(dir, " ", tpe, " ", name, info) } + } - // Turn ports into Seq[String] and add to portdefs - 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(_, name, dir, tpe) => - (dir, tpe) match { - case (_, AnalogType(_)) => "inout " // padded to length of output - case (Input, _) => "input " - case (Output, _) => "output" + 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) } + // Synthesizable ones as well + attachSynAssigns += Seq("assign ", a, " = ", b, ";", sx.info) + attachSynAssigns += Seq("assign ", b, " = ", a, ";", sx.info) } - // Turn types into strings, all ports must be GroundTypes - val tpes = m.ports map { - case Port(_,_,_, tpe: GroundType) => stringify(tpe) - case port: Port => error("Trying to emit non-GroundType Port $port") + // 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) } - - // dirs are already padded - portdefs ++= (dirs, padToMax(tpes), m.ports).zipped.toSeq.zipWithIndex.map { - case ((dir, tpe, Port(info, name, _,_)), i) => - if (i != m.ports.size - 1) Seq(dir, " " , tpe, " ", name, ",", info) - else Seq(dir, " " , tpe, " ", name, info) + 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) } - } - 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) } - // 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() { + emit(Seq("module ", m.name, "(", m.info)) + for (x <- portdefs) emit(Seq(tab, x)) + emit(Seq(");")) + + if (declares.isEmpty && assigns.isEmpty) emit(Seq(tab, "initial begin end")) + for (x <- declares) emit(Seq(tab, x)) + for (x <- instdeclares) emit(Seq(tab, x)) + for (x <- assigns) emit(Seq(tab, x)) + if (attachAliases.nonEmpty) { + emit(Seq("`ifdef SYNTHESIS")) + for (x <- attachSynAssigns) emit(Seq(tab, x)) + emit(Seq("`elsif verilator")) + emit(Seq(tab, "`error \"Verilator does not support alias and thus cannot arbirarily connect bidirectional wires and ports\"")) + emit(Seq("`else")) + for (x <- attachAliases) emit(Seq(tab, x)) + emit(Seq("`endif")) + } + if (initials.nonEmpty) { + emit(Seq("`ifdef RANDOMIZE_GARBAGE_ASSIGN")) + emit(Seq("`define RANDOMIZE")) + emit(Seq("`endif")) + emit(Seq("`ifdef RANDOMIZE_INVALID_ASSIGN")) + emit(Seq("`define RANDOMIZE")) + emit(Seq("`endif")) + emit(Seq("`ifdef RANDOMIZE_REG_INIT")) + emit(Seq("`define RANDOMIZE")) + emit(Seq("`endif")) + emit(Seq("`ifdef RANDOMIZE_MEM_INIT")) + emit(Seq("`define RANDOMIZE")) + emit(Seq("`endif")) + emit(Seq("`ifdef RANDOMIZE")) + emit(Seq(" integer initvar;")) + emit(Seq(" initial begin")) + // This enables test benches to set the random values at time 0.001, + // then start the simulation later + // Verilator does not support delay statements, so they are omitted. + emit(Seq(" `ifndef verilator")) + emit(Seq(" #0.002 begin end")) + emit(Seq(" `endif")) + for (x <- initials) emit(Seq(tab, x)) + emit(Seq(" end")) + emit(Seq("`endif // RANDOMIZE")) } - def emit_streams() { - emit(Seq("module ", m.name, "(", m.info)) - for (x <- portdefs) emit(Seq(tab, x)) - emit(Seq(");")) - - if (declares.isEmpty && assigns.isEmpty) emit(Seq(tab, "initial begin end")) - for (x <- declares) emit(Seq(tab, x)) - for (x <- instdeclares) emit(Seq(tab, x)) - for (x <- assigns) emit(Seq(tab, x)) - if (attachAliases.nonEmpty) { - emit(Seq("`ifdef SYNTHESIS")) - for (x <- attachSynAssigns) emit(Seq(tab, x)) - emit(Seq("`elsif verilator")) - emit(Seq(tab, "`error \"Verilator does not support alias and thus cannot arbirarily connect bidirectional wires and ports\"")) - emit(Seq("`else")) - for (x <- attachAliases) emit(Seq(tab, x)) - emit(Seq("`endif")) - } - if (initials.nonEmpty) { - emit(Seq("`ifdef RANDOMIZE_GARBAGE_ASSIGN")) - emit(Seq("`define RANDOMIZE")) - emit(Seq("`endif")) - emit(Seq("`ifdef RANDOMIZE_INVALID_ASSIGN")) - emit(Seq("`define RANDOMIZE")) - emit(Seq("`endif")) - emit(Seq("`ifdef RANDOMIZE_REG_INIT")) - emit(Seq("`define RANDOMIZE")) - emit(Seq("`endif")) - emit(Seq("`ifdef RANDOMIZE_MEM_INIT")) - emit(Seq("`define RANDOMIZE")) - emit(Seq("`endif")) - emit(Seq("`ifdef RANDOMIZE")) - emit(Seq(" integer initvar;")) - emit(Seq(" initial begin")) - // This enables test benches to set the random values at time 0.001, - // then start the simulation later - // Verilator does not support delay statements, so they are omitted. - emit(Seq(" `ifndef verilator")) - emit(Seq(" #0.002 begin end")) - emit(Seq(" `endif")) - for (x <- initials) emit(Seq(tab, x)) - emit(Seq(" end")) - emit(Seq("`endif // RANDOMIZE")) - } - - for (clk_stream <- at_clock if clk_stream._2.nonEmpty) { - emit(Seq(tab, "always @(posedge ", clk_stream._1, ") begin")) - for (x <- clk_stream._2) emit(Seq(tab, tab, x)) - emit(Seq(tab, "end")) - } - emit(Seq("endmodule")) + for (clk_stream <- at_clock if clk_stream._2.nonEmpty) { + emit(Seq(tab, "always @(posedge ", clk_stream._1, ") begin")) + for (x <- clk_stream._2) emit(Seq(tab, tab, x)) + emit(Seq(tab, "end")) } + emit(Seq("endmodule")) + } + + /** + * The standard verilog emitter, wraps up everything into the + * verilog + * @return + */ + def emit_verilog(): DefModule = { build_netlist(m.body) build_ports() build_streams(m.body) emit_streams() m + } + + /** + * This emits a verilog module that can be bound to a module defined in chisel. + * It uses the same machinery as the general emitter in order to insure that + * parameters signature is exactly the same as the module being bound to + * @param overrideName Override the module name + * @param body the body of the bind module + * @return A module constructed from the body + */ + def emitVerilogBind(overrideName: String, body: String): DefModule = { + build_netlist(m.body) + build_ports() + emit(Seq("module ", overrideName, "(", m.info)) + for (x <- portdefs) emit(Seq(tab, x)) + + emit(Seq(");")) + emit(body) + emit(Seq("endmodule"), top = 0) + m + } } /** Preamble for every emitted Verilog file */ def transforms = Seq( + new BlackBoxSourceHelper, new ReplaceTruncatingArithmetic, new FlattenRegUpdate, new DeadCodeElimination, @@ -670,7 +734,9 @@ class VerilogEmitter extends SeqTransform with Emitter { val circuit = runTransforms(state).circuit val moduleMap = circuit.modules.map(m => m.name -> m).toMap circuit.modules.foreach { - case m: Module => emit_verilog(m, moduleMap)(writer) + case m: Module => + val renderer = new VerilogRender(m, moduleMap)(writer) + renderer.emit_verilog() case _: ExtModule => // do nothing } } @@ -689,7 +755,8 @@ class VerilogEmitter extends SeqTransform with Emitter { circuit.modules flatMap { case module: Module => val writer = new java.io.StringWriter - emit_verilog(module, moduleMap)(writer) + val renderer = new VerilogRender(module, moduleMap)(writer) + renderer.emit_verilog() Some(EmittedVerilogModuleAnnotation(EmittedVerilogModule(module.name, writer.toString))) case _: ExtModule => None } diff --git a/src/main/scala/firrtl/LoweringCompilers.scala b/src/main/scala/firrtl/LoweringCompilers.scala index ba686f08..dce6bac9 100644 --- a/src/main/scala/firrtl/LoweringCompilers.scala +++ b/src/main/scala/firrtl/LoweringCompilers.scala @@ -149,7 +149,7 @@ class LowFirrtlCompiler extends Compiler { class VerilogCompiler extends Compiler { def emitter = new VerilogEmitter def transforms: Seq[Transform] = getLoweringTransforms(ChirrtlForm, LowForm) ++ - Seq(new LowFirrtlOptimization, new BlackBoxSourceHelper) + Seq(new LowFirrtlOptimization) } /** Emits Verilog without optimizations */ diff --git a/src/main/scala/firrtl/annotations/LoadMemoryAnnotation.scala b/src/main/scala/firrtl/annotations/LoadMemoryAnnotation.scala new file mode 100644 index 00000000..a5ba22dd --- /dev/null +++ b/src/main/scala/firrtl/annotations/LoadMemoryAnnotation.scala @@ -0,0 +1,65 @@ +// See LICENSE for license details. + +package firrtl.annotations + +/** + * Enumeration of the two types of readmem statements available in verilog + */ +object MemoryLoadFileType extends Enumeration { + type FileType = Value + + val Hex: Value = Value("h") + val Binary: Value = Value("b") +} + +/** + * Firrtl implementation for load memory + * @param target memory to load + * @param fileName name of input file + * @param hexOrBinary use $readmemh or $readmemb + */ +case class LoadMemoryAnnotation( + target: ComponentName, + fileName: String, + hexOrBinary: MemoryLoadFileType.FileType = MemoryLoadFileType.Hex, + originalMemoryNameOpt: Option[String] = None +) extends SingleTargetAnnotation[Named] { + + val (prefix, suffix) = { + fileName.split("""\.""").toList match { + case Nil => + throw new Exception(s"empty filename not allowed in LoadMemoryAnnotation") + case name :: Nil => + (name, "") + case other => + (other.reverse.tail.reverse.mkString("."), "." + other.last) + } + } + + def getFileName: String = { + originalMemoryNameOpt match { + case Some(originalMemoryName) => + if(target.name == originalMemoryName) { + prefix + suffix + } + else { + prefix + target.name.drop(originalMemoryName.length) + suffix + } + case _ => + fileName + } + } + + def getSuffix: String = suffix + + def duplicate(newNamed: Named): LoadMemoryAnnotation = { + newNamed match { + case componentName: ComponentName => + this.copy(target = componentName, originalMemoryNameOpt = Some(target.name)) + case _ => + throw new Exception(s"Cannot annotate anything but a memory, invalid target ${newNamed.serialize}") + } + } +} + + diff --git a/src/test/scala/firrtlTests/VerilogEmitterTests.scala b/src/test/scala/firrtlTests/VerilogEmitterTests.scala index f4df8519..46f3a711 100644 --- a/src/test/scala/firrtlTests/VerilogEmitterTests.scala +++ b/src/test/scala/firrtlTests/VerilogEmitterTests.scala @@ -173,4 +173,63 @@ class VerilogEmitterSpec extends FirrtlFlatSpec { result should containLine ("assign out = in;") } } + + "The verilog emitter" should "offer support for generating bindable forms of modules" in { + val emitter = new VerilogEmitter + val input = + """circuit Test : + | module Test : + | input a : UInt<25000> + | output b : UInt + | input c : UInt<32> + | output d : UInt + | input e : UInt<1> + | input f : Analog<32> + | b <= a + | d <= add(c, e) + |""".stripMargin + val check = + """ + |module BindsToTest( + | input [24999:0] a, + | output [24999:0] b, + | input [31:0] c, + | output [32:0] d, + | input e, + | inout [31:0] f + |); + | + |$readmemh("file", memory); + | + |endmodule""".stripMargin.split("\n") + + // We don't use executeTest because we care about the spacing in the result + val writer = new java.io.StringWriter + + val initialState = CircuitState(parse(input), ChirrtlForm) + val compiler = new LowFirrtlCompiler() + + val state = compiler.compile(initialState, Seq.empty) + + val moduleMap = state.circuit.modules.map(m => m.name -> m).toMap + + val module = state.circuit.modules.filter(module => module.name == "Test").collectFirst { case m: firrtl.ir.Module => m }.get + + val renderer = emitter.getRenderer(module, moduleMap)(writer) + + renderer.emitVerilogBind("BindsToTest", + """ + |$readmemh("file", memory); + | + |""".stripMargin) + val lines = writer.toString.split("\n") + + val outString = writer.toString + + // This confirms that the module io's were emitted + for (c <- check) { + lines should contain (c) + } + } + } diff --git a/src/test/scala/firrtlTests/transforms/BlacklBoxSourceHelperSpec.scala b/src/test/scala/firrtlTests/transforms/BlacklBoxSourceHelperSpec.scala index d102378f..455ceb3f 100644 --- a/src/test/scala/firrtlTests/transforms/BlacklBoxSourceHelperSpec.scala +++ b/src/test/scala/firrtlTests/transforms/BlacklBoxSourceHelperSpec.scala @@ -4,7 +4,7 @@ package firrtlTests.transforms import firrtl.annotations.{Annotation, CircuitName, ModuleName} import firrtl.transforms._ -import firrtl.{FIRRTLException, Transform, VerilogCompiler} +import firrtl.{FIRRTLException, Transform, VerilogCompiler, VerilogEmitter} import firrtlTests.{HighTransformSpec, LowTransformSpec} import org.scalacheck.Test.Failed import org.scalatest.{FreeSpec, Matchers, Succeeded} @@ -63,7 +63,7 @@ class BlacklBoxSourceHelperTransformSpec extends LowTransformSpec { } "verilog compiler" should "have BlackBoxSourceHelper transform" in { - val verilogCompiler = new VerilogCompiler + val verilogCompiler = new VerilogEmitter verilogCompiler.transforms.map { x => x.getClass } should contain (classOf[BlackBoxSourceHelper]) } } |
