This repository has been archived by the owner on Aug 20, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 177
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2111 from chipsalliance/fpga-backend
Add -fpga flag to enable FPGA-oriented compilation strategies (currently for memories)
- Loading branch information
Showing
11 changed files
with
456 additions
and
68 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
74 changes: 74 additions & 0 deletions
74
src/main/scala/firrtl/passes/memlib/SeparateWriteClocks.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} | ||
} |
57 changes: 57 additions & 0 deletions
57
src/main/scala/firrtl/passes/memlib/SetDefaultReadUnderWrite.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package firrtl.passes | ||
package memlib | ||
|
||
import firrtl._ | ||
import firrtl.ir._ | ||
import firrtl.options.{Dependency, OptionsException} | ||
import firrtl.annotations.NoTargetAnnotation | ||
|
||
sealed trait DefaultReadUnderWriteAnnotation extends NoTargetAnnotation | ||
|
||
/** This annotation directs the [[SetDefaultReadUnderWrite]] transform to assign a default value of 'old' (read-first | ||
* behavior) to all synchronous-read memories with 'undefined' read-under-write parameters. | ||
*/ | ||
case object DefaultReadFirstAnnotation extends DefaultReadUnderWriteAnnotation | ||
|
||
/** This annotation directs the [[SetDefaultReadUnderWrite]] transform to assign a default value of 'new' (write-first | ||
* behavior) to all synchronous-read memories with 'undefined' read-under-write parameters. | ||
*/ | ||
case object DefaultWriteFirstAnnotation extends DefaultReadUnderWriteAnnotation | ||
|
||
/** | ||
* Adding a [[DefaultReadUnderWriteAnnotation]] and running the [[SetDefaultReadUnderWrite]] transform will cause all | ||
* synchronous-read memories with 'undefined' read-under-write parameters to be assigned a default parameter value, | ||
* either 'old' (read-first behavior) or 'new' (write-first behavior). This can help generate Verilog that is amenable | ||
* to RAM macro inference for various FPGA tools, or it can be used to satisfy other downstream design constraints. | ||
*/ | ||
class SetDefaultReadUnderWrite extends Transform with DependencyAPIMigration { | ||
override def prerequisites = firrtl.stage.Forms.HighForm | ||
override def optionalPrerequisites = Seq(Dependency[InferReadWrite]) | ||
override def optionalPrerequisiteOf = Seq(Dependency(VerilogMemDelays)) | ||
override def invalidates(a: Transform): Boolean = false | ||
|
||
private def onStmt(defaultRUW: ReadUnderWrite.Value)(stmt: Statement): Statement = stmt match { | ||
case mem: DefMemory if (mem.readLatency > 0 && mem.readUnderWrite == ReadUnderWrite.Undefined) => | ||
mem.copy(readUnderWrite = defaultRUW) | ||
case s => s.mapStmt(onStmt(defaultRUW)) | ||
} | ||
|
||
override def execute(state: CircuitState): CircuitState = { | ||
val c = state.circuit | ||
val ruwDefaults = state.annotations | ||
.collect({ | ||
case DefaultReadFirstAnnotation => ReadUnderWrite.Old | ||
case DefaultWriteFirstAnnotation => ReadUnderWrite.New | ||
}) | ||
.toSet | ||
if (ruwDefaults.size == 0) { | ||
state | ||
} else if (ruwDefaults.size == 1) { | ||
state.copy(circuit = c.copy(modules = c.modules.map(m => m.mapStmt(onStmt(ruwDefaults.head))))) | ||
} else { | ||
throw new OptionsException("Conflicting default read-under-write settings.") | ||
} | ||
} | ||
} |
Oops, something went wrong.