aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAlbert Magyar2021-03-15 11:24:09 -0700
committerAlbert Magyar2021-04-05 12:00:02 -0700
commit872b282ff5bd7cc08f97a42e388b4e043454fe22 (patch)
tree2ef54cf66c13314300cd02c998486bcbdd963d5d /src
parent47cc4a8c57a7d50c9914ceeadbcd19f71f179187 (diff)
Add SeparateWriteClocks to ensure one mem write per Verilog process
* Address @ekiwi comments from review * Change match cases to scalafmt-mandated lined-up style
Diffstat (limited to 'src')
-rw-r--r--src/main/scala/firrtl/passes/memlib/SeparateWriteClocks.scala74
1 files changed, 74 insertions, 0 deletions
diff --git a/src/main/scala/firrtl/passes/memlib/SeparateWriteClocks.scala b/src/main/scala/firrtl/passes/memlib/SeparateWriteClocks.scala
new file mode 100644
index 00000000..f526f64b
--- /dev/null
+++ b/src/main/scala/firrtl/passes/memlib/SeparateWriteClocks.scala
@@ -0,0 +1,74 @@
+// SPDX-License-Identifier: Apache-2.0
+
+package firrtl.passes
+package memlib
+
+import firrtl._
+import firrtl.ir._
+import firrtl.passes.LowerTypes
+import firrtl.options.{Dependency, OptionsException}
+
+/**
+ * This transform introduces an intermediate wire on the clock field of each write port of synchronous-read memories
+ * that have *multiple* write/readwrite ports and undefined read-under-write collision behavior. Ultimately, the
+ * introduction of these intermediate wires does not change which clock net clocks each port; therefore, the purpose of
+ * this transform is to help generate Verilog that is more amenable to inference of RAM macros with multiple write
+ * ports in FPGA synthesis flows. This change will cause each write and each readwrite port to be emitted in a separate
+ * clocked procedure, yielding multiple benefits:
+ *
+ * 1) Separate write procedures avoid implicitly constraining cross-port read-write and write-write collision behaviors
+ * 2) The preference for separate clocked procedures for each write port is explicitly specified by Intel and Xilinx
+ *
+ * While this feature is not intended to be vendor-specific, inference of *multiple-write* RAM macros from behavioral
+ * Verilog or VHDL requires both advanced underlying RAM primitives and advanced synthesis tools. Currently, mapping
+ * such memories to programmable devices beyond modern Intel and Xilinx architectures can be prohibitive for users.
+ *
+ * Though the emission of separate processes for write ports could be absorbed into the Verilog emitter, the use of a
+ * pure-FIRRTL transform reduces implementation complexity and enhances reliability.
+ */
+class SeparateWriteClocks extends Transform with DependencyAPIMigration {
+ override def prerequisites = Seq(Dependency(passes.RemoveCHIRRTL), Dependency(passes.ExpandConnects))
+ override def optionalPrerequisites = Seq(Dependency[InferReadWrite])
+ override def optionalPrerequisiteOf = Seq(Dependency[SetDefaultReadUnderWrite])
+ override def invalidates(a: Transform): Boolean = a match {
+ case ResolveFlows => true
+ case _ => false
+ }
+
+ private type ExprMap = collection.mutable.HashMap[WrappedExpression, Reference]
+
+ private def onExpr(replaceExprs: ExprMap)(expr: Expression): Expression = expr match {
+ case wsf: WSubField if (replaceExprs.contains(WrappedExpression(wsf))) =>
+ replaceExprs(WrappedExpression(wsf))
+ case e => e.mapExpr(onExpr(replaceExprs))
+ }
+
+ private def isMultiWriteSyncReadUndefinedRUW(mem: DefMemory): Boolean = {
+ (mem.writers.size + mem.readwriters.size) > 1 &&
+ mem.readLatency == 1 && mem.writeLatency == 1 &&
+ mem.readUnderWrite == ReadUnderWrite.Undefined
+ }
+
+ private def onStmt(replaceExprs: ExprMap, ns: Namespace)(stmt: Statement): Statement = stmt match {
+ case mem: DefMemory if isMultiWriteSyncReadUndefinedRUW(mem) =>
+ val clockRefs = (mem.writers ++ mem.readwriters).map { p => MemPortUtils.memPortField(mem, p, "clk") }
+ val clockWireMap = clockRefs.map { pClk =>
+ WrappedExpression(pClk) -> DefWire(mem.info, ns.newName(LowerTypes.loweredName(pClk)), ClockType)
+ }
+ val clockStmts = clockWireMap.flatMap {
+ case (pClk, clkWire) => Seq(clkWire, Connect(mem.info, pClk.e1, Reference(clkWire)))
+ }
+ replaceExprs ++= clockWireMap.map { case (pClk, clkWire) => pClk -> Reference(clkWire) }
+ Block(mem +: clockStmts)
+ case Connect(i, lhs, rhs) => Connect(i, onExpr(replaceExprs)(lhs), rhs)
+ case PartialConnect(i, lhs, rhs) => PartialConnect(i, onExpr(replaceExprs)(lhs), rhs)
+ case IsInvalid(i, invalidated) => IsInvalid(i, onExpr(replaceExprs)(invalidated))
+ case s => s.mapStmt(onStmt(replaceExprs, ns))
+ }
+
+ override def execute(state: CircuitState): CircuitState = {
+ val c = state.circuit
+ val cPrime = c.copy(modules = c.modules.map(m => m.mapStmt(onStmt(new ExprMap, Namespace(m)))))
+ state.copy(circuit = cPrime)
+ }
+}