aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAlbert Magyar2021-03-09 21:30:53 -0800
committerAlbert Magyar2021-04-05 12:00:02 -0700
commita90cf1105467cab7c6708ea3faae35e1454cb0fd (patch)
treebfc40443826cc44196230f7427aa5ba80ca8f332 /src
parent088c82244d58d7e5c8a6ad6e7e3bb1edaf81af3a (diff)
Allow direct emission of sync-read memories to Verilog
* Emit readwrite ports, if applicable * Does not change VerilogMemDelays -> no effect on default flow * Use more single-line declare-and-assign statements for mem wires * Update error messages for too-complex memories in VerilogEmitter * Run scalafmt on VerilogEmitter
Diffstat (limited to 'src')
-rw-r--r--src/main/scala/firrtl/backends/verilog/VerilogEmitter.scala113
-rw-r--r--src/test/scala/firrtlTests/InfoSpec.scala10
2 files changed, 77 insertions, 46 deletions
diff --git a/src/main/scala/firrtl/backends/verilog/VerilogEmitter.scala b/src/main/scala/firrtl/backends/verilog/VerilogEmitter.scala
index f854b33a..66df12e0 100644
--- a/src/main/scala/firrtl/backends/verilog/VerilogEmitter.scala
+++ b/src/main/scala/firrtl/backends/verilog/VerilogEmitter.scala
@@ -1065,32 +1065,53 @@ class VerilogEmitter extends SeqTransform with Emitter {
val decl = if (fullSize > (1 << 29)) "reg /* sparse */" else "reg"
declareVectorType(decl, sx.name, sx.dataType, sx.depth, sx.info)
initialize_mem(sx, options)
- if (sx.readLatency != 0 || sx.writeLatency != 1)
+ // Currently, no idiomatic way to directly emit write-first RW ports
+ val hasComplexRW = (sx.readwriters.nonEmpty &&
+ (sx.readLatency != 1 || sx.readUnderWrite == ReadUnderWrite.New))
+ if (sx.readLatency > 1 || sx.writeLatency != 1 || hasComplexRW)
throw EmitterException(
- "All memories should be transformed into " +
- "blackboxes or combinational by previous passses"
+ Seq(
+ s"Memory ${sx.name} is too complex to emit directly.",
+ "Consider running VerilogMemDelays to simplify complex memories.",
+ "Alternatively, add the --repl-seq-mem flag to replace memories with blackboxes."
+ ).mkString(" ")
)
+ def createMemWire(name: String, tpe: Type, rhs: InfoExpr): Unit = {
+ declare("wire", name, tpe, MultiInfo(sx.info, rhs.info), rhs.expr)
+ }
+
for (r <- sx.readers) {
val data = memPortField(sx, r, "data")
val addr = memPortField(sx, r, "addr")
- // Ports should share an always@posedge, so can't have intermediary wire
-
- 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))
- // assign(en, netlist(en)) //;Connects value to m.r.en
- val mem = WRef(sx.name, memType(sx), MemKind, UnknownFlow)
- val memPort = WSubAccess(mem, addr, sx.dataType, UnknownFlow)
+ val en = memPortField(sx, r, "en")
+ val memPort = WSubAccess(WRef(sx), addr, sx.dataType, UnknownFlow)
val depthValue = UIntLiteral(sx.depth, IntWidth(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)
+ val clkSource = netlist(memPortField(sx, r, "clk")).expr
+
+ createMemWire(LowerTypes.loweredName(en), en.tpe, netlist(en))
+
+ if (sx.readLatency == 1 && sx.readUnderWrite != ReadUnderWrite.Old) {
+ val InfoExpr(addrInfo, addrDriver) = netlist(addr)
+ declare("reg", LowerTypes.loweredName(addr), addr.tpe, sx.info)
+ initialize(WRef(LowerTypes.loweredName(addr), addr.tpe), zero, zero)
+ update(addr, addrDriver, clkSource, en, addrInfo)
+ } else {
+ createMemWire(LowerTypes.loweredName(addr), addr.tpe, netlist(addr))
+ }
+
+ if (sx.readLatency == 1 && sx.readUnderWrite == ReadUnderWrite.Old) {
+ declare("reg", LowerTypes.loweredName(data), data.tpe, sx.info)
+ initialize(WRef(LowerTypes.loweredName(data), data.tpe), zero, zero)
+ update(data, memPort, clkSource, en, sx.info)
+ } else {
+ declare("wire", LowerTypes.loweredName(data), data.tpe, sx.info)
+ if ((sx.depth & (sx.depth - 1)) == 0)
+ assign(data, memPort, sx.info)
+ else
+ garbageAssign(data, memPort, garbageGuard, sx.info)
+ }
}
for (w <- sx.writers) {
@@ -1098,31 +1119,41 @@ class VerilogEmitter extends SeqTransform with Emitter {
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
- // TODO should we use the info here for anything?
- val InfoExpr(_, 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
- assign(data, netlist(data))
- assign(addr, netlist(addr))
- assign(mask, netlist(mask))
- assign(en, netlist(en))
-
- val mem = WRef(sx.name, memType(sx), MemKind, UnknownFlow)
- val memPort = WSubAccess(mem, addr, sx.dataType, UnknownFlow)
- update(memPort, data, clk, AND(en, mask), sx.info)
+
+ val clkSource = netlist(memPortField(sx, w, "clk")).expr
+
+ createMemWire(LowerTypes.loweredName(data), data.tpe, netlist(data))
+ createMemWire(LowerTypes.loweredName(addr), addr.tpe, netlist(addr))
+ createMemWire(LowerTypes.loweredName(mask), mask.tpe, netlist(mask))
+ createMemWire(LowerTypes.loweredName(en), en.tpe, netlist(en))
+
+ val memPort = WSubAccess(WRef(sx), addr, sx.dataType, UnknownFlow)
+ update(memPort, data, clkSource, AND(en, mask), sx.info)
+ }
+
+ for (rw <- sx.readwriters) {
+ val rdata = memPortField(sx, rw, "rdata")
+ val wdata = memPortField(sx, rw, "wdata")
+ val addr = memPortField(sx, rw, "addr")
+ val en = memPortField(sx, rw, "en")
+ val wmode = memPortField(sx, rw, "wmode")
+ val wmask = memPortField(sx, rw, "wmask")
+ val memPort = WSubAccess(WRef(sx), addr, sx.dataType, UnknownFlow)
+
+ val clkSource = netlist(memPortField(sx, rw, "clk")).expr
+
+ createMemWire(LowerTypes.loweredName(wdata), wdata.tpe, netlist(wdata))
+ createMemWire(LowerTypes.loweredName(addr), addr.tpe, netlist(addr))
+ createMemWire(LowerTypes.loweredName(wmode), wmode.tpe, netlist(wmode))
+ createMemWire(LowerTypes.loweredName(wmask), wmask.tpe, netlist(wmask))
+ createMemWire(LowerTypes.loweredName(en), en.tpe, netlist(en))
+
+ declare("reg", LowerTypes.loweredName(rdata), rdata.tpe, sx.info)
+ initialize(WRef(LowerTypes.loweredName(rdata), rdata.tpe), zero, zero)
+ update(rdata, memPort, clkSource, en, sx.info)
+ update(memPort, wdata, clkSource, AND(en, AND(wmode, wmask)), sx.info)
}
- if (sx.readwriters.nonEmpty)
- throw EmitterException(
- "All readwrite ports should be transformed into " +
- "read & write ports by previous passes"
- )
case _ =>
}
}
diff --git a/src/test/scala/firrtlTests/InfoSpec.scala b/src/test/scala/firrtlTests/InfoSpec.scala
index 43fb6ee1..db4828f6 100644
--- a/src/test/scala/firrtlTests/InfoSpec.scala
+++ b/src/test/scala/firrtlTests/InfoSpec.scala
@@ -91,11 +91,11 @@ class InfoSpec extends FirrtlFlatSpec with FirrtlMatchers {
result should containTree { case DefMemory(Info1, "m", _, _, _, _, _, _, _, _) => true }
result should containLine(s"reg [7:0] m [0:31]; //$Info1")
result should containLine(s"wire [7:0] m_r_data; //$Info1")
- result should containLine(s"wire [4:0] m_r_addr; //$Info1")
- result should containLine(s"wire [7:0] m_w_data; //$Info1")
- result should containLine(s"wire [4:0] m_w_addr; //$Info1")
- result should containLine(s"wire m_w_mask; //$Info1")
- result should containLine(s"wire m_w_en; //$Info1")
+ result should containLine(s"wire [4:0] m_r_addr = addr; //$Info1")
+ result should containLine(s"wire [7:0] m_w_data = 8'h0; //$Info1")
+ result should containLine(s"wire [4:0] m_w_addr = addr; //$Info1")
+ result should containLine(s"wire m_w_mask = 1'h0; //$Info1")
+ result should containLine(s"wire m_w_en = 1'h0; //$Info1")
result should containLine(s"assign m_r_data = m[m_r_addr]; //$Info1")
result should containLine(s"m[m_w_addr] <= m_w_data; //$Info1")
}