aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChick Markley2018-07-26 11:18:46 -0700
committerGitHub2018-07-26 11:18:46 -0700
commit1dcf9907eaa2f5fd2bc1e5a7dafeb1ae4b8e1434 (patch)
tree40ac6b81e978a74df69b69dc29ea48608d7d2040
parent7dff927840a30893facae957595a8e88ea62509a (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
-rw-r--r--src/main/scala/firrtl/Emitter.scala685
-rw-r--r--src/main/scala/firrtl/LoweringCompilers.scala2
-rw-r--r--src/main/scala/firrtl/annotations/LoadMemoryAnnotation.scala65
-rw-r--r--src/test/scala/firrtlTests/VerilogEmitterTests.scala59
-rw-r--r--src/test/scala/firrtlTests/transforms/BlacklBoxSourceHelperSpec.scala4
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])
}
}