1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
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)
}
}
|