diff options
| author | Albert Magyar | 2021-03-15 11:24:09 -0700 |
|---|---|---|
| committer | Albert Magyar | 2021-04-05 12:00:02 -0700 |
| commit | 872b282ff5bd7cc08f97a42e388b4e043454fe22 (patch) | |
| tree | 2ef54cf66c13314300cd02c998486bcbdd963d5d /src/main | |
| parent | 47cc4a8c57a7d50c9914ceeadbcd19f71f179187 (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/main')
| -rw-r--r-- | src/main/scala/firrtl/passes/memlib/SeparateWriteClocks.scala | 74 |
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) + } +} |
