From 3be73de269e99a078cca17fbc49c9f592c483143 Mon Sep 17 00:00:00 2001 From: Tim Snyder Date: Mon, 23 Nov 2020 13:26:39 -0600 Subject: [PATCH 01/88] add weak and strong to Utils.v_keywords (#1983) Verilator 4.034 was complaining about wires being named weak and strong because those are SV 2009 keywords. Added them to the Utils.v_keywords list --- src/main/scala/firrtl/Utils.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/scala/firrtl/Utils.scala b/src/main/scala/firrtl/Utils.scala index 921ec60b33..467552cb1c 100644 --- a/src/main/scala/firrtl/Utils.scala +++ b/src/main/scala/firrtl/Utils.scala @@ -819,7 +819,7 @@ object Utils extends LazyLogging { "scalared", "sequence", "shortint", "shortreal", "showcancelled", "signed", "small", "solve", "specify", "specparam", "static", - "strength", "string", "strong0", "strong1", "struct", "super", + "strength", "string", "strong", "strong0", "strong1", "struct", "super", "supply0", "supply1", "table", "tagged", "task", "this", "throughout", "time", "timeprecision", @@ -830,7 +830,7 @@ object Utils extends LazyLogging { "var", "vectored", "virtual", "void", - "wait", "wait_order", "wand", "weak0", "weak1", "while", + "wait", "wait_order", "wand", "weak", "weak0", "weak1", "while", "wildcard", "wire", "with", "within", "wor", "xnor", "xor", From 4e46f8c614b81143621f2b4187392f6912d882bf Mon Sep 17 00:00:00 2001 From: Schuyler Eldridge Date: Mon, 30 Nov 2020 15:01:10 -0500 Subject: [PATCH 02/88] Add SortModules Transform (#1905) * Add SortModules to transform to def-before-use Adds a new transform, SortModules, that transforms a FIRRTL circuit to enforce an invariant of modules and external modules being defined before use. This transform is left as optional in the event that a user may wish to have a quick way of getting the circuit to respect this property as may be expected of some other tool, e.g., MLIR. Signed-off-by: Schuyler Eldridge * Add test of SortModules transform Signed-off-by: Schuyler Eldridge Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- .../scala/firrtl/transforms/SortModules.scala | 23 ++++++++++ .../transforms/SortModulesSpec.scala | 43 +++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 src/main/scala/firrtl/transforms/SortModules.scala create mode 100644 src/test/scala/firrtlTests/transforms/SortModulesSpec.scala diff --git a/src/main/scala/firrtl/transforms/SortModules.scala b/src/main/scala/firrtl/transforms/SortModules.scala new file mode 100644 index 0000000000..ffac16614d --- /dev/null +++ b/src/main/scala/firrtl/transforms/SortModules.scala @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: Apache-2.0 + +package firrtl.transforms + +import firrtl.{CircuitState, DependencyAPIMigration, Transform} +import firrtl.analyses.InstanceKeyGraph +import firrtl.options.Dependency +import firrtl.stage.Forms + +/** Return a circuit where all modules (and external modules) are defined before use. */ +class SortModules extends Transform with DependencyAPIMigration { + + override def prerequisites = Seq(Dependency(firrtl.passes.CheckChirrtl)) + override def optionalPrerequisites = Seq.empty + override def optionalPrerequisiteOf = Forms.ChirrtlEmitters + override def invalidates(a: Transform) = false + + override def execute(state: CircuitState): CircuitState = { + val modulesx = InstanceKeyGraph(state.circuit).moduleOrder.reverse + state.copy(circuit = state.circuit.copy(modules = modulesx)) + } + +} diff --git a/src/test/scala/firrtlTests/transforms/SortModulesSpec.scala b/src/test/scala/firrtlTests/transforms/SortModulesSpec.scala new file mode 100644 index 0000000000..4aabeeeaf1 --- /dev/null +++ b/src/test/scala/firrtlTests/transforms/SortModulesSpec.scala @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: Apache-2.0 + +package firrtlTests.transforms + +import firrtl.{ir, CircuitState, Parser} +import firrtl.transforms.SortModules +import firrtl.traversals.Foreachers._ + +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers + +import scala.collection.mutable + +class SortModulesSpec extends AnyFlatSpec with Matchers { + + private def collectModules(names: mutable.ArrayBuffer[String], module: ir.DefModule): Unit = names += module.name + + behavior.of("SortModules") + + it should "enforce define before use of modules" in { + + val input = + """|circuit Foo: + | module Foo: + | inst bar of Bar + | module Bar: + | inst baz of Baz + | extmodule Baz: + | input a: UInt<1> + |""".stripMargin + + val state = CircuitState(Parser.parse(input), Seq.empty) + val moduleNames = mutable.ArrayBuffer.empty[String] + + (new SortModules) + .execute(state) + .circuit + .foreach(collectModules(moduleNames, _: ir.DefModule)) + + (moduleNames should contain).inOrderOnly("Baz", "Bar", "Foo") + } + +} From 6c5ce834e26386100b196881f6e487aed26c9c0a Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Wed, 2 Dec 2020 01:53:04 +0000 Subject: [PATCH 03/88] Fix subaccess (#1984) * add test for RemoveAccessesSpec. * fix nested SubAccess bug. Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- .../scala/firrtl/passes/RemoveAccesses.scala | 35 ++++++++++++------- .../scala/firrtlTests/LowerTypesSpec.scala | 17 +++++++++ 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/src/main/scala/firrtl/passes/RemoveAccesses.scala b/src/main/scala/firrtl/passes/RemoveAccesses.scala index f49af9353e..90437e56f4 100644 --- a/src/main/scala/firrtl/passes/RemoveAccesses.scala +++ b/src/main/scala/firrtl/passes/RemoveAccesses.scala @@ -47,8 +47,7 @@ object RemoveAccesses extends Pass { * Seq(Location(a[0], UIntLiteral(0)), Location(a[1], UIntLiteral(1))) */ private def getLocations(e: Expression): Seq[Location] = e match { - case e: WRef => create_exps(e).map(Location(_, one)) - case e: WSubIndex => + case e: SubIndex => val ls = getLocations(e.expr) val start = get_point(e) val end = start + get_size(e.tpe) @@ -57,7 +56,7 @@ object RemoveAccesses extends Pass { (l, i) <- ls.zipWithIndex if ((i % stride) >= start) & ((i % stride) < end) ) yield l - case e: WSubField => + case e: SubField => val ls = getLocations(e.expr) val start = get_point(e) val end = start + get_size(e.tpe) @@ -66,17 +65,27 @@ object RemoveAccesses extends Pass { (l, i) <- ls.zipWithIndex if ((i % stride) >= start) & ((i % stride) < end) ) yield l - case e: WSubAccess => - val ls = getLocations(e.expr) - val stride = get_size(e.tpe) - val wrap = e.expr.tpe.asInstanceOf[VectorType].size - ls.zipWithIndex.map { - case (l, i) => - val c = (i / stride) % wrap - val basex = l.base - val guardx = AND(l.guard, EQV(UIntLiteral(c), e.index)) - Location(basex, guardx) + case SubAccess(expr, index, tpe, _) => + getLocations(expr).zipWithIndex.flatMap { + case (Location(exprBase, exprGuard), exprIndex) => + getLocations(index).map { + case Location(indexBase, indexGuard) => + Location( + exprBase, + AND( + AND( + indexGuard, + exprGuard + ), + EQV( + UIntLiteral((exprIndex / get_size(tpe)) % expr.tpe.asInstanceOf[VectorType].size), + indexBase + ) + ) + ) + } } + case e => create_exps(e).map(Location(_, one)) } /** Returns true if e contains a [[firrtl.WSubAccess]] diff --git a/src/test/scala/firrtlTests/LowerTypesSpec.scala b/src/test/scala/firrtlTests/LowerTypesSpec.scala index 9e58b74c59..da84b362c5 100644 --- a/src/test/scala/firrtlTests/LowerTypesSpec.scala +++ b/src/test/scala/firrtlTests/LowerTypesSpec.scala @@ -456,6 +456,23 @@ class LowerTypesUniquifySpec extends FirrtlFlatSpec { executeTest(input, expected) } + it should "remove index express in SubAccess" in { + val input = + s"""circuit Bug : + | module Bug : + | input in0 : UInt<1> [2][2] + | input in1 : UInt<1> [2] + | input in2 : UInt<1> [2] + | output out : UInt<1> + | out <= in0[in1[in2[0]]][in1[in2[1]]] + |""".stripMargin + val expected = Seq( + "out <= _in0_in1_in1_in2_1" + ) + + executeTest(input, expected) + } + it should "rename memories" in { val input = """circuit Test : From 228878ecb49f87497638b41086c7194cd59ea50b Mon Sep 17 00:00:00 2001 From: Kevin Laeufer Date: Wed, 2 Dec 2020 11:21:31 -0800 Subject: [PATCH 04/88] smt: add support for uninterpreted ext modules (#1994) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- .../experimental/smt/Btor2Serializer.scala | 28 +++ .../smt/FirrtlToTransitionSystem.scala | 171 ++++++++++++------ .../backends/experimental/smt/SMTExpr.scala | 11 ++ .../experimental/smt/SMTExprVisitor.scala | 5 + .../experimental/smt/SMTLibSerializer.scala | 6 + .../smt/SMTTransitionSystemEncoder.scala | 3 + .../smt/UninterpretedModuleAnnotation.scala | 86 +++++++++ .../end2end/UninterpretedModulesSpec.scala | 49 +++++ 8 files changed, 304 insertions(+), 55 deletions(-) create mode 100644 src/main/scala/firrtl/backends/experimental/smt/UninterpretedModuleAnnotation.scala create mode 100644 src/test/scala/firrtl/backends/experimental/smt/end2end/UninterpretedModulesSpec.scala diff --git a/src/main/scala/firrtl/backends/experimental/smt/Btor2Serializer.scala b/src/main/scala/firrtl/backends/experimental/smt/Btor2Serializer.scala index 4cd5c9f7a7..f96fd4e80f 100644 --- a/src/main/scala/firrtl/backends/experimental/smt/Btor2Serializer.scala +++ b/src/main/scala/firrtl/backends/experimental/smt/Btor2Serializer.scala @@ -3,12 +3,28 @@ package firrtl.backends.experimental.smt +import firrtl.backends.experimental.smt.Btor2Serializer.functionCallToArrayRead + import scala.collection.mutable private object Btor2Serializer { def serialize(sys: TransitionSystem, skipOutput: Boolean = false): Iterable[String] = { new Btor2Serializer().run(sys, skipOutput) } + + private def functionCallToArrayRead(call: BVFunctionCall): BVExpr = { + if (call.args.isEmpty) { + BVSymbol(call.name, call.width) + } else { + val index = concat(call.args) + val a = ArraySymbol(call.name, indexWidth = index.width, dataWidth = call.width) + ArrayRead(a, index) + } + } + private def concat(e: Iterable[BVExpr]): BVExpr = { + require(e.nonEmpty) + e.reduce((a, b) => BVConcat(a, b)) + } } private class Btor2Serializer private () { @@ -65,6 +81,7 @@ private class Btor2Serializer private () { case BVComparison(Compare.GreaterEqual, a, b, true) => binary("sgte", expr.width, a, b) case BVOp(op, a, b) => binary(s(op), expr.width, a, b) case BVConcat(a, b) => binary("concat", expr.width, a, b) + case call: BVFunctionCall => s(functionCallToArrayRead(call)) case ArrayRead(array, index) => line(s"read ${t(expr.width)} ${s(array)} ${s(index)}") case BVIte(cond, tru, fals) => @@ -164,6 +181,17 @@ private class Btor2Serializer private () { declare(ii.name, line(s"input ${t(ii.width)} ${ii.name}")) } + // declare uninterpreted functions a constant arrays + sys.ufs.foreach { foo => + val sym = if (foo.argWidths.isEmpty) { BVSymbol(foo.name, foo.width) } + else { + ArraySymbol(foo.name, foo.argWidths.sum, foo.width) + } + comment(foo.toString) + declare(sym.name, line(s"state ${t(sym)} ${sym.name}")) + line(s"next ${t(sym)} ${s(sym)} ${s(sym)}") + } + // define state init sys.states.foreach { st => // calculate init expression before declaring the state diff --git a/src/main/scala/firrtl/backends/experimental/smt/FirrtlToTransitionSystem.scala b/src/main/scala/firrtl/backends/experimental/smt/FirrtlToTransitionSystem.scala index aed2011af5..145b5b0f4b 100644 --- a/src/main/scala/firrtl/backends/experimental/smt/FirrtlToTransitionSystem.scala +++ b/src/main/scala/firrtl/backends/experimental/smt/FirrtlToTransitionSystem.scala @@ -40,10 +40,12 @@ private case class TransitionSystem( assumes: Set[String], asserts: Set[String], fair: Set[String], + ufs: List[BVFunctionSymbol] = List(), comments: Map[String, String] = Map(), header: Array[String] = Array()) { def serialize: String = { (Iterator(name) ++ + ufs.map(u => u.toString) ++ inputs.map(i => s"input ${i.name} : ${SMTExpr.serializeType(i)}") ++ signals.map(s => s"${s.name} : ${SMTExpr.serializeType(s.e)} = ${s.e}") ++ states.map(s => s"state ${s.sym} = [init] ${s.init} [next] ${s.next}")).mkString("\n") @@ -79,15 +81,25 @@ object FirrtlToTransitionSystem extends Transform with DependencyAPIMigration { .map(a => a.target.ref -> a.initValue) .toMap + // module look up table + val modules = circuit.modules.map(m => m.name -> m).toMap + + // collect uninterpreted module annotations + val uninterpreted = afterPreset.annotations.collect { + case a: UninterpretedModuleAnnotation => + UninterpretedModuleAnnotation.checkModule(modules(a.target.module), a) + a.target.module -> a + }.toMap + // convert the main module - val main = circuit.modules.find(_.name == circuit.main).get + val main = modules(circuit.main) val sys = main match { case x: ir.ExtModule => throw new ExtModuleException( "External modules are not supported by the SMT backend. Use yosys if you need to convert Verilog." ) case m: ir.Module => - new ModuleToTransitionSystem().run(m, presetRegs = presetRegs, memInit = memInit) + new ModuleToTransitionSystem().run(m, presetRegs = presetRegs, memInit = memInit, uninterpreted = uninterpreted) } val sortedSys = TopologicalSort.run(sys) @@ -122,12 +134,13 @@ private class MissingFeatureException(s: String) private class ModuleToTransitionSystem extends LazyLogging { def run( - m: ir.Module, - presetRegs: Set[String] = Set(), - memInit: Map[String, MemoryInitValue] = Map() + m: ir.Module, + presetRegs: Set[String] = Set(), + memInit: Map[String, MemoryInitValue] = Map(), + uninterpreted: Map[String, UninterpretedModuleAnnotation] = Map() ): TransitionSystem = { // first pass over the module to convert expressions; discover state and I/O - val scan = new ModuleScanner(makeRandom) + val scan = new ModuleScanner(makeRandom, uninterpreted) m.foreachPort(scan.onPort) m.foreachStmt(scan.onStatement) @@ -188,6 +201,10 @@ private class ModuleToTransitionSystem extends LazyLogging { val header = serializeInfo(m.info).map(InfoPrefix + _).toArray val fair = Set[String]() // as of firrtl 1.4 we do not support fairness constraints + + // collect unique functions + val ufs = scan.functionCalls.groupBy(_.name).map(_._2.head).toList + TransitionSystem( m.name, inputs.toArray, @@ -197,6 +214,7 @@ private class ModuleToTransitionSystem extends LazyLogging { constraints, bad, fair, + ufs, comments.toMap, header ) @@ -456,7 +474,10 @@ private class MemoryEncoding(makeRandom: (String, Int) => BVExpr, namespace: Nam } // performas a first pass over the module collecting all connections, wires, registers, input and outputs -private class ModuleScanner(makeRandom: (String, Int) => BVExpr) extends LazyLogging { +private class ModuleScanner( + makeRandom: (String, Int) => BVExpr, + uninterpreted: Map[String, UninterpretedModuleAnnotation]) + extends LazyLogging { import FirrtlExpressionSemantics.getWidth private[firrtl] val inputs = mutable.ArrayBuffer[BVSymbol]() @@ -473,10 +494,13 @@ private class ModuleScanner(makeRandom: (String, Int) => BVExpr) extends LazyLog private[firrtl] val assumes = mutable.ArrayBuffer[String]() // maps identifiers to their info private[firrtl] val infos = mutable.ArrayBuffer[(String, ir.Info)]() - // keeps track of unused memory (data) outputs so that we can see where they are first used - private val unusedMemOutputs = mutable.LinkedHashMap[String, Int]() + // Keeps track of (so far) unused memory (data) and uninterpreted module outputs. + // This is used in order to delay declaring them for as long as possible. + private val unusedOutputs = mutable.LinkedHashMap[String, BVExpr]() // ensure unique names for assert/assume signals private[firrtl] val namespace = Namespace() + // keep track of all uninterpreted functions called + private[firrtl] val functionCalls = mutable.ArrayBuffer[BVFunctionSymbol]() private[firrtl] def onPort(p: ir.Port): Unit = { if (isAsyncReset(p.tpe)) { @@ -508,7 +532,7 @@ private class ModuleScanner(makeRandom: (String, Int) => BVExpr) extends LazyLog case ir.DefNode(info, name, expr) => namespace.newName(name) if (!isClock(expr.tpe)) { - insertDummyAssignsForMemoryOutputs(expr) + insertDummyAssignsForUnusedOutputs(expr) infos.append(name -> info) val e = onExpression(expr, name) nodes.append(name) @@ -516,8 +540,8 @@ private class ModuleScanner(makeRandom: (String, Int) => BVExpr) extends LazyLog } case ir.DefRegister(info, name, tpe, _, reset, init) => namespace.newName(name) - insertDummyAssignsForMemoryOutputs(reset) - insertDummyAssignsForMemoryOutputs(init) + insertDummyAssignsForUnusedOutputs(reset) + insertDummyAssignsForUnusedOutputs(init) infos.append(name -> info) val width = getWidth(tpe) val resetExpr = onExpression(reset, 1, name + "_reset") @@ -529,13 +553,13 @@ private class ModuleScanner(makeRandom: (String, Int) => BVExpr) extends LazyLog val outputs = getMemOutputs(m) (getMemInputs(m) ++ outputs).foreach(memSignals.append(_)) val dataWidth = getWidth(m.dataType) - outputs.foreach(name => unusedMemOutputs(name) = dataWidth) + outputs.foreach(name => unusedOutputs(name) = BVSymbol(name, dataWidth)) memories.append(m) case ir.Connect(info, loc, expr) => if (!isGroundType(loc.tpe)) error("All connects should have been lowered to ground type!") if (!isClock(loc.tpe)) { // we ignore clock connections val name = loc.serialize - insertDummyAssignsForMemoryOutputs(expr) + insertDummyAssignsForUnusedOutputs(expr) infos.append(name -> info) connects.append((name, onExpression(expr, getWidth(loc.tpe), name))) } @@ -544,40 +568,13 @@ private class ModuleScanner(makeRandom: (String, Int) => BVExpr) extends LazyLog val name = loc.serialize infos.append(name -> info) connects.append((name, makeRandom(name + "_INVALID", getWidth(loc.tpe)))) - case ir.DefInstance(info, name, module, tpe) => - namespace.newName(name) - if (!tpe.isInstanceOf[ir.BundleType]) error(s"Instance $name of $module has an invalid type: ${tpe.serialize}") - // we treat all instances as blackboxes - logger.warn( - s"WARN: treating instance $name of $module as blackbox. " + - "Please flatten your hierarchy if you want to include submodules in the formal model." - ) - val ports = tpe.asInstanceOf[ir.BundleType].fields - // skip async reset ports - ports.filterNot(p => isAsyncReset(p.tpe)).foreach { p => - if (!p.tpe.isInstanceOf[ir.GroundType]) error(s"Instance $name of $module has an invalid port type: $p") - val isOutput = p.flip == ir.Default - val pName = name + "." + p.name - infos.append(pName -> info) - // outputs of the submodule become inputs to our module - if (isOutput) { - if (isClock(p.tpe)) { - clocks.add(pName) - } else { - inputs.append(BVSymbol(pName, getWidth(p.tpe))) - } - } else { - if (!isClock(p.tpe)) { // we ignore clock outputs - outputs.append(pName) - } - } - } + case ir.DefInstance(info, name, module, tpe) => onInstance(info, name, module, tpe) case s @ ir.Verification(op, info, _, pred, en, msg) => if (op == ir.Formal.Cover) { logger.warn(s"WARN: Cover statement was ignored: ${s.serialize}") } else { - insertDummyAssignsForMemoryOutputs(pred) - insertDummyAssignsForMemoryOutputs(en) + insertDummyAssignsForUnusedOutputs(pred) + insertDummyAssignsForUnusedOutputs(en) val name = namespace.newName(msgToName(op.toString, msg.string)) val predicate = onExpression(pred, name + "_predicate") val enabled = onExpression(en, name + "_enabled") @@ -604,6 +601,70 @@ private class ModuleScanner(makeRandom: (String, Int) => BVExpr) extends LazyLog case other => other.foreachStmt(onStatement) } + private def onInstance(info: ir.Info, name: String, module: String, tpe: ir.Type): Unit = { + namespace.newName(name) + if (!tpe.isInstanceOf[ir.BundleType]) error(s"Instance $name of $module has an invalid type: ${tpe.serialize}") + if (uninterpreted.contains(module)) { + onUninterpretedInstance(info: ir.Info, name: String, module: String, tpe: ir.Type) + } else { + // We treat all instances that aren't annotated as uninterpreted as blackboxes + // this means that their outputs could be any value, no matter what their inputs are. + logger.warn( + s"WARN: treating instance $name of $module as blackbox. " + + "Please flatten your hierarchy if you want to include submodules in the formal model." + ) + val ports = tpe.asInstanceOf[ir.BundleType].fields + // skip async reset ports + ports.filterNot(p => isAsyncReset(p.tpe)).foreach { p => + if (!p.tpe.isInstanceOf[ir.GroundType]) error(s"Instance $name of $module has an invalid port type: $p") + val isOutput = p.flip == ir.Default + val pName = name + "." + p.name + infos.append(pName -> info) + // outputs of the submodule become inputs to our module + if (isOutput) { + if (isClock(p.tpe)) { + clocks.add(pName) + } else { + inputs.append(BVSymbol(pName, getWidth(p.tpe))) + } + } else { + if (!isClock(p.tpe)) { // we ignore clock outputs + outputs.append(pName) + } + } + } + } + } + + private def onUninterpretedInstance(info: ir.Info, instanceName: String, module: String, tpe: ir.Type): Unit = { + val anno = uninterpreted(module) + + // sanity checks for ports were done already using the UninterpretedModule.checkModule function + val ports = tpe.asInstanceOf[ir.BundleType].fields + + val outputs = ports.filter(_.flip == ir.Default).map(p => BVSymbol(p.name, getWidth(p.tpe))) + val inputs = ports.filterNot(_.flip == ir.Default).map(p => BVSymbol(p.name, getWidth(p.tpe))) + + assert(anno.stateBits == 0, "TODO: implement support for uninterpreted stateful modules!") + + // for state-less (i.e. combinatorial) circuits, the outputs only depend on the inputs + val args = inputs.map(i => BVSymbol(instanceName + "." + i.name, i.width)).toList + outputs.foreach { out => + val functionName = anno.prefix + "." + out.name + val call = BVFunctionCall(functionName, args, out.width) + val wireName = instanceName + "." + out.name + // remember which functions were called + functionCalls.append(call.toSymbol) + // insert the output definition right before its first use in an attempt to get SSA + unusedOutputs(wireName) = call + // treat these outputs as wires + wires.append(wireName) + } + + // we also treat the arguments as wires + wires ++= args.map(_.name) + } + private val readInputFields = List("en", "addr") private val writeInputFields = List("en", "mask", "addr", "data") private def getMemInputs(m: ir.DefMemory): Iterable[String] = { @@ -617,27 +678,27 @@ private class ModuleScanner(makeRandom: (String, Int) => BVExpr) extends LazyLog val p = m.name + "." m.readers.map(r => p + r + ".data") } - // inserts a dummy assign right before a memory output is used for the first time + // inserts a dummy assign right before a memory/uninterpreted module output is used for the first time // example: // m.r.data <= m.r.data ; this is the dummy assign // test <= m.r.data ; this is the first use of m.r.data - private def insertDummyAssignsForMemoryOutputs(next: ir.Expression): Unit = if (unusedMemOutputs.nonEmpty) { - implicit val uses = mutable.ArrayBuffer[String]() - findUnusedMemoryOutputUse(next) + private def insertDummyAssignsForUnusedOutputs(next: ir.Expression): Unit = if (unusedOutputs.nonEmpty) { + val uses = mutable.ArrayBuffer[String]() + findUnusedOutputUse(next)(uses) if (uses.nonEmpty) { val useSet = uses.toSet - unusedMemOutputs.foreach { - case (name, width) => - if (useSet.contains(name)) connects.append(name -> BVSymbol(name, width)) + unusedOutputs.foreach { + case (name, value) => + if (useSet.contains(name)) connects.append(name -> value) } - useSet.foreach(name => unusedMemOutputs.remove(name)) + useSet.foreach(name => unusedOutputs.remove(name)) } } - private def findUnusedMemoryOutputUse(e: ir.Expression)(implicit uses: mutable.ArrayBuffer[String]): Unit = e match { + private def findUnusedOutputUse(e: ir.Expression)(implicit uses: mutable.ArrayBuffer[String]): Unit = e match { case s: ir.SubField => val name = s.serialize - if (unusedMemOutputs.contains(name)) uses.append(name) - case other => other.foreachExpr(findUnusedMemoryOutputUse) + if (unusedOutputs.contains(name)) uses.append(name) + case other => other.foreachExpr(findUnusedOutputUse) } private case class Context(baseName: String) extends TranslationContext { diff --git a/src/main/scala/firrtl/backends/experimental/smt/SMTExpr.scala b/src/main/scala/firrtl/backends/experimental/smt/SMTExpr.scala index 6369200625..0fc507e6bb 100644 --- a/src/main/scala/firrtl/backends/experimental/smt/SMTExpr.scala +++ b/src/main/scala/firrtl/backends/experimental/smt/SMTExpr.scala @@ -138,6 +138,17 @@ private case class BVIte(cond: BVExpr, tru: BVExpr, fals: BVExpr) extends BVExpr override def children: List[BVExpr] = List(cond, tru, fals) } +/** apply bv arguments to a function which returns a result of bit vector type */ +private case class BVFunctionCall(name: String, args: List[BVExpr], width: Int) extends BVExpr { + override def children = args + def toSymbol: BVFunctionSymbol = BVFunctionSymbol(name, args.map(_.width), width) + override def toString: String = args.mkString(name + "(", ", ", ")") +} + +private case class BVFunctionSymbol(name: String, argWidths: List[Int], width: Int) { + override def toString: String = s"$name : " + (argWidths :+ width).map(w => s"bv<$w>").mkString(" -> ") +} + private sealed trait ArrayExpr extends SMTExpr { val indexWidth: Int; val dataWidth: Int } private case class ArraySymbol(name: String, indexWidth: Int, dataWidth: Int) extends ArrayExpr with SMTSymbol { assert(!name.contains("|"), s"Invalid id $name contains escape character `|`") diff --git a/src/main/scala/firrtl/backends/experimental/smt/SMTExprVisitor.scala b/src/main/scala/firrtl/backends/experimental/smt/SMTExprVisitor.scala index 19f1de8483..13ed8bdd61 100644 --- a/src/main/scala/firrtl/backends/experimental/smt/SMTExprVisitor.scala +++ b/src/main/scala/firrtl/backends/experimental/smt/SMTExprVisitor.scala @@ -54,6 +54,11 @@ private object SMTExprVisitor { case old @ BVIte(a, b, c) => val (nA, nB, nC) = (map(a, bv, ar), map(b, bv, ar), map(c, bv, ar)) bv(if (nA.eq(a) && nB.eq(b) && nC.eq(c)) old else BVIte(nA, nB, nC)) + // n-ary + case old @ BVFunctionCall(name, args, width) => + val nArgs = args.map(a => map(a, bv, ar)) + val noneNew = nArgs.zip(args).forall { case (n, o) => n.eq(o) } + bv(if (noneNew) old else BVFunctionCall(name, nArgs, width)) } private def map(e: ArrayExpr, bv: BVFun, ar: ArrayFun): ArrayExpr = e match { diff --git a/src/main/scala/firrtl/backends/experimental/smt/SMTLibSerializer.scala b/src/main/scala/firrtl/backends/experimental/smt/SMTLibSerializer.scala index 7bc0a077e9..75bde09c65 100644 --- a/src/main/scala/firrtl/backends/experimental/smt/SMTLibSerializer.scala +++ b/src/main/scala/firrtl/backends/experimental/smt/SMTLibSerializer.scala @@ -24,6 +24,11 @@ private object SMTLibSerializer { case a: ArrayExpr => serializeArrayType(a.indexWidth, a.dataWidth) } + def declareFunction(foo: BVFunctionSymbol): SMTCommand = { + val args = foo.argWidths.map(serializeBitVectorType) + DeclareFunction(BVSymbol(foo.name, foo.width), args) + } + private def serialize(e: BVExpr): String = e match { case BVLiteral(value, width) => val mask = (BigInt(1) << width) - 1 @@ -74,6 +79,7 @@ private object SMTLibSerializer { case BVConcat(a, b) => s"(concat ${asBitVector(a)} ${asBitVector(b)})" case ArrayRead(array, index) => s"(select ${serialize(array)} ${asBitVector(index)})" case BVIte(cond, tru, fals) => s"(ite ${serialize(cond)} ${serialize(tru)} ${serialize(fals)})" + case BVFunctionCall(name, args, _) => args.map(serialize).mkString(s"($name ", " ", ")") case BVRawExpr(serialized, _) => serialized } diff --git a/src/main/scala/firrtl/backends/experimental/smt/SMTTransitionSystemEncoder.scala b/src/main/scala/firrtl/backends/experimental/smt/SMTTransitionSystemEncoder.scala index f6d9a26f32..d35fe139d8 100644 --- a/src/main/scala/firrtl/backends/experimental/smt/SMTTransitionSystemEncoder.scala +++ b/src/main/scala/firrtl/backends/experimental/smt/SMTTransitionSystemEncoder.scala @@ -20,6 +20,9 @@ private object SMTTransitionSystemEncoder { // emit header as comments cmds ++= sys.header.map(Comment) + // declare uninterpreted functions used in model + cmds ++= sys.ufs.map(SMTLibSerializer.declareFunction) + // declare state type val stateType = id(name + "_s") cmds += DeclareUninterpretedSort(stateType) diff --git a/src/main/scala/firrtl/backends/experimental/smt/UninterpretedModuleAnnotation.scala b/src/main/scala/firrtl/backends/experimental/smt/UninterpretedModuleAnnotation.scala new file mode 100644 index 0000000000..c7442f6941 --- /dev/null +++ b/src/main/scala/firrtl/backends/experimental/smt/UninterpretedModuleAnnotation.scala @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: Apache-2.0 +// Author: Kevin Laeufer + +package firrtl.backends.experimental.smt + +import firrtl.annotations._ +import firrtl.ir +import firrtl.passes.PassException + +/** ExtModules annotated as UninterpretedModule will be modelled as + * UninterpretedFunction (SMTLib) or constant arrays (btor2). + * This can be useful when trying to abstract over a function that the + * SMT solver or model checker is struggling with. + * + * E.g., one could declare an abstract 64bit multiplier like this: + * ``` + * extmodule Mul64 : + * input a : UInt<64> + * input b : UInt<64> + * output r : UInt<64> + * ``` + * Now instead of using Chisel to actually implement a multiplication circuit + * we can instantiate this Mul64 module twice: Once in our implementation + * and once for our correctness property that might specify how the + * multiply instruction is supposed to be executed on our CPU. + * Now instead of having to prove equivalence of multiplication circuits, the + * solver only has to make sure that the connections to the multiplier are correct, + * since if `a` and `b` are the same on both instances of `Mul64`, then the `r` output + * will also be the same. This is a much easier problem and will result in much faster + * solving due to manual abstraction. + * + * When [[stateBits]] is 0, we model the module as purely combinatorial circuit and + * thus expect there to be no clock wire going into the module. + * Every output is thus a function of all inputs of the module. + * + * When [[stateBits]] is an N greater than zero, we will model the module as having an abstract state of width N. + * Thus on every clock transition the abstract state is updated and all outputs will take the state + * as well as the current inputs as arguments. + * TODO: Support for stateful circuits is work in progress. + * + * All output functions well be prefixed with [[prefix]] and end in the name of the output pin. + * It is the users responsibility to ensure that all function names will be unique by choosing apropriate + * prefixes. + * + * The annotation is consumed by the [[FirrtlToTransitionSystem]] pass. + */ +case class UninterpretedModuleAnnotation(target: ModuleTarget, prefix: String, stateBits: Int = 0) + extends SingleTargetAnnotation[ModuleTarget] { + require(stateBits >= 0, "negative number of bits is forbidden") + if (stateBits > 0) throw new NotImplementedError("TODO: support for stateful circuits is not implemented yet!") + override def duplicate(n: ModuleTarget) = copy(n) +} + +object UninterpretedModuleAnnotation { + + /** checks to see whether the annotation module can actually be abstracted. Use *after* LowerTypes! */ + def checkModule(m: ir.DefModule, anno: UninterpretedModuleAnnotation): Unit = m match { + case _: ir.Module => + throw new UninterpretedModuleException(s"UninterpretedModuleAnnotation can only be used with extmodule! $anno") + case m: ir.ExtModule => + val clockInputs = m.ports.collect { case p @ ir.Port(_, _, ir.Input, ir.ClockType) => p.name } + val clockOutput = m.ports.collect { case p @ ir.Port(_, _, ir.Output, ir.ClockType) => p.name } + val asyncResets = m.ports.collect { case p @ ir.Port(_, _, _, ir.AsyncResetType) => p.name } + if (clockOutput.nonEmpty) { + throw new UninterpretedModuleException( + s"We do not support clock outputs for uninterpreted modules! $clockOutput" + ) + } + if (asyncResets.nonEmpty) { + throw new UninterpretedModuleException( + s"We do not support async reset I/O for uninterpreted modules! $asyncResets" + ) + } + if (anno.stateBits == 0) { + if (clockInputs.nonEmpty) { + throw new UninterpretedModuleException(s"A combinatorial module may not have any clock inputs! $clockInputs") + } + } else { + if (clockInputs.size != 1) { + throw new UninterpretedModuleException(s"A stateful module must have exactly one clock input! $clockInputs") + } + } + } +} + +private class UninterpretedModuleException(s: String) extends PassException(s) diff --git a/src/test/scala/firrtl/backends/experimental/smt/end2end/UninterpretedModulesSpec.scala b/src/test/scala/firrtl/backends/experimental/smt/end2end/UninterpretedModulesSpec.scala new file mode 100644 index 0000000000..e4404d1007 --- /dev/null +++ b/src/test/scala/firrtl/backends/experimental/smt/end2end/UninterpretedModulesSpec.scala @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: Apache-2.0 + +package firrtl.backends.experimental.smt.end2end + +import firrtl.annotations.CircuitTarget +import firrtl.backends.experimental.smt.UninterpretedModuleAnnotation + +class UninterpretedModulesSpec extends EndToEndSMTBaseSpec { + + private def testCircuit(assumption: String = ""): String = { + s"""circuit UF00: + | module UF00: + | input clk: Clock + | input a: UInt<128> + | input b: UInt<128> + | input c: UInt<128> + | + | inst m0 of Magic + | m0.a <= a + | m0.b <= b + | + | inst m1 of Magic + | m1.a <= a + | m1.b <= c + | + | assert(clk, eq(m0.r, m1.r), UInt(1), "m0.r == m1.r") + | $assumption + | extmodule Magic: + | input a: UInt<128> + | input b: UInt<128> + | output r: UInt<128> + |""".stripMargin + } + private val magicAnno = UninterpretedModuleAnnotation(CircuitTarget("UF00").module("Magic"), "magic", 0) + + "two instances of the same uninterpreted module" should "give the same result when given the same inputs" taggedAs (RequiresZ3) in { + val assumeTheSame = """assume(clk, eq(b,c), UInt(1), "b == c")""" + test(testCircuit(assumeTheSame), MCSuccess, 1, "inputs are the same ==> outputs are the same", Seq(magicAnno)) + } + "two instances of the same uninterpreted module" should "not always give the same result when given potentially different inputs" taggedAs (RequiresZ3) in { + test( + testCircuit(), + MCFail(0), + 1, + "inputs are not necessarily the same ==> outputs can be different", + Seq(magicAnno) + ) + } +} From 594f783b7faf70cf7703bcb11b1d0654658c6f67 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Wed, 2 Dec 2020 17:20:43 -0800 Subject: [PATCH 05/88] Restore publish settings to before sbt-ci-release (#1999) sbt-ci-release changes the commands required to publish to Sonatype. While this may be a desirable change at some point, it is inconsistent with other repos. Reverting for the time being. --- build.sbt | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index d39f99dc29..557a6fe454 100644 --- a/build.sbt +++ b/build.sbt @@ -90,7 +90,7 @@ lazy val antlrSettings = Seq( ) lazy val publishSettings = Seq( - // publishMavenStyle and publishTo handled by sbt-ci-release + publishMavenStyle := true, publishArtifact in Test := false, pomIncludeRepository := { x => false }, // Don't add 'scm' elements if we have a git.remoteRepo definition, @@ -114,6 +114,15 @@ lazy val publishSettings = Seq( http://www.eecs.berkeley.edu/~jrb/ , + publishTo := { + val v = version.value + val nexus = "https://oss.sonatype.org/" + if (v.trim.endsWith("SNAPSHOT")) { + Some("snapshots" at nexus + "content/repositories/snapshots") + } else { + Some("releases" at nexus + "service/local/staging/deploy/maven2") + } + } ) From 862aaec44d25824aa5ee090a4c3e845ec0069134 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Fri, 4 Dec 2020 12:29:51 -0800 Subject: [PATCH 06/88] Remove explicit pom scm from build.sbt (#2004) This is now set by sbt-ci-release --- build.sbt | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/build.sbt b/build.sbt index 557a6fe454..14aafe1b3c 100644 --- a/build.sbt +++ b/build.sbt @@ -93,8 +93,7 @@ lazy val publishSettings = Seq( publishMavenStyle := true, publishArtifact in Test := false, pomIncludeRepository := { x => false }, - // Don't add 'scm' elements if we have a git.remoteRepo definition, - // but since we don't (with the removal of ghpages), add them in below. + // scm is set by sbt-ci-release pomExtra := http://chisel.eecs.berkeley.edu/ @@ -103,10 +102,6 @@ lazy val publishSettings = Seq( repo - - https://github.com/freechipsproject/firrtl.git - scm:git:github.com/freechipsproject/firrtl.git - jackbackrack From b02bea66a9430a85d4782cc50f6964a86a4ae583 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Mon, 7 Dec 2020 11:41:03 -0800 Subject: [PATCH 07/88] Fix Mergify badge in README (#1974) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 70921ed521..d53dd0250b 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ [![Mergify Status][mergify-status]][mergify] [mergify]: https://mergify.io -[mergify-status]: https://gh.mergify.io/badges/:freechipsproject/:firrtl.png?style=cut +[mergify-status]: https://img.shields.io/endpoint.svg?url=https://gh.mergify.io/badges/chipsalliance/firrtl&style=flat #### Flexible Internal Representation for RTL From 5fb8845fedb39ad73a70e5df372cdd7ab69b89f5 Mon Sep 17 00:00:00 2001 From: XinJun Ma Date: Fri, 11 Dec 2020 01:22:48 +0800 Subject: [PATCH 08/88] Add newline in the end of LoFIRRTL file (#2015) --- src/main/scala/firrtl/ir/Serializer.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/scala/firrtl/ir/Serializer.scala b/src/main/scala/firrtl/ir/Serializer.scala index 4a1bfbedf3..7fb30f1957 100644 --- a/src/main/scala/firrtl/ir/Serializer.scala +++ b/src/main/scala/firrtl/ir/Serializer.scala @@ -240,6 +240,7 @@ object Serializer { newLineAndIndent(1); s(modules.head)(b, indent + 1) modules.drop(1).foreach { m => newLineNoIndent(); newLineAndIndent(1); s(m)(b, indent + 1) } } + newLineNoIndent() } // serialize constraints From 93869ccec89aa9739b6fe9f0e3bd62ae8cf155cd Mon Sep 17 00:00:00 2001 From: Megan Wachs Date: Fri, 11 Dec 2020 12:09:03 -0800 Subject: [PATCH 09/88] fix scaladoc for ReferenceTarget (#2014) --- src/main/scala/firrtl/annotations/Target.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/firrtl/annotations/Target.scala b/src/main/scala/firrtl/annotations/Target.scala index 137d070e15..8e84a269e3 100644 --- a/src/main/scala/firrtl/annotations/Target.scala +++ b/src/main/scala/firrtl/annotations/Target.scala @@ -612,7 +612,7 @@ case class ModuleTarget(circuit: String, module: String) extends IsModule { } /** Target pointing to a declared named component in a [[firrtl.ir.DefModule]] - * This includes: [[firrtl.ir.Port]], [[firrtl.ir.DefWire]], [[firrtl.ir.DefRegister]], [[firrtl.ir.DefInstance]], + * This includes: [[firrtl.ir.Port]], [[firrtl.ir.DefWire]], [[firrtl.ir.DefRegister]], * [[firrtl.ir.DefMemory]], [[firrtl.ir.DefNode]] * @param circuit Name of the encapsulating circuit * @param module Name of the root module of this reference From 15013df6f6ac2dafeb35d7ed15cf95c7ac8a5bef Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Tue, 15 Dec 2020 16:41:53 -0800 Subject: [PATCH 10/88] Improve performance of LowerTypes renaming (#2024) This is done by having LowerTypes uses two RenameMaps instead of one for each module. There is one for renaming instance paths, and one for renaming everything within modules. Also add some utilities: * TargetUtils for dealing with InstanceTargets * RenameMap.fromInstanceRenames --- src/main/scala/firrtl/RenameMap.scala | 54 ++++++++++++++++++ .../firrtl/annotations/TargetUtils.scala | 46 +++++++++++++++ src/main/scala/firrtl/passes/LowerTypes.scala | 42 ++++++++------ .../scala/firrtl/RenameMapPrivateSpec.scala | 39 +++++++++++++ .../scala/firrtl/passes/LowerTypesSpec.scala | 32 ++++++++--- .../scala/firrtlTests/RenameMapSpec.scala | 2 + .../annotationTests/TargetUtilsSpec.scala | 56 +++++++++++++++++++ 7 files changed, 246 insertions(+), 25 deletions(-) create mode 100644 src/main/scala/firrtl/annotations/TargetUtils.scala create mode 100644 src/test/scala/firrtl/RenameMapPrivateSpec.scala create mode 100644 src/test/scala/firrtlTests/annotationTests/TargetUtilsSpec.scala diff --git a/src/main/scala/firrtl/RenameMap.scala b/src/main/scala/firrtl/RenameMap.scala index df98f72f2a..82c00ca5c7 100644 --- a/src/main/scala/firrtl/RenameMap.scala +++ b/src/main/scala/firrtl/RenameMap.scala @@ -4,7 +4,9 @@ package firrtl import annotations._ import firrtl.RenameMap.IllegalRenameException +import firrtl.analyses.InstanceKeyGraph import firrtl.annotations.TargetToken.{Field, Index, Instance, OfModule} +import TargetUtils.{instKeyPathToTarget, unfoldInstanceTargets} import scala.collection.mutable @@ -21,6 +23,58 @@ object RenameMap { rm } + /** RenameMap factory for simple renaming of instances + * + * @param graph [[InstanceKeyGraph]] from *before* renaming + * @param renames Mapping of old instance name to new within Modules + */ + private[firrtl] def fromInstanceRenames( + graph: InstanceKeyGraph, + renames: Map[OfModule, Map[Instance, Instance]] + ): RenameMap = { + def renameAll(it: InstanceTarget): InstanceTarget = { + var prevMod = OfModule(it.module) + val pathx = it.path.map { + case (inst, of) => + val instx = renames + .get(prevMod) + .flatMap(_.get(inst)) + .getOrElse(inst) + prevMod = of + instx -> of + } + // Sanity check, the last one should always be a rename (or we wouldn't be calling this method) + val instx = renames(prevMod)(Instance(it.instance)) + it.copy(path = pathx, instance = instx.value) + } + val underlying = new mutable.HashMap[CompleteTarget, Seq[CompleteTarget]] + val instOf: String => Map[String, String] = + graph.getChildInstances.toMap + // Laziness here is desirable, we only access each key once, some we don't access + .mapValues(_.map(k => k.name -> k.module).toMap) + for ((OfModule(module), instMapping) <- renames) { + val modLookup = instOf(module) + val parentInstances = graph.findInstancesInHierarchy(module) + for { + // For every instance of the Module where the renamed instance resides + parent <- parentInstances + parentTarget = instKeyPathToTarget(parent) + // Create the absolute InstanceTarget to be renamed + (Instance(from), _) <- instMapping // The to is given by renameAll + instMod = modLookup(from) + fromTarget = parentTarget.instOf(from, instMod) + // Ensure all renames apply to the InstanceTarget + toTarget = renameAll(fromTarget) + // RenameMap only allows 1 hit when looking up InstanceTargets, so rename all possible + // paths to this instance + (fromx, tox) <- unfoldInstanceTargets(fromTarget).zip(unfoldInstanceTargets(toTarget)) + } yield { + underlying(fromx) = List(tox) + } + } + new RenameMap(underlying) + } + /** Initialize a new RenameMap */ def apply(): RenameMap = new RenameMap diff --git a/src/main/scala/firrtl/annotations/TargetUtils.scala b/src/main/scala/firrtl/annotations/TargetUtils.scala new file mode 100644 index 0000000000..164c430b55 --- /dev/null +++ b/src/main/scala/firrtl/annotations/TargetUtils.scala @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: Apache-2.0 + +package firrtl.annotations + +import firrtl._ +import firrtl.analyses.InstanceKeyGraph +import firrtl.analyses.InstanceKeyGraph.InstanceKey +import firrtl.annotations.TargetToken._ + +object TargetUtils { + + /** Turns an instance path into a corresponding [[IsModule]] + * + * @note First InstanceKey is treated as the [[CircuitTarget]] + * @param path Instance path + * @param start Module in instance path to be starting [[ModuleTarget]] + * @return [[IsModule]] corresponding to Instance path + */ + def instKeyPathToTarget(path: Seq[InstanceKey], start: Option[String] = None): IsModule = { + val head = path.head + val startx = start.getOrElse(head.module) + val top: IsModule = CircuitTarget(head.module).module(startx) // ~Top|Start + val pathx = path.dropWhile(_.module != startx) + if (pathx.isEmpty) top + else pathx.tail.foldLeft(top) { case (acc, key) => acc.instOf(key.name, key.module) } + } + + /** Calculates all [[InstanceTarget]]s that refer to the given [[IsModule]] + * + * {{{ + * ~Top|Top/a:A/b:B/c:C unfolds to: + * * ~Top|Top/a:A/b:B/c:C + * * ~Top|A/b:B/c:C + * * ~Top|B/c:C + * }}} + * @note [[ModuleTarget]] arguments return an empty Iterable + */ + def unfoldInstanceTargets(ismod: IsModule): Iterable[InstanceTarget] = { + // concretely use List which is fast in practice + def rec(im: IsModule): List[InstanceTarget] = im match { + case inst: InstanceTarget => inst :: rec(inst.stripHierarchy(1)) + case _ => Nil + } + rec(ismod) + } +} diff --git a/src/main/scala/firrtl/passes/LowerTypes.scala b/src/main/scala/firrtl/passes/LowerTypes.scala index 592caf5d17..0bd44a8ca3 100644 --- a/src/main/scala/firrtl/passes/LowerTypes.scala +++ b/src/main/scala/firrtl/passes/LowerTypes.scala @@ -8,8 +8,10 @@ import firrtl.annotations.{ MemoryInitAnnotation, MemoryRandomInitAnnotation, ModuleTarget, - ReferenceTarget + ReferenceTarget, + TargetToken } +import TargetToken.{Instance, OfModule} import firrtl.{ CircuitForm, CircuitState, @@ -73,16 +75,18 @@ object LowerTypes extends Transform with DependencyAPIMigration { val memInitByModule = memInitAnnos.map(_.asInstanceOf[MemoryInitAnnotation]).groupBy(_.target.encapsulatingModule) val c = CircuitTarget(state.circuit.main) - val resultAndRenames = state.circuit.modules.map(m => onModule(c, m, memInitByModule.getOrElse(m.name, Seq()))) + val refRenameMap = RenameMap() + val resultAndRenames = + state.circuit.modules.map(m => onModule(c, m, memInitByModule.getOrElse(m.name, Seq()), refRenameMap)) val result = state.circuit.copy(modules = resultAndRenames.map(_._1)) // memory init annotations could have been modified val newAnnos = otherAnnos ++ resultAndRenames.flatMap(_._3) - // chain module renames in topological order - val moduleRenames = resultAndRenames.map { case (m, r, _) => m.name -> r }.toMap - val moduleOrderBottomUp = InstanceKeyGraph(result).moduleOrder.reverseIterator - val renames = moduleOrderBottomUp.map(m => moduleRenames(m.name)).reduce((a, b) => a.andThen(b)) + // Build RenameMap for instances + val moduleRenames = resultAndRenames.map { case (m, r, _) => OfModule(m.name) -> r }.toMap + val instRenameMap = RenameMap.fromInstanceRenames(InstanceKeyGraph(state.circuit), moduleRenames) + val renames = instRenameMap.andThen(refRenameMap) state.copy(circuit = result, renames = Some(renames), annotations = newAnnos) } @@ -90,9 +94,9 @@ object LowerTypes extends Transform with DependencyAPIMigration { private def onModule( c: CircuitTarget, m: DefModule, - memoryInit: Seq[MemoryInitAnnotation] - ): (DefModule, RenameMap, Seq[MemoryInitAnnotation]) = { - val renameMap = RenameMap() + memoryInit: Seq[MemoryInitAnnotation], + renameMap: RenameMap + ): (DefModule, Map[Instance, Instance], Seq[MemoryInitAnnotation]) = { val ref = c.module(m.name) // first we lower the ports in order to ensure that their names are independent of the module body @@ -105,7 +109,9 @@ object LowerTypes extends Transform with DependencyAPIMigration { implicit val memInit: Seq[MemoryInitAnnotation] = memoryInit val newMod = mLoweredPorts.mapStmt(onStatement) - (newMod, renameMap, memInit) + val instRenames = symbols.getInstanceRenames.toMap + + (newMod, instRenames, memInit) } // We lower ports in a separate pass in order to ensure that statements inside the module do not influence port names. @@ -221,6 +227,7 @@ private class LoweringTable( private val namespace = mutable.HashSet[String]() ++ table.getSymbolNames // Serialized old access string to new ground type reference. private val nameToExprs = mutable.HashMap[String, Seq[RefLikeExpression]]() ++ portNameToExprs + private val instRenames = mutable.ListBuffer[(Instance, Instance)]() def lower(mem: DefMemory): Seq[DefMemory] = { val (mems, refs) = DestructTypes.destructMemory(m, mem, namespace, renameMap, portNames) @@ -228,7 +235,7 @@ private class LoweringTable( mems } def lower(inst: DefInstance): DefInstance = { - val (newInst, refs) = DestructTypes.destructInstance(m, inst, namespace, renameMap, portNames) + val (newInst, refs) = DestructTypes.destructInstance(m, inst, namespace, instRenames, portNames) nameToExprs ++= refs.map { case (name, r) => name -> List(r) } newInst } @@ -245,6 +252,7 @@ private class LoweringTable( } def getReferences(expr: RefLikeExpression): Seq[RefLikeExpression] = nameToExprs(serialize(expr)) + def getInstanceRenames: List[(Instance, Instance)] = instRenames.toList // We could just use FirrtlNode.serialize here, but we want to make sure there are not SubAccess nodes left. private def serialize(expr: RefLikeExpression): String = expr match { @@ -296,11 +304,11 @@ private object DestructTypes { * instead of a flat Reference when turning them into access expressions. */ def destructInstance( - m: ModuleTarget, - instance: DefInstance, - namespace: Namespace, - renameMap: RenameMap, - reserved: Set[String] + m: ModuleTarget, + instance: DefInstance, + namespace: Namespace, + instRenames: mutable.ListBuffer[(Instance, Instance)], + reserved: Set[String] ): (DefInstance, Seq[(String, SubField)]) = { val (rename, _) = uniquify(Field(instance.name, Default, instance.tpe), namespace, reserved) val newName = rename.map(_.name).getOrElse(instance.name) @@ -314,7 +322,7 @@ private object DestructTypes { // rename all references to the instance if necessary if (newName != instance.name) { - renameMap.record(m.instOf(instance.name, instance.module), m.instOf(newName, instance.module)) + instRenames += Instance(instance.name) -> Instance(newName) } // The ports do not need to be explicitly renamed here. They are renamed when the module ports are lowered. diff --git a/src/test/scala/firrtl/RenameMapPrivateSpec.scala b/src/test/scala/firrtl/RenameMapPrivateSpec.scala new file mode 100644 index 0000000000..d735e6c876 --- /dev/null +++ b/src/test/scala/firrtl/RenameMapPrivateSpec.scala @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: Apache-2.0 + +package firrtl + +import firrtl.annotations.Target +import firrtl.annotations.TargetToken.{Instance, OfModule} +import firrtl.analyses.InstanceKeyGraph +import firrtl.testutils.FirrtlFlatSpec + +class RenameMapPrivateSpec extends FirrtlFlatSpec { + "RenameMap.fromInstanceRenames" should "handle instance renames" in { + def tar(str: String): Target = Target.deserialize(str) + val circuit = parse( + """circuit Top : + | module Bar : + | skip + | module Foo : + | inst bar of Bar + | module Top : + | inst foo1 of Foo + | inst foo2 of Foo + | inst bar of Bar + |""".stripMargin + ) + val graph = InstanceKeyGraph(circuit) + val renames = Map( + OfModule("Foo") -> Map(Instance("bar") -> Instance("bbb")), + OfModule("Top") -> Map(Instance("foo1") -> Instance("ffff")) + ) + val rm = RenameMap.fromInstanceRenames(graph, renames) + rm.get(tar("~Top|Top/foo1:Foo")) should be(Some(Seq(tar("~Top|Top/ffff:Foo")))) + rm.get(tar("~Top|Top/foo2:Foo")) should be(None) + // Check of nesting + rm.get(tar("~Top|Top/foo1:Foo/bar:Bar")) should be(Some(Seq(tar("~Top|Top/ffff:Foo/bbb:Bar")))) + rm.get(tar("~Top|Top/foo2:Foo/bar:Bar")) should be(Some(Seq(tar("~Top|Top/foo2:Foo/bbb:Bar")))) + rm.get(tar("~Top|Foo/bar:Bar")) should be(Some(Seq(tar("~Top|Foo/bbb:Bar")))) + rm.get(tar("~Top|Top/bar:Bar")) should be(None) + } +} diff --git a/src/test/scala/firrtl/passes/LowerTypesSpec.scala b/src/test/scala/firrtl/passes/LowerTypesSpec.scala index 70fa51fdc1..7ca9854496 100644 --- a/src/test/scala/firrtl/passes/LowerTypesSpec.scala +++ b/src/test/scala/firrtl/passes/LowerTypesSpec.scala @@ -2,10 +2,13 @@ package firrtl.passes import firrtl.annotations.{CircuitTarget, IsMember} +import firrtl.annotations.TargetToken.{Instance, OfModule} +import firrtl.analyses.InstanceKeyGraph import firrtl.{CircuitState, RenameMap, Utils} import firrtl.options.Dependency import firrtl.stage.TransformManager import firrtl.stage.TransformManager.TransformDependency +import firrtl.testutils.FirrtlMatchers import org.scalatest.flatspec.AnyFlatSpec /** Unit test style tests for [[LowerTypes]]. @@ -228,22 +231,35 @@ class LowerTypesRenamingSpec extends AnyFlatSpec { } /** Instances are a special case since they do not get completely destructed but instead become a 1-deep bundle. */ -class LowerTypesOfInstancesSpec extends AnyFlatSpec { +class LowerTypesOfInstancesSpec extends AnyFlatSpec with FirrtlMatchers { import LowerTypesSpecUtils._ private case class Lower(inst: firrtl.ir.DefInstance, fields: Seq[String], renameMap: RenameMap) private val m = CircuitTarget("m").module("m") + private val igraph = InstanceKeyGraph( + parse( + """circuit m: + | module c: + | skip + | module m: + | inst i of c + |""".stripMargin + ) + ) def resultToFieldSeq(res: Seq[(String, firrtl.ir.SubField)]): Seq[String] = res.map(_._2).map(r => s"${r.name} : ${r.tpe.serialize}") private def lower( - n: String, - tpe: String, - module: String, - namespace: Set[String], - renames: RenameMap = RenameMap() + n: String, + tpe: String, + module: String, + namespace: Set[String], + otherRenames: RenameMap = RenameMap() ): Lower = { val ref = firrtl.ir.DefInstance(firrtl.ir.NoInfo, n, module, parseType(tpe)) val mutableSet = scala.collection.mutable.HashSet[String]() ++ namespace - val (newInstance, res) = DestructTypes.destructInstance(m, ref, mutableSet, renames, Set()) + val instRenames = scala.collection.mutable.ListBuffer[(Instance, Instance)]() + val (newInstance, res) = DestructTypes.destructInstance(m, ref, mutableSet, instRenames, Set()) + val instMap = Map(OfModule("m") -> instRenames.toMap) + val renames = RenameMap.fromInstanceRenames(igraph, instMap).andThen(otherRenames) Lower(newInstance, resultToFieldSeq(res), renames) } private def get(l: Lower, m: IsMember): Set[IsMember] = l.renameMap.get(m).get.toSet @@ -305,7 +321,7 @@ class LowerTypesOfInstancesSpec extends AnyFlatSpec { assert(get(l, i) == Set(i_)) // the ports renaming is also noted - val r = portRenames.andThen(otherRenames) + val r = portRenames.andThen(l.renameMap) assert(r.get(i.ref("b")).get == Seq(i_.ref("b__c"))) assert(r.get(i.ref("b").field("c")).get == Seq(i_.ref("b__c"))) assert(r.get(i.ref("b_c")).get == Seq(i_.ref("b_c"))) diff --git a/src/test/scala/firrtlTests/RenameMapSpec.scala b/src/test/scala/firrtlTests/RenameMapSpec.scala index 29466c72ed..bebeb0bf8e 100644 --- a/src/test/scala/firrtlTests/RenameMapSpec.scala +++ b/src/test/scala/firrtlTests/RenameMapSpec.scala @@ -5,6 +5,8 @@ package firrtlTests import firrtl.RenameMap import firrtl.RenameMap.IllegalRenameException import firrtl.annotations._ +import firrtl.annotations.TargetToken.{Instance, OfModule} +import firrtl.analyses.InstanceKeyGraph import firrtl.testutils._ class RenameMapSpec extends FirrtlFlatSpec { diff --git a/src/test/scala/firrtlTests/annotationTests/TargetUtilsSpec.scala b/src/test/scala/firrtlTests/annotationTests/TargetUtilsSpec.scala new file mode 100644 index 0000000000..38266efeb1 --- /dev/null +++ b/src/test/scala/firrtlTests/annotationTests/TargetUtilsSpec.scala @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: Apache-2.0 + +package firrtlTests.annotationTests + +import firrtl.analyses.InstanceKeyGraph.InstanceKey +import firrtl.annotations._ +import firrtl.annotations.TargetToken._ +import firrtl.annotations.TargetUtils._ +import firrtl.testutils.FirrtlFlatSpec + +class TargetUtilsSpec extends FirrtlFlatSpec { + + behavior.of("instKeyPathToTarget") + + it should "create a ModuleTarget for the top module" in { + val input = InstanceKey("Top", "Top") :: Nil + val expected = ModuleTarget("Top", "Top") + instKeyPathToTarget(input) should be(expected) + } + + it should "create absolute InstanceTargets" in { + val input = InstanceKey("Top", "Top") :: + InstanceKey("foo", "Foo") :: + InstanceKey("bar", "Bar") :: + Nil + val expected = InstanceTarget("Top", "Top", Seq((Instance("foo"), OfModule("Foo"))), "bar", "Bar") + instKeyPathToTarget(input) should be(expected) + } + + it should "support starting somewhere down the path" in { + val input = InstanceKey("Top", "Top") :: + InstanceKey("foo", "Foo") :: + InstanceKey("bar", "Bar") :: + InstanceKey("fizz", "Fizz") :: + Nil + val expected = InstanceTarget("Top", "Bar", Seq(), "fizz", "Fizz") + instKeyPathToTarget(input, Some("Bar")) should be(expected) + } + + behavior.of("unfoldInstanceTargets") + + it should "return nothing for ModuleTargets" in { + val input = ModuleTarget("Top", "Foo") + unfoldInstanceTargets(input) should be(Iterable()) + } + + it should "return all other InstanceTargets to the same instance" in { + val input = ModuleTarget("Top", "Top").instOf("foo", "Foo").instOf("bar", "Bar").instOf("fizz", "Fizz") + val expected = + input :: + ModuleTarget("Top", "Foo").instOf("bar", "Bar").instOf("fizz", "Fizz") :: + ModuleTarget("Top", "Bar").instOf("fizz", "Fizz") :: + Nil + unfoldInstanceTargets(input) should be(expected) + } +} From bbd7fc4115728ecc7cf88bf0524f2126d8220c34 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Tue, 19 Jan 2021 11:51:33 -0800 Subject: [PATCH 11/88] Switch from Travis to Github Actions CI (#2041) * Fix .run_formal_checks for Github Actions instead of Travis * Remove .run_chisel_tests Because we publish SNAPSHOTs on every merge to master, the Chisel repo checks against master of FIRRTL regularly * Only run unidoc for Scala 2.12 Under Travis we ran for 2.11, 2.12, and 2.13, but it doesn't work when using ++2.. when major != to 2.12. It seems if we want to run all 3, we have to run as `sbt +unidoc`. It's not clear how much benefit we get from building on the other versions, so stick with 2.12 for now. * Generate .mergify.yml with script in chisel-repo-tools Generated with config: conditions: - status-success=all tests passed branches: - 1.2.x - 1.3.x - 1.4.x --- .github/workflows/test.yml | 108 ++++++++++++++++++ .mergify.yml | 219 ++++++++++++++++++------------------- .run_chisel_tests.sh | 20 ---- .run_formal_checks.sh | 26 +++-- .travis.yml | 151 ------------------------- build.sbt | 5 + project/plugins.sbt | 2 + scripts/formal_equiv.sh | 2 +- 8 files changed, 235 insertions(+), 298 deletions(-) create mode 100644 .github/workflows/test.yml delete mode 100644 .run_chisel_tests.sh delete mode 100644 .travis.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000000..3f57a1a6a9 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,108 @@ +name: Continuous Integration + +on: + pull_request: + push: + branches: + - master + - 1.4.x + - 1.3.x + - 1.2.x + +jobs: + test: + name: sbt test + runs-on: ubuntu-latest + strategy: + matrix: + scala: [2.13.2, 2.12.12, 2.11.12] + container: + image: ucbbar/chisel3-tools + options: --user github --entrypoint /bin/bash + env: + CONTAINER_HOME: /home/github + + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Setup Scala + uses: olafurpg/setup-scala@v10 + with: + java-version: adopt@1.8 + - name: Cache Scala + uses: coursier/cache-action@v5 + - name: Unidoc builds (Scala 2.12 only) + if: matrix.scala == '2.12.12' + run: sbt ++${{ matrix.scala }} unidoc + - name: Sanity check benchmarking scripts (Scala 2.12 only) + if: matrix.scala == '2.12.12' + run: | + benchmark/scripts/benchmark_cold_compile.py -N 2 --designs regress/ICache.fir --versions HEAD + benchmark/scripts/find_heap_bound.py -- -cp firrtl*jar firrtl.stage.FirrtlMain -i regress/ICache.fir -o out -X verilog + - name: Test + run: sbt ++${{ matrix.scala }} test + - name: Binary compatibility + run: sbt ++${{ matrix.scala }} mimaReportBinaryIssues + + # TODO find better way to express Ops and AddNot as single test + equiv: + name: formal equivalence + runs-on: ubuntu-latest + strategy: + matrix: + design: [RocketCore, FPU, ICache, Ops, AddNot] + container: + image: ucbbar/chisel3-tools + options: --user github --entrypoint /bin/bash + env: + CONTAINER_HOME: /home/github + + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Setup Scala + uses: olafurpg/setup-scala@v10 + with: + java-version: adopt@1.8 + - name: Cache Scala + uses: coursier/cache-action@v5 + - name: Run Formal Equivalence + run: ./.run_formal_checks.sh ${{ matrix.design }} + + # Sentinel job to simplify how we specify which checks need to pass in branch + # protection and in Mergify + # + # When adding new jobs, please add them to `needs` below + all_tests_passed: + name: "all tests passed" + needs: [test, equiv] + runs-on: ubuntu-latest + steps: + - run: echo Success! + + # sbt ci-release publishes all cross versions so this job needs to be + # separate from a Scala versions build matrix to avoid duplicate publishing + publish: + needs: [all_tests_passed] + runs-on: ubuntu-latest + if: github.event_name == 'push' + + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Setup Scala + uses: olafurpg/setup-scala@v10 + with: + java-version: adopt@1.8 + - name: Cache Scala + uses: coursier/cache-action@v5 + - name: Setup GPG (for Publish) + uses: olafurpg/setup-gpg@v3 + - name: Publish + run: sbt ci-release + env: + PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} + PGP_SECRET: ${{ secrets.PGP_SECRET }} + SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} + SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} + diff --git a/.mergify.yml b/.mergify.yml index b09c861ae4..a4b8697f6a 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -1,116 +1,107 @@ pull_request_rules: - - name: remove outdated reviews - conditions: - - base=master - actions: - dismiss_reviews: +- name: automatic squash-and-merge on CI success and review + conditions: + - status-success=all tests passed + - '#approved-reviews-by>=1' + - '#changes-requested-reviews-by=0' + - base=master + - label="Please Merge" + - label!="DO NOT MERGE" + - label!="bp-conflict" + actions: + merge: + method: squash + strict: smart + strict_method: merge +- name: backport to 1.4.x + conditions: + - merged + - base=master + - milestone=1.4.x + actions: + backport: + branches: + - 1.4.x + ignore_conflicts: true + label_conflicts: bp-conflict + label: + add: + - Backported +- name: backport to 1.3.x, 1.4.x + conditions: + - merged + - base=master + - milestone=1.3.x + actions: + backport: + branches: + - 1.3.x + - 1.4.x + ignore_conflicts: true + label_conflicts: bp-conflict + label: + add: + - Backported +- name: backport to 1.2.x, 1.3.x, 1.4.x + conditions: + - merged + - base=master + - milestone=1.2.x + actions: + backport: + branches: + - 1.2.x + - 1.3.x + - 1.4.x + ignore_conflicts: true + label_conflicts: bp-conflict + label: + add: + - Backported +- name: label Mergify backport PR + conditions: + - body~=This is an automated backport of pull request \#\d+ done by Mergify + actions: + label: + add: + - Backport +- name: automatic squash-and-mege of 1.2.x backport PRs + conditions: + - status-success=all tests passed + - '#changes-requested-reviews-by=0' + - base=1.2.x + - label="Backport" + - label!="DO NOT MERGE" + - label!="bp-conflict" + actions: + merge: + method: squash + strict: smart + strict_method: merge +- name: automatic squash-and-mege of 1.3.x backport PRs + conditions: + - status-success=all tests passed + - '#changes-requested-reviews-by=0' + - base=1.3.x + - label="Backport" + - label!="DO NOT MERGE" + - label!="bp-conflict" + actions: + merge: + method: squash + strict: smart + strict_method: merge +- name: automatic squash-and-mege of 1.4.x backport PRs + conditions: + - status-success=all tests passed + - '#changes-requested-reviews-by=0' + - base=1.4.x + - label="Backport" + - label!="DO NOT MERGE" + - label!="bp-conflict" + actions: + merge: + method: squash + strict: smart + strict_method: merge -pull_request_rules: - - name: automatic squash-and-merge on CI success and review - conditions: - - status-success=Travis CI - Pull Request - - "#approved-reviews-by>=1" - - "#changes-requested-reviews-by=0" - - base=master - - label="Please Merge" - - label!="DO NOT MERGE" - - label!="bp-conflict" - actions: - merge: - method: squash - strict: smart - strict_method: merge - - - name: backport to 1.4.x - conditions: - - merged - - base=master - - milestone=1.4.x - actions: - backport: - branches: - - 1.4.x - ignore_conflicts: True - label_conflicts: "bp-conflict" - label: - add: [Backported] - - - name: backport to 1.3.x and 1.4.x - conditions: - - merged - - base=master - - milestone=1.3.x - actions: - backport: - branches: - - 1.3.x - - 1.4.x - ignore_conflicts: True - label_conflicts: "bp-conflict" - label: - add: [Backported] - - - name: backport to 1.2.x, 1.3.x, and 1.4.x - conditions: - - merged - - base=master - - milestone=1.2.X - actions: - backport: - branches: - - 1.2.x - - 1.3.x - - 1.4.x - ignore_conflicts: True - label_conflicts: "bp-conflict" - label: - add: [Backported] - - - name: label Mergify backport PR - conditions: - - body~=This is an automated backport of pull request \#\d+ done by Mergify - actions: - label: - add: [Backport] - - - name: automatic squash-and-merge of 1.4.x backport PRs - conditions: - - status-success=Travis CI - Pull Request - - "#changes-requested-reviews-by=0" - - base=1.4.x - - label="Backport" - - label!="DO NOT MERGE" - - label!="bp-conflict" - actions: - merge: - method: squash - strict: smart - strict_method: merge - - - name: automatic squash-and-merge of 1.3.x backport PRs - conditions: - - status-success=Travis CI - Pull Request - - "#changes-requested-reviews-by=0" - - base=1.3.x - - label="Backport" - - label!="DO NOT MERGE" - - label!="bp-conflict" - actions: - merge: - method: squash - strict: smart - strict_method: merge - - - name: automatic squash-and-merge of 1.2.x backport PRs - conditions: - - status-success=Travis CI - Pull Request - - "#changes-requested-reviews-by=0" - - base=1.2.x - - label="Backport" - - label!="DO NOT MERGE" - - label!="bp-conflict" - actions: - merge: - method: squash - strict: smart - strict_method: merge diff --git a/.run_chisel_tests.sh b/.run_chisel_tests.sh deleted file mode 100644 index dbec894e6e..0000000000 --- a/.run_chisel_tests.sh +++ /dev/null @@ -1,20 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -set -e - -# Use appropriate branches. -# Each stable branch of FIRRTL should have a fixed value for these branches. -CHISEL_BRANCH="master" -TREADLE_BRANCH="master" - -# Skip chisel tests if the commit message says to -# Replace ... with .. in TRAVIS_COMMIT_RANGE, see https://github.com/travis-ci/travis-ci/issues/4596 -if git log --format=%B --no-merges ${TRAVIS_COMMIT_RANGE/.../..} | grep '\[skip chisel tests\]'; then - exit 0 -else - sbt $SBT_ARGS publishLocal - git clone https://github.com/freechipsproject/treadle.git --single-branch -b ${TREADLE_BRANCH} --depth 10 - (cd treadle && sbt $SBT_ARGS publishLocal) - git clone https://github.com/ucb-bar/chisel3.git --single-branch -b ${CHISEL_BRANCH} - cd chisel3 - sbt $SBT_ARGS test -fi diff --git a/.run_formal_checks.sh b/.run_formal_checks.sh index f5ffec25c9..0a28a1d16a 100755 --- a/.run_formal_checks.sh +++ b/.run_formal_checks.sh @@ -11,23 +11,25 @@ fi DUT=$1 -# Run formal check only for PRs -if [ $TRAVIS_PULL_REQUEST = "false" ]; then - echo "Not a pull request, no formal check" - exit 0 -else - # $TRAVIS_BRANCH is branch targeted by PR - # Travis does a shallow clone, checkout PR target so that we have it - # THen return to previous branch so HEAD points to the commit we're testing - git remote set-branches origin $TRAVIS_BRANCH && git fetch - git checkout $TRAVIS_BRANCH +# See https://docs.github.com/en/actions/reference/environment-variables +# for info about these variables + +# Run formal check only for PRs, GITHUB_BASE_REF is only set for PRs +if [ ! -z "$GITHUB_BASE_REF" ]; then + # Github Actions does a shallow clone, checkout PR target so that we have it + # Then return to previous branch so HEAD points to the commit we're testing + git remote set-branches origin $GITHUB_BASE_REF && git fetch + git checkout $GITHUB_BASE_REF git checkout - # Skip if '[skip formal checks]' shows up in any of the commit messages in the PR - if git log --format=%B --no-merges $TRAVIS_BRANCH..HEAD | grep '\[skip formal checks\]'; then + if git log --format=%B --no-merges $GITHUB_BASE_REF..HEAD | grep '\[skip formal checks\]'; then echo "Commit message says to skip formal checks" exit 0 else cp regress/$DUT.fir $DUT.fir - ./scripts/formal_equiv.sh HEAD $TRAVIS_BRANCH $DUT + ./scripts/formal_equiv.sh HEAD $GITHUB_BASE_REF $DUT fi +else + echo "Not a pull request, no formal check" + exit 0 fi diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index efcb6434a5..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,151 +0,0 @@ -language: scala -sudo: false - -jdk: openjdk8 - -branches: - only: - - master - - 1.4.x - - 1.3.x - - 1.2.x - -cache: - directories: - $HOME/.cache/coursier - $HOME/.sbt - $INSTALL_DIR - -git: - depth: 10 - -env: - global: - INSTALL_DIR=$TRAVIS_BUILD_DIR/install - VERILATOR_ROOT=$INSTALL_DIR - PATH=$PATH:$VERILATOR_ROOT/bin:$TRAVIS_BUILD_DIR/utils/bin - SBT_ARGS="-Dsbt.log.noformat=true" - -before_script: - - OLDEST_SHARED=`git log --format=%H $TRAVIS_COMMIT_RANGE | tail -n1` - - OLDEST_COMMIT=`git log --format=%H | tail -n1` - - if [ $OLDEST_SHARED == $OLDEST_COMMIT ]; then git fetch --unshallow; fi - -stages: - - name: prepare - - name: test - - name: release - if: (branch IN (master, 1.4.x, 1.3.x, 1.2.x)) AND (type = push) AND (NOT fork) - -# We do not use the built-in tests as generated by using multiple Scala -# versions because the cache is not shared between stages with any -# environmental differences. -# Instead, we specify the version of Scala manually for each test (or leave it -# as the default as defined in FIRRTL's build.sbt). -jobs: - include: - # Because these write to the same install directory, they must run in the - # same script - - stage: prepare - name: "Install: [Verilator, Yosys, Z3]" - script: - - bash .install_verilator.sh - - verilator --version - - bash .install_yosys.sh - - yosys -V - - bash .install_z3.sh - - z3 -version - - stage: prepare - name: "Compile FIRRTL to share with subsequent stages" - script: - - sbt $SBT_ARGS assembly - workspaces: - create: - name: firrtl_build - paths: - - target/ - - utils/bin/firrtl.jar - - project/project/ - - project/target/ - - stage: test - name: "All files must be formatted with scalafmt" - workspaces: - use: firrtl_build - script: - - sbt scalafmtCheckAll - - stage: test - name: "Unidoc builds (no warnings)" - workspaces: - use: firrtl_build - script: - - sbt $SBT_ARGS +unidoc - - stage: test - name: "Tests: FIRRTL (2.13)" - workspaces: - use: firrtl_build - script: - - verilator --version - - sbt ++2.13.2 $SBT_ARGS test - - stage: test - name: "Tests: FIRRTL (2.12)" - workspaces: - use: firrtl_build - script: - - verilator --version - - sbt $SBT_ARGS test - - stage: test - name: "Tests: FIRRTL (2.11)" - script: - - verilator --version - - sbt ++2.11.12 $SBT_ARGS test - - stage: test - name: "Tests: chisel3 (2.12)" - workspaces: - use: firrtl_build - script: - - verilator --version - - bash .run_chisel_tests.sh - - stage: test - name: "Formal equivalence: RocketCore" - workspaces: - use: firrtl_build - script: - - yosys -V - - "travis_wait 30 sleep 1800 &" - - ./.run_formal_checks.sh RocketCore - - stage: test - name: "Formal equivalence: FPU" - workspaces: - use: firrtl_build - script: - - yosys -V - - "travis_wait 30 sleep 1800 &" - - ./.run_formal_checks.sh FPU - - stage: test - name: "Formal equivalence: ICache" - workspaces: - use: firrtl_build - script: - - yosys -V - - "travis_wait 30 sleep 1800 &" - - ./.run_formal_checks.sh ICache - - stage: test - name: "Formal equivalence: small expression-tree stress tests" - workspaces: - use: firrtl_build - script: - - yosys -V - - "travis_wait 30 sleep 1800 &" - - ./.run_formal_checks.sh Ops - - ./.run_formal_checks.sh AddNot - - stage: test - name: "Sanity check benchmarking scripts" - workspaces: - use: firrtl_build - script: - - benchmark/scripts/benchmark_cold_compile.py -N 2 --designs regress/ICache.fir --versions HEAD - - benchmark/scripts/find_heap_bound.py -- -cp firrtl*jar firrtl.stage.FirrtlMain -i regress/ICache.fir -o out -X verilog - # run ci-release only if previous stages passed - - stage: release - name: "Publish SNAPSHOT release" - script: sbt ci-release diff --git a/build.sbt b/build.sbt index 14aafe1b3c..f5a6a611df 100644 --- a/build.sbt +++ b/build.sbt @@ -55,6 +55,10 @@ lazy val commonSettings = Seq( ) ) +lazy val mimaSettings = Seq( + mimaPreviousArtifacts := Set() +) + lazy val protobufSettings = Seq( sourceDirectory in ProtobufConfig := baseDirectory.value / "src" / "main" / "proto", protobufRunProtoc in ProtobufConfig := (args => @@ -179,6 +183,7 @@ lazy val firrtl = (project in file(".")) buildInfoUsePackageAsPath := true, buildInfoKeys := Seq[BuildInfoKey](buildInfoPackage, version, scalaVersion, sbtVersion) ) + .settings(mimaSettings) lazy val benchmark = (project in file("benchmark")) .dependsOn(firrtl) diff --git a/project/plugins.sbt b/project/plugins.sbt index 346e2560e3..3ed7accf48 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -24,6 +24,8 @@ addSbtPlugin("com.thoughtworks.sbt-api-mappings" % "sbt-api-mappings" % "3.0.0") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.0") +addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.8.1") + addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.4") libraryDependencies += "com.github.os72" % "protoc-jar" % "3.11.4" diff --git a/scripts/formal_equiv.sh b/scripts/formal_equiv.sh index 2304b74e17..c3d4535750 100755 --- a/scripts/formal_equiv.sh +++ b/scripts/formal_equiv.sh @@ -28,7 +28,7 @@ make_verilog () { git checkout $1 local filename="$DUT.$1.v" - sbt "runMain firrtl.Driver -i $DUT.fir -o $filename -X verilog" + sbt "runMain firrtl.stage.FirrtlMain -i $DUT.fir -o $filename -X verilog" RET=$filename } From 2882912385c744389092b4f8d426379908d01ed7 Mon Sep 17 00:00:00 2001 From: Kevin Laeufer Date: Tue, 19 Jan 2021 13:14:24 -0800 Subject: [PATCH 12/88] smt: run DeadCodeElimination after PropagatePresetAnnotations (#2036) --- .../smt/FirrtlToTransitionSystem.scala | 19 ++++------- .../FirrtlToTransitionSystemPassSpec.scala | 33 +++++++++++++++++++ 2 files changed, 39 insertions(+), 13 deletions(-) create mode 100644 src/test/scala/firrtl/backends/experimental/smt/FirrtlToTransitionSystemPassSpec.scala diff --git a/src/main/scala/firrtl/backends/experimental/smt/FirrtlToTransitionSystem.scala b/src/main/scala/firrtl/backends/experimental/smt/FirrtlToTransitionSystem.scala index 145b5b0f4b..9c3099174d 100644 --- a/src/main/scala/firrtl/backends/experimental/smt/FirrtlToTransitionSystem.scala +++ b/src/main/scala/firrtl/backends/experimental/smt/FirrtlToTransitionSystem.scala @@ -10,18 +10,8 @@ import firrtl.options.Dependency import firrtl.passes.PassException import firrtl.stage.Forms import firrtl.stage.TransformManager.TransformDependency -import firrtl.transforms.PropagatePresetAnnotations -import firrtl.{ - ir, - CircuitState, - DependencyAPIMigration, - MemoryArrayInit, - MemoryInitValue, - MemoryScalarInit, - Namespace, - Transform, - Utils -} +import firrtl.transforms.{DeadCodeElimination, PropagatePresetAnnotations} +import firrtl.{CircuitState, DependencyAPIMigration, MemoryArrayInit, MemoryInitValue, MemoryScalarInit, Namespace, Transform, Utils, ir} import logger.LazyLogging import scala.collection.mutable @@ -67,9 +57,12 @@ object FirrtlToTransitionSystem extends Transform with DependencyAPIMigration { // Verilog emission passes. // Ideally we would go in and enable the [[PropagatePresetAnnotations]] to only depend on LowForm. private val presetPass = new PropagatePresetAnnotations + // We also need to run the DeadCodeElimination since PropagatePresets does not remove possible remaining + // AsyncReset nodes. + private val deadCodeElimination = new DeadCodeElimination override protected def execute(state: CircuitState): CircuitState = { // run the preset pass to extract all preset registers and remove preset reset signals - val afterPreset = presetPass.execute(state) + val afterPreset = deadCodeElimination.execute(presetPass.execute(state)) val circuit = afterPreset.circuit val presetRegs = afterPreset.annotations.collect { case PresetRegAnnotation(target) if target.module == circuit.main => target.ref diff --git a/src/test/scala/firrtl/backends/experimental/smt/FirrtlToTransitionSystemPassSpec.scala b/src/test/scala/firrtl/backends/experimental/smt/FirrtlToTransitionSystemPassSpec.scala new file mode 100644 index 0000000000..7dc5298c45 --- /dev/null +++ b/src/test/scala/firrtl/backends/experimental/smt/FirrtlToTransitionSystemPassSpec.scala @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 + +package firrtl.backends.experimental.smt + +import firrtl.annotations.{CircuitTarget, PresetAnnotation} +import firrtl.options.Dependency +import firrtl.testutils.LeanTransformSpec + +class FirrtlToTransitionSystemPassSpec extends LeanTransformSpec(Seq(Dependency(firrtl.backends.experimental.smt.FirrtlToTransitionSystem))) { + behavior of "FirrtlToTransitionSystem" + + it should "support preset wires" in { + // In order to give registers an initial wire, we use preset annotated resets. + // When using a wire instead of an input (which has the advantage of working regardless of the + // module hierarchy), we need to initialize it in order to get through the wire initialization check. + // In Chisel this generates a node which needs to be removed. + + val src = """circuit ModuleAB : + | module ModuleAB : + | input clock : Clock + | node _T = asAsyncReset(UInt<1>("h0")) + | node preset = _T + | reg REG : UInt<1>, clock with : + | reset => (preset, UInt<1>("h0")) + | assert(clock, UInt(1), not(REG), "REG == 0") + |""".stripMargin + val anno = PresetAnnotation(CircuitTarget("ModuleAB").module("ModuleAB").ref("preset")) + + val result = compile(src, List(anno)) + val sys = result.annotations.collectFirst{ case TransitionSystemAnnotation(sys) => sys }.get + assert(sys.states.head.init.isDefined) + } +} From 0748d0458c30a5d58198080aca36b43c09184841 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Tue, 19 Jan 2021 16:06:39 -0800 Subject: [PATCH 13/88] Fix Mergify condition for labeling backports (#2048) --- .mergify.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.mergify.yml b/.mergify.yml index a4b8697f6a..3929b42343 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -60,7 +60,7 @@ pull_request_rules: - Backported - name: label Mergify backport PR conditions: - - body~=This is an automated backport of pull request \#\d+ done by Mergify + - title~=\(bp \#\d+\) actions: label: add: From 6d8e9041e000f9ea5fb3d069d1f9ec06d2158575 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Tue, 19 Jan 2021 16:47:26 -0800 Subject: [PATCH 14/88] Restore scalafmt CI check (#2047) Fix scalafmtCheckAll failures that snuck through --- .github/workflows/test.yml | 3 +++ .../smt/FirrtlToTransitionSystem.scala | 12 +++++++++- .../FirrtlToTransitionSystemPassSpec.scala | 23 ++++++++++--------- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3f57a1a6a9..18a680f5c9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -31,6 +31,9 @@ jobs: java-version: adopt@1.8 - name: Cache Scala uses: coursier/cache-action@v5 + - name: Check Formatting (Scala 2.12 only) + if: matrix.scala == '2.12.12' + run: sbt ++${{ matrix.scala }} scalafmtCheckAll - name: Unidoc builds (Scala 2.12 only) if: matrix.scala == '2.12.12' run: sbt ++${{ matrix.scala }} unidoc diff --git a/src/main/scala/firrtl/backends/experimental/smt/FirrtlToTransitionSystem.scala b/src/main/scala/firrtl/backends/experimental/smt/FirrtlToTransitionSystem.scala index 9c3099174d..c185766733 100644 --- a/src/main/scala/firrtl/backends/experimental/smt/FirrtlToTransitionSystem.scala +++ b/src/main/scala/firrtl/backends/experimental/smt/FirrtlToTransitionSystem.scala @@ -11,7 +11,17 @@ import firrtl.passes.PassException import firrtl.stage.Forms import firrtl.stage.TransformManager.TransformDependency import firrtl.transforms.{DeadCodeElimination, PropagatePresetAnnotations} -import firrtl.{CircuitState, DependencyAPIMigration, MemoryArrayInit, MemoryInitValue, MemoryScalarInit, Namespace, Transform, Utils, ir} +import firrtl.{ + ir, + CircuitState, + DependencyAPIMigration, + MemoryArrayInit, + MemoryInitValue, + MemoryScalarInit, + Namespace, + Transform, + Utils +} import logger.LazyLogging import scala.collection.mutable diff --git a/src/test/scala/firrtl/backends/experimental/smt/FirrtlToTransitionSystemPassSpec.scala b/src/test/scala/firrtl/backends/experimental/smt/FirrtlToTransitionSystemPassSpec.scala index 7dc5298c45..e9254d7fdb 100644 --- a/src/test/scala/firrtl/backends/experimental/smt/FirrtlToTransitionSystemPassSpec.scala +++ b/src/test/scala/firrtl/backends/experimental/smt/FirrtlToTransitionSystemPassSpec.scala @@ -6,8 +6,9 @@ import firrtl.annotations.{CircuitTarget, PresetAnnotation} import firrtl.options.Dependency import firrtl.testutils.LeanTransformSpec -class FirrtlToTransitionSystemPassSpec extends LeanTransformSpec(Seq(Dependency(firrtl.backends.experimental.smt.FirrtlToTransitionSystem))) { - behavior of "FirrtlToTransitionSystem" +class FirrtlToTransitionSystemPassSpec + extends LeanTransformSpec(Seq(Dependency(firrtl.backends.experimental.smt.FirrtlToTransitionSystem))) { + behavior.of("FirrtlToTransitionSystem") it should "support preset wires" in { // In order to give registers an initial wire, we use preset annotated resets. @@ -16,18 +17,18 @@ class FirrtlToTransitionSystemPassSpec extends LeanTransformSpec(Seq(Dependency( // In Chisel this generates a node which needs to be removed. val src = """circuit ModuleAB : - | module ModuleAB : - | input clock : Clock - | node _T = asAsyncReset(UInt<1>("h0")) - | node preset = _T - | reg REG : UInt<1>, clock with : - | reset => (preset, UInt<1>("h0")) - | assert(clock, UInt(1), not(REG), "REG == 0") - |""".stripMargin + | module ModuleAB : + | input clock : Clock + | node _T = asAsyncReset(UInt<1>("h0")) + | node preset = _T + | reg REG : UInt<1>, clock with : + | reset => (preset, UInt<1>("h0")) + | assert(clock, UInt(1), not(REG), "REG == 0") + |""".stripMargin val anno = PresetAnnotation(CircuitTarget("ModuleAB").module("ModuleAB").ref("preset")) val result = compile(src, List(anno)) - val sys = result.annotations.collectFirst{ case TransitionSystemAnnotation(sys) => sys }.get + val sys = result.annotations.collectFirst { case TransitionSystemAnnotation(sys) => sys }.get assert(sys.states.head.init.isDefined) } } From 698a9dca52f819aca6309e3b03f2420a71bc89a6 Mon Sep 17 00:00:00 2001 From: Schuyler Eldridge Date: Tue, 19 Jan 2021 22:49:31 -0500 Subject: [PATCH 15/88] Add --dont-fold option to disable folding prim ops (#2040) This adds a --dont-fold options (backed by a DisableFold annotation) that lets a user specify primitive operations which should never be folded. This feature lets a user disable certain folds which may be allowable in FIRRTL (or by any sane synthesis tool), but due to inane Verilog language design causes formal equivalence tools to fail due to the fold. Add a test that a user can disable `a / a -> 1` with a DisableFold(PrimOps.Div) annotation. Signed-off-by: Schuyler Eldridge --- .../firrtl/stage/FirrtlAnnotations.scala | 26 ++++++++++ src/main/scala/firrtl/stage/FirrtlCli.scala | 3 +- src/main/scala/firrtl/stage/package.scala | 1 + .../transforms/ConstantPropagation.scala | 51 ++++++++++++------- .../ConstantPropagationTests.scala | 12 +++++ 5 files changed, 75 insertions(+), 18 deletions(-) diff --git a/src/main/scala/firrtl/stage/FirrtlAnnotations.scala b/src/main/scala/firrtl/stage/FirrtlAnnotations.scala index 99a6e9c3fb..2ac74de20d 100644 --- a/src/main/scala/firrtl/stage/FirrtlAnnotations.scala +++ b/src/main/scala/firrtl/stage/FirrtlAnnotations.scala @@ -280,3 +280,29 @@ case object PrettyNoExprInlining extends NoTargetAnnotation with FirrtlOption wi ) ) } + +/** Turn off folding a specific primitive operand + * @param op the op that should never be folded + */ +case class DisableFold(op: ir.PrimOp) extends NoTargetAnnotation with FirrtlOption + +object DisableFold extends HasShellOptions { + + private val mapping: Map[String, ir.PrimOp] = PrimOps.builtinPrimOps.map { case op => op.toString -> op }.toMap + + override val options = Seq( + new ShellOption[String]( + longOption = "dont-fold", + toAnnotationSeq = a => { + mapping + .get(a) + .orElse(throw new OptionsException(s"Unknown primop '$a'. (Did you misspell it?)")) + .map(DisableFold(_)) + .toSeq + }, + helpText = "Disable folding of specific primitive operations", + helpValueName = Some("") + ) + ) + +} diff --git a/src/main/scala/firrtl/stage/FirrtlCli.scala b/src/main/scala/firrtl/stage/FirrtlCli.scala index 18f14107c5..8be5fb74d4 100644 --- a/src/main/scala/firrtl/stage/FirrtlCli.scala +++ b/src/main/scala/firrtl/stage/FirrtlCli.scala @@ -21,7 +21,8 @@ trait FirrtlCli { this: Shell => firrtl.EmitAllModulesAnnotation, NoCircuitDedupAnnotation, WarnNoScalaVersionDeprecation, - PrettyNoExprInlining + PrettyNoExprInlining, + DisableFold ) .map(_.addOptions(parser)) diff --git a/src/main/scala/firrtl/stage/package.scala b/src/main/scala/firrtl/stage/package.scala index c159f8522a..68e7a9c5d5 100644 --- a/src/main/scala/firrtl/stage/package.scala +++ b/src/main/scala/firrtl/stage/package.scala @@ -34,6 +34,7 @@ package object stage { case a: CompilerAnnotation => logger.warn(s"Use of CompilerAnnotation is deprecated. Ignoring $a"); c case WarnNoScalaVersionDeprecation => c case PrettyNoExprInlining => c + case _: DisableFold => c } } } diff --git a/src/main/scala/firrtl/transforms/ConstantPropagation.scala b/src/main/scala/firrtl/transforms/ConstantPropagation.scala index 5d57de3a8d..c89fcff196 100644 --- a/src/main/scala/firrtl/transforms/ConstantPropagation.scala +++ b/src/main/scala/firrtl/transforms/ConstantPropagation.scala @@ -14,6 +14,7 @@ import firrtl.graph.DiGraph import firrtl.analyses.InstanceKeyGraph import firrtl.annotations.TargetToken.Ref import firrtl.options.Dependency +import firrtl.stage.DisableFold import annotation.tailrec import collection.mutable @@ -401,7 +402,8 @@ class ConstantPropagation extends Transform with DependencyAPIMigration { override def reduce = (a: Boolean, b: Boolean) => a ^ b } - private def constPropPrim(e: DoPrim): Expression = e.op match { + private def constPropPrim(e: DoPrim, disabledOps: Set[PrimOp]): Expression = e.op match { + case a if disabledOps(a) => e case Shl => foldShiftLeft(e) case Dshl => foldDynamicShiftLeft(e) case Shr => foldShiftRight(e) @@ -495,19 +497,25 @@ class ConstantPropagation extends Transform with DependencyAPIMigration { private def betterName(a: String, b: String): Boolean = (a.head != '_') && (b.head == '_') def optimize(e: Expression): Expression = - constPropExpression(new NodeMap(), Map.empty[Instance, OfModule], Map.empty[OfModule, Map[String, Literal]])(e) + constPropExpression( + new NodeMap(), + Map.empty[Instance, OfModule], + Map.empty[OfModule, Map[String, Literal]], + Set.empty + )(e) def optimize(e: Expression, nodeMap: NodeMap): Expression = - constPropExpression(nodeMap, Map.empty[Instance, OfModule], Map.empty[OfModule, Map[String, Literal]])(e) + constPropExpression(nodeMap, Map.empty[Instance, OfModule], Map.empty[OfModule, Map[String, Literal]], Set.empty)(e) private def constPropExpression( nodeMap: NodeMap, instMap: collection.Map[Instance, OfModule], - constSubOutputs: Map[OfModule, Map[String, Literal]] + constSubOutputs: Map[OfModule, Map[String, Literal]], + disabledOps: Set[PrimOp] )(e: Expression ): Expression = { - val old = e.map(constPropExpression(nodeMap, instMap, constSubOutputs)) + val old = e.map(constPropExpression(nodeMap, instMap, constSubOutputs, disabledOps)) val propagated = old match { - case p: DoPrim => constPropPrim(p) + case p: DoPrim => constPropPrim(p, disabledOps) case m: Mux => constPropMux(m) case ref @ WRef(rname, _, _, SourceFlow) if nodeMap.contains(rname) => constPropNodeRef(ref, InfoExpr.unwrap(nodeMap(rname))._2) @@ -519,7 +527,7 @@ class ConstantPropagation extends Transform with DependencyAPIMigration { } // We're done when the Expression no longer changes if (propagated eq old) propagated - else constPropExpression(nodeMap, instMap, constSubOutputs)(propagated) + else constPropExpression(nodeMap, instMap, constSubOutputs, disabledOps)(propagated) } /** Hacky way of propagating source locators across nodes and connections that have just a @@ -555,6 +563,7 @@ class ConstantPropagation extends Transform with DependencyAPIMigration { * @param instMap map of instance names to Module name * @param constInputs map of names of m's input ports to literal driving it (if applicable) * @param constSubOutputs Map of Module name to Map of output port name to literal driving it + * @param disabledOps a Set of any PrimOps that should not be folded * @return (Constpropped Module, Map of output port names to literal value, * Map of submodule modulenames to Map of input port names to literal values) */ @@ -564,7 +573,8 @@ class ConstantPropagation extends Transform with DependencyAPIMigration { dontTouches: Set[String], instMap: collection.Map[Instance, OfModule], constInputs: Map[String, Literal], - constSubOutputs: Map[OfModule, Map[String, Literal]] + constSubOutputs: Map[OfModule, Map[String, Literal]], + disabledOps: Set[PrimOp] ): (Module, Map[String, Literal], Map[OfModule, Map[String, Seq[Literal]]]) = { var nPropagated = 0L @@ -646,7 +656,8 @@ class ConstantPropagation extends Transform with DependencyAPIMigration { def constPropStmt(s: Statement): Statement = { val s0 = s.map(constPropStmt) // Statement recurse val s1 = propagateDirectConnectionInfoOnly(nodeMap, dontTouches)(s0) // hacky source locator propagation - val stmtx = s1.map(constPropExpression(nodeMap, instMap, constSubOutputs)) // propagate sub-Expressions + // propagate sub-Expressions + val stmtx = s1.map(constPropExpression(nodeMap, instMap, constSubOutputs, disabledOps)) // Record things that should be propagated stmtx match { case DefNode(info, name, value) if !dontTouches.contains(name) => @@ -654,11 +665,12 @@ class ConstantPropagation extends Transform with DependencyAPIMigration { case reg: DefRegister if reg.reset.tpe == AsyncResetType => asyncResetRegs(reg.name) = reg case Connect(info, WRef(wname, wtpe, WireKind, _), expr: Literal) if !dontTouches.contains(wname) => - val exprx = constPropExpression(nodeMap, instMap, constSubOutputs)(pad(expr, wtpe)) + val exprx = constPropExpression(nodeMap, instMap, constSubOutputs, disabledOps)(pad(expr, wtpe)) propagateRef(wname, exprx, info) // Record constants driving outputs case Connect(_, WRef(pname, ptpe, PortKind, _), lit: Literal) if !dontTouches.contains(pname) => - val paddedLit = constPropExpression(nodeMap, instMap, constSubOutputs)(pad(lit, ptpe)).asInstanceOf[Literal] + val paddedLit = + constPropExpression(nodeMap, instMap, constSubOutputs, disabledOps)(pad(lit, ptpe)).asInstanceOf[Literal] constOutputs(pname) = paddedLit // Const prop registers that are driven by a mux tree containing only instances of one constant or self-assigns // This requires that reset has been made explicit @@ -714,7 +726,8 @@ class ConstantPropagation extends Transform with DependencyAPIMigration { case _ => } - def padCPExp(e: Expression) = constPropExpression(nodeMap, instMap, constSubOutputs)(pad(e, ltpe)) + def padCPExp(e: Expression) = + constPropExpression(nodeMap, instMap, constSubOutputs, disabledOps)(pad(e, ltpe)) asyncResetRegs.get(lname) match { // Normal Register @@ -725,7 +738,8 @@ class ConstantPropagation extends Transform with DependencyAPIMigration { // Mark instance inputs connected to a constant case Connect(_, lref @ WSubField(WRef(inst, _, InstanceKind, _), port, ptpe, _), lit: Literal) => - val paddedLit = constPropExpression(nodeMap, instMap, constSubOutputs)(pad(lit, ptpe)).asInstanceOf[Literal] + val paddedLit = + constPropExpression(nodeMap, instMap, constSubOutputs, disabledOps)(pad(lit, ptpe)).asInstanceOf[Literal] val module = instMap(inst.Instance) val portsMap = constSubInputs.getOrElseUpdate(module, mutable.HashMap.empty) portsMap(port) = paddedLit +: portsMap.getOrElse(port, List.empty) @@ -750,7 +764,7 @@ class ConstantPropagation extends Transform with DependencyAPIMigration { // When we call this function again, constOutputs and constSubInputs are reconstructed and // strictly a superset of the versions here - if (nPropagated > 0) constPropModule(modx, dontTouches, instMap, constInputs, constSubOutputs) + if (nPropagated > 0) constPropModule(modx, dontTouches, instMap, constInputs, constSubOutputs, disabledOps) else (modx, constOutputs.toMap, constSubInputs.mapValues(_.toMap).toMap) } @@ -761,7 +775,7 @@ class ConstantPropagation extends Transform with DependencyAPIMigration { acc + (k -> acc.get(k).map(f(_, v)).getOrElse(v)) } - private def run(c: Circuit, dontTouchMap: Map[OfModule, Set[String]]): Circuit = { + private def run(c: Circuit, dontTouchMap: Map[OfModule, Set[String]], disabledOps: Set[PrimOp]): Circuit = { val iGraph = InstanceKeyGraph(c) val moduleDeps = iGraph.getChildInstanceMap val instCount = iGraph.staticInstanceCount @@ -800,7 +814,8 @@ class ConstantPropagation extends Transform with DependencyAPIMigration { dontTouches, moduleDeps(mname), constInputs.getOrElse(mname, Map.empty), - constOutputs + constOutputs, + disabledOps ) // Accumulate all Literals used to drive a particular Module port val constInputsx = unify(constInputsAcc, mci)((a, b) => unify(a, b)((c, d) => c ++ d)) @@ -852,6 +867,8 @@ class ConstantPropagation extends Transform with DependencyAPIMigration { val dontTouchMap: Map[OfModule, Set[String]] = dontTouches.groupBy(_._1).mapValues(_.map(_._2).toSet).toMap - state.copy(circuit = run(state.circuit, dontTouchMap)) + val disabledOps = state.annotations.collect { case DisableFold(op) => op }.toSet + + state.copy(circuit = run(state.circuit, dontTouchMap, disabledOps)) } } diff --git a/src/test/scala/firrtlTests/ConstantPropagationTests.scala b/src/test/scala/firrtlTests/ConstantPropagationTests.scala index 9497304298..28c1d823cf 100644 --- a/src/test/scala/firrtlTests/ConstantPropagationTests.scala +++ b/src/test/scala/firrtlTests/ConstantPropagationTests.scala @@ -7,6 +7,7 @@ import firrtl.passes._ import firrtl.transforms._ import firrtl.testutils._ import firrtl.annotations.Annotation +import firrtl.stage.DisableFold class ConstantPropagationSpec extends FirrtlFlatSpec { val transforms: Seq[Transform] = @@ -798,6 +799,17 @@ class ConstantPropagationSingleModule extends ConstantPropagationSpec { castCheck("Clock", "asClock") castCheck("AsyncReset", "asAsyncReset") } + + /* */ + "The rule a / a -> 1" should "be ignored if division folds are disabled" in { + val input = + """circuit foo: + | module foo: + | input a: UInt<8> + | output b: UInt<8> + | b <= div(a, a)""".stripMargin + (parse(exec(input, Seq(DisableFold(PrimOps.Div))))) should be(parse(input)) + } } // More sophisticated tests of the full compiler From 031fe1382660867750e6eeebea5665c137dbccbe Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Tue, 19 Jan 2021 20:19:08 -0800 Subject: [PATCH 16/88] Cleanup some warnings (#2032) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- src/main/scala/firrtl/Compiler.scala | 10 +++++++++- src/main/scala/firrtl/Utils.scala | 1 - src/main/scala/firrtl/Visitor.scala | 4 ++-- .../firrtl/analyses/ConnectionGraph.scala | 1 + .../scala/firrtl/analyses/InstanceGraph.scala | 1 - .../scala/firrtl/annotations/Annotation.scala | 2 +- src/main/scala/firrtl/annotations/Target.scala | 4 +++- .../firrtl/backends/firrtl/FirrtlEmitter.scala | 11 +++++------ src/main/scala/firrtl/ir/IR.scala | 1 + src/main/scala/firrtl/passes/CInferMDir.scala | 5 +---- .../scala/firrtl/passes/CheckHighForm.scala | 4 +--- src/main/scala/firrtl/passes/InferWidths.scala | 1 + .../passes/clocklist/RemoveAllButClocks.scala | 1 - .../firrtl/passes/wiring/WiringUtils.scala | 2 -- src/main/scala/firrtl/proto/FromProto.scala | 18 ++++++++++++++++-- 15 files changed, 41 insertions(+), 25 deletions(-) diff --git a/src/main/scala/firrtl/Compiler.scala b/src/main/scala/firrtl/Compiler.scala index b4629a2a99..38b71f4a4d 100644 --- a/src/main/scala/firrtl/Compiler.scala +++ b/src/main/scala/firrtl/Compiler.scala @@ -116,6 +116,14 @@ sealed abstract class CircuitForm(private val value: Int) extends Ordered[Circui /** Defines a suffix to use if this form is written to a file */ def outputSuffix: String } +private[firrtl] object CircuitForm { + // Private internal utils to reduce number of deprecation warnings + val ChirrtlForm = firrtl.ChirrtlForm + val HighForm = firrtl.HighForm + val MidForm = firrtl.MidForm + val LowForm = firrtl.LowForm + val UnknownForm = firrtl.UnknownForm +} // These magic numbers give an ordering to CircuitForm /** Chirrtl Form @@ -310,7 +318,7 @@ trait Transform extends TransformLike[CircuitState] with DependencyAPI[Transform def transform(state: CircuitState): CircuitState = execute(state) - import firrtl.{ChirrtlForm => C, HighForm => H, MidForm => M, LowForm => L, UnknownForm => U} + import firrtl.CircuitForm.{ChirrtlForm => C, HighForm => H, MidForm => M, LowForm => L, UnknownForm => U} override def prerequisites: Seq[Dependency[Transform]] = inputForm match { case C => Nil diff --git a/src/main/scala/firrtl/Utils.scala b/src/main/scala/firrtl/Utils.scala index 467552cb1c..886ee986c4 100644 --- a/src/main/scala/firrtl/Utils.scala +++ b/src/main/scala/firrtl/Utils.scala @@ -632,7 +632,6 @@ object Utils extends LazyLogging { def get_flow(s: Statement): Flow = s match { case sx: DefWire => DuplexFlow case sx: DefRegister => DuplexFlow - case sx: WDefInstance => SourceFlow case sx: DefNode => SourceFlow case sx: DefInstance => SourceFlow case sx: DefMemory => SourceFlow diff --git a/src/main/scala/firrtl/Visitor.scala b/src/main/scala/firrtl/Visitor.scala index 7ba8a0bf79..dadcac4649 100644 --- a/src/main/scala/firrtl/Visitor.scala +++ b/src/main/scala/firrtl/Visitor.scala @@ -164,12 +164,12 @@ class Visitor(infoMode: InfoMode) extends AbstractParseTreeVisitor[FirrtlNode] w } IntervalType(UnknownBound, UnknownBound, point) case 2 => - val lower = (ctx.lowerBound.getText, ctx.boundValue(0).getText) match { + val lower = ((ctx.lowerBound.getText, ctx.boundValue(0).getText): @unchecked) match { case (_, "?") => UnknownBound case ("(", v) => Open(string2BigDecimal(v)) case ("[", v) => Closed(string2BigDecimal(v)) } - val upper = (ctx.upperBound.getText, ctx.boundValue(1).getText) match { + val upper = ((ctx.upperBound.getText, ctx.boundValue(1).getText): @unchecked) match { case (_, "?") => UnknownBound case (")", v) => Open(string2BigDecimal(v)) case ("]", v) => Closed(string2BigDecimal(v)) diff --git a/src/main/scala/firrtl/analyses/ConnectionGraph.scala b/src/main/scala/firrtl/analyses/ConnectionGraph.scala index e5e3bde2dc..32bb156456 100644 --- a/src/main/scala/firrtl/analyses/ConnectionGraph.scala +++ b/src/main/scala/firrtl/analyses/ConnectionGraph.scala @@ -416,6 +416,7 @@ object ConnectionGraph { case firrtl.ir.Field(name, Default, tpe) => Utils.create_exps(Reference(name, tpe, PortKind, SourceFlow)) // Module input case firrtl.ir.Field(name, Flip, tpe) => Utils.create_exps(Reference(name, tpe, PortKind, SinkFlow)) + case x => Utils.error(s"Unexpected flip: ${x.flip}") } assert(instPorts.size == modulePorts.size) val o = m.circuitTarget.module(ofModule) diff --git a/src/main/scala/firrtl/analyses/InstanceGraph.scala b/src/main/scala/firrtl/analyses/InstanceGraph.scala index 0017ff8ba1..8858c4eac0 100644 --- a/src/main/scala/firrtl/analyses/InstanceGraph.scala +++ b/src/main/scala/firrtl/analyses/InstanceGraph.scala @@ -185,7 +185,6 @@ object InstanceGraph { @deprecated("Use InstanceKeyGraph.collectInstances instead.", "FIRRTL 1.4") def collectInstances(insts: mutable.Set[DefInstance])(s: Statement): Unit = s match { case i: DefInstance => insts += i - case i: DefInstance => throwInternalError("Expecting DefInstance, found a DefInstance!") case i: WDefInstanceConnector => throwInternalError("Expecting DefInstance, found a DefInstanceConnector!") case _ => s.foreach(collectInstances(insts)) } diff --git a/src/main/scala/firrtl/annotations/Annotation.scala b/src/main/scala/firrtl/annotations/Annotation.scala index 5f7921279f..b5c9c7e0da 100644 --- a/src/main/scala/firrtl/annotations/Annotation.scala +++ b/src/main/scala/firrtl/annotations/Annotation.scala @@ -71,7 +71,7 @@ trait SingleTargetAnnotation[T <: Named] extends Annotation { case c: CircuitTarget => c.toNamed case other => throw Target.NamedException(s"Cannot convert $other to [[Named]]") } - Target.convertTarget2Named(result) match { + (Target.convertTarget2Named(result): @unchecked) match { case newTarget: T @unchecked => try { duplicate(newTarget) diff --git a/src/main/scala/firrtl/annotations/Target.scala b/src/main/scala/firrtl/annotations/Target.scala index 8e84a269e3..92339946b7 100644 --- a/src/main/scala/firrtl/annotations/Target.scala +++ b/src/main/scala/firrtl/annotations/Target.scala @@ -265,7 +265,7 @@ case class GenericTarget(circuitOpt: Option[String], moduleOpt: Option[String], case GenericTarget(Some(c), Some(m), Instance(i) +: OfModule(o) +: Vector()) => InstanceTarget(c, m, Nil, i, o) case GenericTarget(Some(c), Some(m), component) => val path = getPath.getOrElse(Nil) - (getRef, getInstanceOf) match { + ((getRef, getInstanceOf): @unchecked) match { case (Some((r, comps)), _) => ReferenceTarget(c, m, path, r, comps) case (None, Some((i, o))) => InstanceTarget(c, m, path, i, o) } @@ -516,6 +516,7 @@ trait IsComponent extends IsMember { case ("", Ref(name)) => name case (string, Field(value)) => s"$string.$value" case (string, Index(value)) => s"$string[$value]" + case (_, token) => Utils.error(s"Unexpected token: $token") } ComponentName(name, mn) case Seq(Instance(name), OfModule(o)) => ComponentName(name, mn) @@ -660,6 +661,7 @@ case class ReferenceTarget( case Index(idx) => sub_type(baseType) case Field(field) => field_type(baseType, field) case _: Ref => baseType + case token => Utils.error(s"Unexpected token $token") } componentType(headType, tokens.tail) } diff --git a/src/main/scala/firrtl/backends/firrtl/FirrtlEmitter.scala b/src/main/scala/firrtl/backends/firrtl/FirrtlEmitter.scala index 26e0363319..80aea9963d 100644 --- a/src/main/scala/firrtl/backends/firrtl/FirrtlEmitter.scala +++ b/src/main/scala/firrtl/backends/firrtl/FirrtlEmitter.scala @@ -20,8 +20,7 @@ sealed abstract class FirrtlEmitter(form: CircuitForm) extends Transform with Em // Use list instead of set to maintain order val modules = mutable.ArrayBuffer.empty[DefModule] def onStmt(stmt: Statement): Unit = stmt match { - case DefInstance(_, _, name, _) => modules += map(name) - case WDefInstance(_, _, name, _) => modules += map(name) + case DefInstance(_, _, name, _) => modules += map(name) case _: WDefInstanceConnector => throwInternalError(s"unrecognized statement: $stmt") case other => other.foreach(onStmt) } @@ -61,7 +60,7 @@ sealed abstract class FirrtlEmitter(form: CircuitForm) extends Transform with Em def emit(state: CircuitState, writer: Writer): Unit = writer.write(state.circuit.serialize) } -class ChirrtlEmitter extends FirrtlEmitter(ChirrtlForm) -class HighFirrtlEmitter extends FirrtlEmitter(HighForm) -class MiddleFirrtlEmitter extends FirrtlEmitter(MidForm) -class LowFirrtlEmitter extends FirrtlEmitter(LowForm) +class ChirrtlEmitter extends FirrtlEmitter(CircuitForm.ChirrtlForm) +class HighFirrtlEmitter extends FirrtlEmitter(CircuitForm.HighForm) +class MiddleFirrtlEmitter extends FirrtlEmitter(CircuitForm.MidForm) +class LowFirrtlEmitter extends FirrtlEmitter(CircuitForm.LowForm) diff --git a/src/main/scala/firrtl/ir/IR.scala b/src/main/scala/firrtl/ir/IR.scala index a26a2a944e..7d091176a1 100644 --- a/src/main/scala/firrtl/ir/IR.scala +++ b/src/main/scala/firrtl/ir/IR.scala @@ -891,6 +891,7 @@ case class IntervalType(lower: Bound, upper: Bound, point: Width) extends Ground case x => Some(x.setScale(0, FLOOR) * prec) } case (Closed(a), Some(prec)) => Some((a / prec).setScale(0, FLOOR) * prec) + case _ => None } def minAdjusted: Option[BigInt] = min.map(_ * BigDecimal(BigInt(1) << bp) match { diff --git a/src/main/scala/firrtl/passes/CInferMDir.scala b/src/main/scala/firrtl/passes/CInferMDir.scala index 90f1c73950..cca8fde4ff 100644 --- a/src/main/scala/firrtl/passes/CInferMDir.scala +++ b/src/main/scala/firrtl/passes/CInferMDir.scala @@ -22,22 +22,19 @@ object CInferMDir extends Pass { case None => case Some(p) => mports(e.name) = (p, dir) match { - case (MInfer, MInfer) => throwInternalError(s"infer_mdir_e: shouldn't be here - $p, $dir") case (MInfer, MWrite) => MWrite case (MInfer, MRead) => MRead case (MInfer, MReadWrite) => MReadWrite - case (MWrite, MInfer) => throwInternalError(s"infer_mdir_e: shouldn't be here - $p, $dir") case (MWrite, MWrite) => MWrite case (MWrite, MRead) => MReadWrite case (MWrite, MReadWrite) => MReadWrite - case (MRead, MInfer) => throwInternalError(s"infer_mdir_e: shouldn't be here - $p, $dir") case (MRead, MWrite) => MReadWrite case (MRead, MRead) => MRead case (MRead, MReadWrite) => MReadWrite - case (MReadWrite, MInfer) => throwInternalError(s"infer_mdir_e: shouldn't be here - $p, $dir") case (MReadWrite, MWrite) => MReadWrite case (MReadWrite, MRead) => MReadWrite case (MReadWrite, MReadWrite) => MReadWrite + case _ => throwInternalError(s"infer_mdir_e: shouldn't be here - $p, $dir") } } e diff --git a/src/main/scala/firrtl/passes/CheckHighForm.scala b/src/main/scala/firrtl/passes/CheckHighForm.scala index 5514741ae8..7e305b3352 100644 --- a/src/main/scala/firrtl/passes/CheckHighForm.scala +++ b/src/main/scala/firrtl/passes/CheckHighForm.scala @@ -243,8 +243,7 @@ trait CheckHighFormLike { this: Pass => errors.append(new NegUIntException(info, mname)) case ex: DoPrim => checkHighFormPrimop(info, mname, ex) case _: Reference | _: WRef | _: UIntLiteral | _: Mux | _: ValidIf => - case ex: SubAccess => validSubexp(info, mname)(ex.expr) - case ex: WSubAccess => validSubexp(info, mname)(ex.expr) + case ex: SubAccess => validSubexp(info, mname)(ex.expr) case ex => ex.foreach(validSubexp(info, mname)) } e.foreach(checkHighFormW(info, mname + "/" + e.serialize)) @@ -284,7 +283,6 @@ trait CheckHighFormLike { this: Pass => if (sx.depth <= 0) errors.append(new NegMemSizeException(info, mname)) case sx: DefInstance => checkInstance(info, mname, sx.module) - case sx: WDefInstance => checkInstance(info, mname, sx.module) case sx: Connect => checkValidLoc(info, mname, sx.loc) case sx: PartialConnect => checkValidLoc(info, mname, sx.loc) case sx: Print => checkFstring(info, mname, sx.string, sx.args.length) diff --git a/src/main/scala/firrtl/passes/InferWidths.scala b/src/main/scala/firrtl/passes/InferWidths.scala index aa2095fabb..56cd4dd290 100644 --- a/src/main/scala/firrtl/passes/InferWidths.scala +++ b/src/main/scala/firrtl/passes/InferWidths.scala @@ -110,6 +110,7 @@ class InferWidths extends Transform with ResolvedAnnotationPaths with Dependency case (AsyncResetType, AsyncResetType) => Nil case (ResetType, _) => Nil case (_, ResetType) => Nil + case _ => throwInternalError("Shouldn't be here") } private def addExpConstraints(e: Expression)(implicit constraintSolver: ConstraintSolver): Expression = diff --git a/src/main/scala/firrtl/passes/clocklist/RemoveAllButClocks.scala b/src/main/scala/firrtl/passes/clocklist/RemoveAllButClocks.scala index 9ad653cf40..671a08b950 100644 --- a/src/main/scala/firrtl/passes/clocklist/RemoveAllButClocks.scala +++ b/src/main/scala/firrtl/passes/clocklist/RemoveAllButClocks.scala @@ -16,7 +16,6 @@ object RemoveAllButClocks extends Pass { case DefWire(i, n, ClockType) => s case DefNode(i, n, value) if value.tpe == ClockType => s case Connect(i, l, r) if l.tpe == ClockType => s - case sx: WDefInstance => sx case sx: DefInstance => sx case sx: Block => sx case sx: Conditionally => sx diff --git a/src/main/scala/firrtl/passes/wiring/WiringUtils.scala b/src/main/scala/firrtl/passes/wiring/WiringUtils.scala index d926f6a95d..cab6aa5f60 100644 --- a/src/main/scala/firrtl/passes/wiring/WiringUtils.scala +++ b/src/main/scala/firrtl/passes/wiring/WiringUtils.scala @@ -90,8 +90,6 @@ object WiringUtils { def getChildrenMap(c: Circuit): ChildrenMap = { val childrenMap = new ChildrenMap() def getChildren(mname: String)(s: Statement): Unit = s match { - case s: WDefInstance => - childrenMap(mname) = childrenMap(mname) :+ ((s.name, s.module)) case s: DefInstance => childrenMap(mname) = childrenMap(mname) :+ ((s.name, s.module)) case s => s.foreach(getChildren(mname)) diff --git a/src/main/scala/firrtl/proto/FromProto.scala b/src/main/scala/firrtl/proto/FromProto.scala index 663e91b3cb..cb9b705e2f 100644 --- a/src/main/scala/firrtl/proto/FromProto.scala +++ b/src/main/scala/firrtl/proto/FromProto.scala @@ -148,6 +148,9 @@ object FromProto { case ReadUnderWrite.UNDEFINED => ir.ReadUnderWrite.Undefined case ReadUnderWrite.OLD => ir.ReadUnderWrite.Old case ReadUnderWrite.NEW => ir.ReadUnderWrite.New + case ReadUnderWrite.UNRECOGNIZED => + val msg = s"Unrecognized ReadUnderWrite value '$ruw', perhaps this version of FIRRTL is too old?" + throw new FirrtlUserException(msg) } def convert(dt: Firrtl.Statement.CMemory.TypeAndDepth): (ir.Type, BigInt) = @@ -171,6 +174,10 @@ object FromProto { case MEMORY_PORT_DIRECTION_READ => MRead case MEMORY_PORT_DIRECTION_WRITE => MWrite case MEMORY_PORT_DIRECTION_READ_WRITE => MReadWrite + case MEMORY_PORT_DIRECTION_UNKNOWN => MInfer + case UNRECOGNIZED => + val msg = s"Unrecognized MemoryPort Direction value '$mportdir', perhaps this version of FIRRTL is too old?" + throw new FirrtlUserException(msg) } def convert(port: Firrtl.Statement.MemoryPort, info: Firrtl.SourceInfo): CDefMPort = { @@ -191,6 +198,9 @@ object FromProto { case Formal.ASSERT => ir.Formal.Assert case Formal.ASSUME => ir.Formal.Assume case Formal.COVER => ir.Formal.Cover + case Formal.UNRECOGNIZED => + val msg = s"Unrecognized Formal value '$formal', perhaps this version of FIRRTL is too old?" + throw new FirrtlUserException(msg) } def convert(ver: Firrtl.Statement.Verification, info: Firrtl.SourceInfo): ir.Verification = @@ -308,9 +318,13 @@ object FromProto { } def convert(dir: Firrtl.Port.Direction): ir.Direction = { + import Firrtl.Port.Direction._ dir match { - case Firrtl.Port.Direction.PORT_DIRECTION_IN => ir.Input - case Firrtl.Port.Direction.PORT_DIRECTION_OUT => ir.Output + case PORT_DIRECTION_IN => ir.Input + case PORT_DIRECTION_OUT => ir.Output + case (PORT_DIRECTION_UNKNOWN | UNRECOGNIZED) => + val msg = s"Unrecognized Port Direction value '$dir', perhaps this version of FIRRTL is too old?" + throw new FirrtlUserException(msg) } } From e32309412f7da6e0e50f4aae23baa79e61857f9b Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Fri, 22 Jan 2021 14:06:35 -0800 Subject: [PATCH 17/88] Bump to Scala 2.12.13 and 2.13.4 (#2053) --- build.sbt | 4 ++-- project/plugins.sbt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build.sbt b/build.sbt index f5a6a611df..e2c0b19e3d 100644 --- a/build.sbt +++ b/build.sbt @@ -21,8 +21,8 @@ lazy val commonSettings = Seq( organization := "edu.berkeley.cs", name := "firrtl", version := "1.5-SNAPSHOT", - scalaVersion := "2.12.12", - crossScalaVersions := Seq("2.13.2", "2.12.12", "2.11.12"), + scalaVersion := "2.12.13", + crossScalaVersions := Seq("2.13.4", "2.12.13", "2.11.12"), addCompilerPlugin(scalafixSemanticdb), scalacOptions := Seq( "-deprecation", diff --git a/project/plugins.sbt b/project/plugins.sbt index 3ed7accf48..888e09dab2 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -18,11 +18,11 @@ addSbtPlugin("com.simplytyped" % "sbt-antlr4" % "0.8.2") addSbtPlugin("com.github.gseitz" % "sbt-protobuf" % "0.6.5") -addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.19") +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.25") addSbtPlugin("com.thoughtworks.sbt-api-mappings" % "sbt-api-mappings" % "3.0.0") -addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.0") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.2") addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.8.1") From aec9e9e61f9b6775bf313601ec5a44a34f608609 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Tue, 26 Jan 2021 11:14:33 -0800 Subject: [PATCH 18/88] Fix post-merge publishing (#2055) * Check Unidoc on all versions of Scala It is required for publishing and we publish every version * Fix conflicting cross-version suffixes issue When running `sbt ++2.13.4 unidoc`, SBT would set the Scala version for the fuzzer and benchmark projects even though they aren't really relevant to the command. This may be a misconfiguration or a bug in the unidoc plugin. Whatever the case, simply making it possible for them to use the same version of Scala as the firrtl project (on which they depend) fixes the issue. * Match versions of Scala in build.sbt and CI * Fix unidoc issues in 2.13.4 There is some bug in ScalaDoc not finding some links in firrtl.options so those links were made absolute as a workaround. --- .github/workflows/test.yml | 5 ++--- build.sbt | 12 +++++++++--- src/main/scala/firrtl/FileUtils.scala | 2 +- src/main/scala/firrtl/LexerHelper.scala | 4 ++-- src/main/scala/firrtl/LoweringCompilers.scala | 10 +++++----- .../scala/firrtl/analyses/ConnectionGraph.scala | 2 +- .../scala/firrtl/analyses/InstanceGraph.scala | 2 +- .../scala/firrtl/analyses/InstanceKeyGraph.scala | 2 +- src/main/scala/firrtl/graph/DiGraph.scala | 10 +++++----- src/main/scala/firrtl/ir/Serializer.scala | 2 +- .../scala/firrtl/options/DependencyManager.scala | 16 ++++++++-------- src/main/scala/firrtl/options/Stage.scala | 4 ++-- .../scala/firrtl/passes/wiring/WiringUtils.scala | 8 ++++---- .../scala/firrtl/stage/FirrtlAnnotations.scala | 2 +- .../stage/phases/DriverCompatibility.scala | 8 ++++---- .../firrtl/transforms/ConstantPropagation.scala | 3 ++- .../firrtl/transforms/ManipulateNames.scala | 8 ++++---- src/main/scala/logger/LoggerOptions.scala | 2 +- .../AnalyzeCircuit.scala | 2 +- 19 files changed, 55 insertions(+), 49 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 18a680f5c9..31f7ca6af7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - scala: [2.13.2, 2.12.12, 2.11.12] + scala: [2.13.4, 2.12.13, 2.11.12] container: image: ucbbar/chisel3-tools options: --user github --entrypoint /bin/bash @@ -34,8 +34,7 @@ jobs: - name: Check Formatting (Scala 2.12 only) if: matrix.scala == '2.12.12' run: sbt ++${{ matrix.scala }} scalafmtCheckAll - - name: Unidoc builds (Scala 2.12 only) - if: matrix.scala == '2.12.12' + - name: Unidoc run: sbt ++${{ matrix.scala }} unidoc - name: Sanity check benchmarking scripts (Scala 2.12 only) if: matrix.scala == '2.12.12' diff --git a/build.sbt b/build.sbt index e2c0b19e3d..76908bb77d 100644 --- a/build.sbt +++ b/build.sbt @@ -16,13 +16,15 @@ def javacOptionsVersion(scalaVersion: String): Seq[String] = { } } - lazy val commonSettings = Seq( organization := "edu.berkeley.cs", + scalaVersion := "2.12.13", + crossScalaVersions := Seq("2.13.4", "2.12.13", "2.11.12") +) + +lazy val firrtlSettings = Seq( name := "firrtl", version := "1.5-SNAPSHOT", - scalaVersion := "2.12.13", - crossScalaVersions := Seq("2.13.4", "2.12.13", "2.11.12"), addCompilerPlugin(scalafixSemanticdb), scalacOptions := Seq( "-deprecation", @@ -170,6 +172,7 @@ lazy val firrtl = (project in file(".")) Test / testForkedParallel := true ) .settings(commonSettings) + .settings(firrtlSettings) .settings(protobufSettings) .settings(antlrSettings) .settings(assemblySettings) @@ -187,6 +190,7 @@ lazy val firrtl = (project in file(".")) lazy val benchmark = (project in file("benchmark")) .dependsOn(firrtl) + .settings(commonSettings) .settings( assemblyJarName in assembly := "firrtl-benchmark.jar", test in assembly := {}, @@ -196,6 +200,7 @@ lazy val benchmark = (project in file("benchmark")) val JQF_VERSION = "1.5" lazy val jqf = (project in file("jqf")) + .settings(commonSettings) .settings( libraryDependencies ++= Seq( "edu.berkeley.cs.jqf" % "jqf-fuzz" % JQF_VERSION, @@ -221,6 +226,7 @@ lazy val testClassAndMethodParser = { lazy val fuzzer = (project in file("fuzzer")) .dependsOn(firrtl) + .settings(commonSettings) .settings( libraryDependencies ++= Seq( "com.pholser" % "junit-quickcheck-core" % "0.8", diff --git a/src/main/scala/firrtl/FileUtils.scala b/src/main/scala/firrtl/FileUtils.scala index 2cc2961e87..f92d50cc1a 100644 --- a/src/main/scala/firrtl/FileUtils.scala +++ b/src/main/scala/firrtl/FileUtils.scala @@ -71,7 +71,7 @@ object FileUtils { val ioToDevNull = BasicIO(withIn = false, ProcessLogger(line => sb.append(line))) try { - cmd.run(ioToDevNull).exitValue == 0 + cmd.run(ioToDevNull).exitValue() == 0 } catch { case _: Throwable => false } diff --git a/src/main/scala/firrtl/LexerHelper.scala b/src/main/scala/firrtl/LexerHelper.scala index e1327cd75e..b755790a26 100644 --- a/src/main/scala/firrtl/LexerHelper.scala +++ b/src/main/scala/firrtl/LexerHelper.scala @@ -98,7 +98,7 @@ abstract class LexerHelper { if (tokenBuffer.isEmpty) pullToken() else - tokenBuffer.dequeue + tokenBuffer.dequeue() if (reachedEof) t @@ -157,6 +157,6 @@ abstract class LexerHelper { doPop() indentations.push(targetIndent) - tokenBuffer.dequeue + tokenBuffer.dequeue() } } diff --git a/src/main/scala/firrtl/LoweringCompilers.scala b/src/main/scala/firrtl/LoweringCompilers.scala index e27cafb7c4..e0bde64fd4 100644 --- a/src/main/scala/firrtl/LoweringCompilers.scala +++ b/src/main/scala/firrtl/LoweringCompilers.scala @@ -123,7 +123,7 @@ class NoneCompiler extends Compiler { ) class HighFirrtlCompiler extends Compiler { val emitter = new HighFirrtlEmitter - def transforms: Seq[Transform] = Forms.HighForm.map(_.getObject) + def transforms: Seq[Transform] = Forms.HighForm.map(_.getObject()) } /** Emits middle Firrtl input circuit */ @@ -133,7 +133,7 @@ class HighFirrtlCompiler extends Compiler { ) class MiddleFirrtlCompiler extends Compiler { val emitter = new MiddleFirrtlEmitter - def transforms: Seq[Transform] = Forms.MidForm.map(_.getObject) + def transforms: Seq[Transform] = Forms.MidForm.map(_.getObject()) } /** Emits lowered input circuit */ @@ -143,7 +143,7 @@ class MiddleFirrtlCompiler extends Compiler { ) class LowFirrtlCompiler extends Compiler { val emitter = new LowFirrtlEmitter - def transforms: Seq[Transform] = Forms.LowForm.map(_.getObject) + def transforms: Seq[Transform] = Forms.LowForm.map(_.getObject()) } /** Emits Verilog */ @@ -153,7 +153,7 @@ class LowFirrtlCompiler extends Compiler { ) class VerilogCompiler extends Compiler { val emitter = new VerilogEmitter - def transforms: Seq[Transform] = Forms.LowFormOptimized.map(_.getObject) + def transforms: Seq[Transform] = Forms.LowFormOptimized.map(_.getObject()) } /** Emits Verilog without optimizations */ @@ -163,7 +163,7 @@ class VerilogCompiler extends Compiler { ) class MinimumVerilogCompiler extends Compiler { val emitter = new MinimumVerilogEmitter - def transforms: Seq[Transform] = Forms.LowFormMinimumOptimized.map(_.getObject) + def transforms: Seq[Transform] = Forms.LowFormMinimumOptimized.map(_.getObject()) } /** Currently just an alias for the [[VerilogCompiler]] */ diff --git a/src/main/scala/firrtl/analyses/ConnectionGraph.scala b/src/main/scala/firrtl/analyses/ConnectionGraph.scala index 32bb156456..85cbe4df65 100644 --- a/src/main/scala/firrtl/analyses/ConnectionGraph.scala +++ b/src/main/scala/firrtl/analyses/ConnectionGraph.scala @@ -147,7 +147,7 @@ class ConnectionGraph protected (val circuit: Circuit, val digraph: DiGraph[Refe val bfsQueue = new mutable.PriorityQueue[ReferenceTarget]()(ordering) bfsQueue.enqueue(root) while (bfsQueue.nonEmpty) { - val u = bfsQueue.dequeue + val u = bfsQueue.dequeue() for (v <- getEdges(u)) { if (!prev.contains(v) && !blacklist.contains(v)) { prev(v) = u diff --git a/src/main/scala/firrtl/analyses/InstanceGraph.scala b/src/main/scala/firrtl/analyses/InstanceGraph.scala index 8858c4eac0..83a04ca389 100644 --- a/src/main/scala/firrtl/analyses/InstanceGraph.scala +++ b/src/main/scala/firrtl/analyses/InstanceGraph.scala @@ -45,7 +45,7 @@ class InstanceGraph(c: Circuit) { val topInstance = DefInstance(subTop, subTop) instanceQueue.enqueue(topInstance) while (instanceQueue.nonEmpty) { - val current = instanceQueue.dequeue + val current = instanceQueue.dequeue() instanceGraph.addVertex(current) for (child <- childInstances(current.module)) { if (!instanceGraph.contains(child)) { diff --git a/src/main/scala/firrtl/analyses/InstanceKeyGraph.scala b/src/main/scala/firrtl/analyses/InstanceKeyGraph.scala index 06c0b3774f..7584e3c84c 100644 --- a/src/main/scala/firrtl/analyses/InstanceKeyGraph.scala +++ b/src/main/scala/firrtl/analyses/InstanceKeyGraph.scala @@ -158,7 +158,7 @@ object InstanceKeyGraph { val instanceQueue = new mutable.Queue[InstanceKey] instanceQueue.enqueue(topInstance) while (instanceQueue.nonEmpty) { - val current = instanceQueue.dequeue + val current = instanceQueue.dequeue() instanceGraph.addVertex(current) for (child <- childInstanceMap(current.module)) { if (!instanceGraph.contains(child)) { diff --git a/src/main/scala/firrtl/graph/DiGraph.scala b/src/main/scala/firrtl/graph/DiGraph.scala index b99c696115..3a08d05e1c 100644 --- a/src/main/scala/firrtl/graph/DiGraph.scala +++ b/src/main/scala/firrtl/graph/DiGraph.scala @@ -153,7 +153,7 @@ class DiGraph[T](private[graph] val edges: LinkedHashMap[T, LinkedHashSet[T]]) { val queue = new mutable.Queue[T] queue.enqueue(root) while (queue.nonEmpty) { - val u = queue.dequeue + val u = queue.dequeue() for (v <- getEdges(u)) { if (!prev.contains(v) && !blacklist.contains(v)) { prev(v) = u @@ -257,7 +257,7 @@ class DiGraph[T](private[graph] val edges: LinkedHashMap[T, LinkedHashSet[T]]) { } frame.childCall = None while (frame.edgeIter.hasNext && frame.childCall.isEmpty) { - val w = frame.edgeIter.next + val w = frame.edgeIter.next() if (!indices.contains(w)) { frame.childCall = Some(w) callStack.push(new StrongConnectFrame(w, getEdges(w).iterator)) @@ -269,13 +269,13 @@ class DiGraph[T](private[graph] val edges: LinkedHashMap[T, LinkedHashSet[T]]) { if (lowlinks(v) == indices(v)) { val scc = new mutable.ArrayBuffer[T] do { - val w = stack.pop + val w = stack.pop() onstack -= w scc += w } while (scc.last != v); sccs.append(scc.toSeq) } - callStack.pop + callStack.pop() } } } @@ -305,7 +305,7 @@ class DiGraph[T](private[graph] val edges: LinkedHashMap[T, LinkedHashSet[T]]) { queue += start queue ++= linearize.filter(reachable.contains(_)) while (!queue.isEmpty) { - val current = queue.dequeue + val current = queue.dequeue() for (v <- getEdges(current)) { for (p <- paths(current)) { addBinding(v, p :+ v) diff --git a/src/main/scala/firrtl/ir/Serializer.scala b/src/main/scala/firrtl/ir/Serializer.scala index 7fb30f1957..983a786620 100644 --- a/src/main/scala/firrtl/ir/Serializer.scala +++ b/src/main/scala/firrtl/ir/Serializer.scala @@ -99,7 +99,7 @@ object Serializer { case Block(stmts) => val it = stmts.iterator while (it.hasNext) { - s(it.next) + s(it.next()) if (it.hasNext) newLineAndIndent() } case Stop(info, ret, clk, en) => diff --git a/src/main/scala/firrtl/options/DependencyManager.scala b/src/main/scala/firrtl/options/DependencyManager.scala index 39998ed7fb..5e9119401d 100644 --- a/src/main/scala/firrtl/options/DependencyManager.scala +++ b/src/main/scala/firrtl/options/DependencyManager.scala @@ -91,7 +91,7 @@ trait DependencyManager[A, B <: TransformLike[A] with DependencyAPI[B]] extends } while (queue.nonEmpty) { - val u: Dependency[B] = queue.dequeue + val u: Dependency[B] = queue.dequeue() for (v <- extractor(dependencyToObject(u))) { if (!blacklist.contains(v) && !edges.contains(v)) { queue.enqueue(v) @@ -193,13 +193,13 @@ trait DependencyManager[A, B <: TransformLike[A] with DependencyAPI[B]] extends ) } - /** An ordering of [[firrtl.options.TransformLike TransformLike]]s that causes the requested [[DependencyManager.targets - * targets]] to be executed starting from the [[DependencyManager.currentState currentState]]. This ordering respects + /** An ordering of [[firrtl.options.TransformLike TransformLike]]s that causes the requested [[firrtl.options.DependencyManager.targets + * targets]] to be executed starting from the [[firrtl.options.DependencyManager.currentState currentState]]. This ordering respects * prerequisites, optionalPrerequisites, optionalPrerequisiteOf, and invalidates of all constituent * [[firrtl.options.TransformLike TransformLike]]s. This uses an algorithm that attempts to reduce the number of - * re-lowerings due to invalidations. Re-lowerings are implemented as new [[DependencyManager]]s. - * @throws DependencyManagerException if a cycle exists in either the [[DependencyManager.dependencyGraph - * dependencyGraph]] or the [[DependencyManager.invalidateGraph invalidateGraph]]. + * re-lowerings due to invalidations. Re-lowerings are implemented as new [[firrtl.options.DependencyManager]]s. + * @throws firrtl.options.DependencyManagerException if a cycle exists in either the [[firrtl.options.DependencyManager.dependencyGraph + * dependencyGraph]] or the [[firrtl.options.DependencyManager.invalidateGraph invalidateGraph]]. */ lazy val transformOrder: Seq[B] = { @@ -244,8 +244,8 @@ trait DependencyManager[A, B <: TransformLike[A] with DependencyAPI[B]] extends l ++ postprocessing } - /** A version of the [[DependencyManager.transformOrder transformOrder]] that flattens the transforms of any internal - * [[DependencyManager]]s. + /** A version of the [[firrtl.options.DependencyManager.transformOrder transformOrder]] that flattens the transforms of any internal + * [[firrtl.options.DependencyManager DependencyManager]]s. */ lazy val flattenedTransformOrder: Seq[B] = transformOrder.flatMap { case p: DependencyManager[A, B] => p.flattenedTransformOrder diff --git a/src/main/scala/firrtl/options/Stage.scala b/src/main/scala/firrtl/options/Stage.scala index 0783bfb5e7..cefdd95793 100644 --- a/src/main/scala/firrtl/options/Stage.scala +++ b/src/main/scala/firrtl/options/Stage.scala @@ -28,7 +28,7 @@ abstract class Stage extends Phase { /** Execute this stage on some input annotations. Annotations will be read from any input annotation files. * @param annotations input annotations * @return output annotations - * @throws OptionsException if command line or annotation validation fails + * @throws firrtl.options.OptionsException if command line or annotation validation fails */ final def transform(annotations: AnnotationSeq): AnnotationSeq = { val annotationsx = @@ -52,7 +52,7 @@ abstract class Stage extends Phase { * @param args command line arguments * @param initialAnnotations annotation * @return output annotations - * @throws OptionsException if command line or annotation validation fails + * @throws firrtl.options.OptionsException if command line or annotation validation fails */ final def execute(args: Array[String], annotations: AnnotationSeq): AnnotationSeq = transform(shell.parse(args, annotations)) diff --git a/src/main/scala/firrtl/passes/wiring/WiringUtils.scala b/src/main/scala/firrtl/passes/wiring/WiringUtils.scala index cab6aa5f60..6f9b4f83c4 100644 --- a/src/main/scala/firrtl/passes/wiring/WiringUtils.scala +++ b/src/main/scala/firrtl/passes/wiring/WiringUtils.scala @@ -150,11 +150,11 @@ object WiringUtils { * sources/sinks not under sinks/sources. */ if (queue.size == 1) { - val u = queue.dequeue + val u = queue.dequeue() sinkInsts.foreach { v => owners(v) = Vector(u) } } else { while (queue.nonEmpty) { - val u = queue.dequeue + val u = queue.dequeue() visited(u) = true val edges = (i.graph.getEdges(u.last).map(u :+ _).toVector :+ u.dropRight(1)) @@ -222,11 +222,11 @@ object WiringUtils { * sources/sinks not under sinks/sources. */ if (queue.size == 1) { - val u = queue.dequeue + val u = queue.dequeue() sinkInsts.foreach { v => owners(v) = Vector(u) } } else { while (queue.nonEmpty) { - val u = queue.dequeue + val u = queue.dequeue() visited(u) = true val edges = i.graph.getEdges(u.last).map(u :+ _).toVector :+ u.dropRight(1) diff --git a/src/main/scala/firrtl/stage/FirrtlAnnotations.scala b/src/main/scala/firrtl/stage/FirrtlAnnotations.scala index 2ac74de20d..7d0d237a9f 100644 --- a/src/main/scala/firrtl/stage/FirrtlAnnotations.scala +++ b/src/main/scala/firrtl/stage/FirrtlAnnotations.scala @@ -180,7 +180,7 @@ case class RunFirrtlTransformAnnotation(transform: Transform) extends NoTargetAn object RunFirrtlTransformAnnotation extends HasShellOptions { def apply(transform: TransformDependency): RunFirrtlTransformAnnotation = - RunFirrtlTransformAnnotation(transform.getObject) + RunFirrtlTransformAnnotation(transform.getObject()) private[firrtl] def stringToEmitter(a: String): RunFirrtlTransformAnnotation = { val emitter = a match { diff --git a/src/main/scala/firrtl/stage/phases/DriverCompatibility.scala b/src/main/scala/firrtl/stage/phases/DriverCompatibility.scala index a37774e5ae..7ad77212dd 100644 --- a/src/main/scala/firrtl/stage/phases/DriverCompatibility.scala +++ b/src/main/scala/firrtl/stage/phases/DriverCompatibility.scala @@ -58,8 +58,8 @@ object DriverCompatibility { def addOptions(p: OptionParser[AnnotationSeq]): Unit = p .opt[Unit]("top-name") .abbr("tn") - .hidden - .unbounded + .hidden() + .unbounded() .action((_, _) => throw new OptionsException(optionRemoved("--top-name/-tn"))) } @@ -71,8 +71,8 @@ object DriverCompatibility { def addOptions(p: OptionParser[AnnotationSeq]): Unit = p .opt[Unit]("split-modules") .abbr("fsm") - .hidden - .unbounded + .hidden() + .unbounded() .action((_, _) => throw new OptionsException(optionRemoved("--split-modules/-fsm"))) } diff --git a/src/main/scala/firrtl/transforms/ConstantPropagation.scala b/src/main/scala/firrtl/transforms/ConstantPropagation.scala index c89fcff196..e411544183 100644 --- a/src/main/scala/firrtl/transforms/ConstantPropagation.scala +++ b/src/main/scala/firrtl/transforms/ConstantPropagation.scala @@ -647,7 +647,8 @@ class ConstantPropagation extends Transform with DependencyAPIMigration { case WRef(rname, _, kind, _) if betterName(lname, rname) && !swapMap.contains(rname) && kind != PortKind => assert(!swapMap.contains(lname)) // <- Shouldn't be possible because lname is either a // node declaration or the single connection to a wire or register - swapMap += (lname -> rname, rname -> lname) + swapMap += lname -> rname + swapMap += rname -> lname case _ => } nodeMap(lname) = InfoExpr.wrap(info, value) diff --git a/src/main/scala/firrtl/transforms/ManipulateNames.scala b/src/main/scala/firrtl/transforms/ManipulateNames.scala index 7be876ef0c..4a796e58c3 100644 --- a/src/main/scala/firrtl/transforms/ManipulateNames.scala +++ b/src/main/scala/firrtl/transforms/ManipulateNames.scala @@ -464,7 +464,7 @@ abstract class ManipulateNames[A <: ManipulateNames[_]: ClassTag] extends Transf val block = state.annotations.collect { case ManipulateNamesBlocklistAnnotation(targetSeq, t) => - t.getObject match { + t.getObject() match { case _: A => targetSeq case _ => Nil } @@ -473,7 +473,7 @@ abstract class ManipulateNames[A <: ManipulateNames[_]: ClassTag] extends Transf val allow = { val allowx = state.annotations.collect { case ManipulateNamesAllowlistAnnotation(targetSeq, t) => - t.getObject match { + t.getObject() match { case _: A => targetSeq case _ => Nil } @@ -491,13 +491,13 @@ abstract class ManipulateNames[A <: ManipulateNames[_]: ClassTag] extends Transf val annotationsx = state.annotations.flatMap { /* Consume blocklist annotations */ case foo @ ManipulateNamesBlocklistAnnotation(_, t) => - t.getObject match { + t.getObject() match { case _: A => None case _ => Some(foo) } /* Convert allowlist annotations to result annotations */ case foo @ ManipulateNamesAllowlistAnnotation(a, t) => - t.getObject match { + t.getObject() match { case _: A => (a, a.map(_.map(renames(_)).flatten)) match { case (a, b) => Some(ManipulateNamesAllowlistResultAnnotation(b, t, a)) diff --git a/src/main/scala/logger/LoggerOptions.scala b/src/main/scala/logger/LoggerOptions.scala index 683d6741f7..bfd072df24 100644 --- a/src/main/scala/logger/LoggerOptions.scala +++ b/src/main/scala/logger/LoggerOptions.scala @@ -32,7 +32,7 @@ class LoggerOptions private[logger] ( } /** Return the name of the log file, defaults to `a.log` if unspecified */ - def getLogFileName(): Option[String] = if (!logToFile) None else logFileName.orElse(Some("a.log")) + def getLogFileName(): Option[String] = if (!logToFile()) None else logFileName.orElse(Some("a.log")) /** True if a [[Logger]] should be writing to a file */ @deprecated("logToFile was removed, use logFileName.nonEmpty", "FIRRTL 1.2") diff --git a/src/main/scala/tutorial/lesson1-circuit-traversal/AnalyzeCircuit.scala b/src/main/scala/tutorial/lesson1-circuit-traversal/AnalyzeCircuit.scala index 23ecb114ca..bab67a74e3 100644 --- a/src/main/scala/tutorial/lesson1-circuit-traversal/AnalyzeCircuit.scala +++ b/src/main/scala/tutorial/lesson1-circuit-traversal/AnalyzeCircuit.scala @@ -142,7 +142,7 @@ class AnalyzeCircuit extends Transform { visited match { // If e is a [[firrtl.ir.Mux Mux]], increment our ledger and return e. case Mux(cond, tval, fval, tpe) => - ledger.foundMux + ledger.foundMux() e // If e is not a [[firrtl.ir.Mux Mux]], return e. case notmux => notmux From 651fbe9339aca5fcb562715d00b1f87cf66296ee Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Thu, 28 Jan 2021 18:39:57 -0800 Subject: [PATCH 19/88] Stop padding multiply and divide ops (#2058) Fixes bug with mul or div followed by cat. Also fixes some Verilog lint issues. --- src/main/scala/firrtl/passes/PadWidths.scala | 2 +- .../scala/firrtl/testutils/FirrtlSpec.scala | 42 ++++++ .../firrtlTests/VerilogEmitterTests.scala | 24 ++++ .../firrtlTests/VerilogEquivalenceSpec.scala | 123 ++++++++++++++++++ 4 files changed, 190 insertions(+), 1 deletion(-) create mode 100644 src/test/scala/firrtlTests/VerilogEquivalenceSpec.scala diff --git a/src/main/scala/firrtl/passes/PadWidths.scala b/src/main/scala/firrtl/passes/PadWidths.scala index 875e80ae59..1a430778ff 100644 --- a/src/main/scala/firrtl/passes/PadWidths.scala +++ b/src/main/scala/firrtl/passes/PadWidths.scala @@ -58,7 +58,7 @@ object PadWidths extends Pass { case ex: ValidIf => ex.copy(value = fixup(width(ex.tpe))(ex.value)) case ex: DoPrim => ex.op match { - case Lt | Leq | Gt | Geq | Eq | Neq | Not | And | Or | Xor | Add | Sub | Mul | Div | Rem | Shr => + case Lt | Leq | Gt | Geq | Eq | Neq | Not | And | Or | Xor | Add | Sub | Rem | Shr => // sensitive ops ex.map(fixup((ex.args.map(width).foldLeft(0))(math.max))) case Dshl => diff --git a/src/test/scala/firrtl/testutils/FirrtlSpec.scala b/src/test/scala/firrtl/testutils/FirrtlSpec.scala index 3a6f937237..6de2af1e9a 100644 --- a/src/test/scala/firrtl/testutils/FirrtlSpec.scala +++ b/src/test/scala/firrtl/testutils/FirrtlSpec.scala @@ -122,6 +122,48 @@ trait FirrtlRunners extends BackendCompilationUtilities { assert(BackendCompilationUtilities.yosysExpectSuccess(customName, refName, testDir, timesteps)) } + /** Check equivalence of Firrtl with reference Verilog + * + * @note the name of the reference Verilog module is grabbed via regex + * @param inputFirrtl string containing Firrtl source + * @param referenceVerilog Verilog that will be used as reference for LEC + * @param timesteps the maximum number of timesteps to consider + */ + def firrtlEquivalenceWithVerilog( + inputFirrtl: String, + referenceVerilog: String, + timesteps: Int = 1 + ): Unit = { + val VerilogModule = """(?s).*module\s(\w+).*""".r + val refName = referenceVerilog match { + case VerilogModule(name) => name + case _ => throw new Exception(s"Reference Verilog must match simple regex! $VerilogModule") + } + val circuit = Parser.parse(inputFirrtl.split("\n").toIterator) + val inputName = circuit.main + require(refName != inputName, s"Name of reference Verilog must not match name of input FIRRTL: $refName") + + val testDir = createTestDirectory(inputName + "_equivalence_test") + + val annos = List( + TargetDirAnnotation(testDir.toString), + InfoModeAnnotation("ignore"), + stage.FirrtlCircuitAnnotation(circuit), + stage.RunFirrtlTransformAnnotation.stringToEmitter("verilog"), + stage.OutputFileAnnotation(inputName) + ) + + (new firrtl.stage.FirrtlStage).execute(Array(), annos) + + // Write reference + val w = new FileWriter(new File(testDir, s"$refName.v")) + w.write(referenceVerilog) + w.close() + + assert(BackendCompilationUtilities.yosysExpectSuccess(inputName, refName, testDir, timesteps)) + } + + /** Compiles input Firrtl to Verilog */ def compileToVerilog(input: String, annotations: AnnotationSeq = Seq.empty): String = { val circuit = Parser.parse(input.split("\n").toIterator) diff --git a/src/test/scala/firrtlTests/VerilogEmitterTests.scala b/src/test/scala/firrtlTests/VerilogEmitterTests.scala index 7704a0a20c..ec30e55c87 100644 --- a/src/test/scala/firrtlTests/VerilogEmitterTests.scala +++ b/src/test/scala/firrtlTests/VerilogEmitterTests.scala @@ -754,6 +754,30 @@ class VerilogEmitterSpec extends FirrtlFlatSpec { result should containLine("assign z = _GEN_0[1:0];") } + it should "not pad multiplication" in { + val compiler = new VerilogCompiler + val result = compileBody( + """input x : UInt<2> + |input y: UInt<4> + |output z : UInt<6> + |z <= mul(x, y) + |""".stripMargin + ) + result should containLine("assign z = x * y;") + } + + it should "not pad division" in { + val compiler = new VerilogCompiler + val result = compileBody( + """input x : UInt<4> + |input y: UInt<2> + |output z : UInt<4> + |z <= div(x, y) + |""".stripMargin + ) + result should containLine("assign z = x / y;") + } + it should "correctly emit addition with a negative literal with width > 32" in { val result = compileBody( """input x : SInt<34> diff --git a/src/test/scala/firrtlTests/VerilogEquivalenceSpec.scala b/src/test/scala/firrtlTests/VerilogEquivalenceSpec.scala new file mode 100644 index 0000000000..d88309ced0 --- /dev/null +++ b/src/test/scala/firrtlTests/VerilogEquivalenceSpec.scala @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: Apache-2.0 + +package firrtlTests + +import firrtl.testutils._ + +class VerilogEquivalenceSpec extends FirrtlFlatSpec { + "mul followed by cat" should "be correct" in { + val header = s""" + |circuit Multiply : + | module Multiply : + | input x : UInt<4> + | input y : UInt<2> + | input z : UInt<2> + | output out : UInt<8> + |""".stripMargin + val input1 = header + """ + | out <= cat(z, mul(x, y))""".stripMargin + val input2 = header + """ + | node n = mul(x, y) + | node m = cat(z, n) + | out <= m""".stripMargin + val expected = s""" + |module MultiplyRef( + | input [3:0] x, + | input [1:0] y, + | input [1:0] z, + | output [7:0] out + |); + | wire [5:0] w = x * y; + | assign out = {z, w}; + |endmodule""".stripMargin + firrtlEquivalenceWithVerilog(input1, expected) + firrtlEquivalenceWithVerilog(input2, expected) + } + + "div followed by cat" should "be correct" in { + val header = s""" + |circuit Divide : + | module Divide : + | input x : UInt<4> + | input y : UInt<2> + | input z : UInt<2> + | output out : UInt<6> + |""".stripMargin + val input1 = header + """ + | out <= cat(z, div(x, y))""".stripMargin + val input2 = header + """ + | node n = div(x, y) + | node m = cat(z, n) + | out <= m""".stripMargin + val expected = s""" + |module DivideRef( + | input [3:0] x, + | input [1:0] y, + | input [1:0] z, + | output [5:0] out + |); + | wire [3:0] w = x / y; + | assign out = {z, w}; + |endmodule""".stripMargin + firrtlEquivalenceWithVerilog(input1, expected) + firrtlEquivalenceWithVerilog(input2, expected) + } + + "signed mul followed by cat" should "be correct" in { + val header = s""" + |circuit SignedMultiply : + | module SignedMultiply : + | input x : SInt<4> + | input y : SInt<2> + | input z : SInt<2> + | output out : UInt<8> + |""".stripMargin + val input1 = header + """ + | out <= cat(z, mul(x, y))""".stripMargin + val input2 = header + """ + | node n = mul(x, y) + | node m = cat(z, n) + | out <= m""".stripMargin + val expected = s""" + |module SignedMultiplyRef( + | input signed [3:0] x, + | input signed [1:0] y, + | input signed [1:0] z, + | output [7:0] out + |); + | wire [5:0] w = x * y; + | assign out = {z, w}; + |endmodule""".stripMargin + firrtlEquivalenceWithVerilog(input1, expected) + firrtlEquivalenceWithVerilog(input2, expected) + } + + "signed div followed by cat" should "be correct" in { + val header = s""" + |circuit SignedDivide : + | module SignedDivide : + | input x : SInt<4> + | input y : SInt<2> + | input z : SInt<2> + | output out : UInt<7> + |""".stripMargin + val input1 = header + """ + | out <= cat(z, div(x, y))""".stripMargin + val input2 = header + """ + | node n = div(x, y) + | node m = cat(z, n) + | out <= m""".stripMargin + val expected = s""" + |module SignedDivideRef( + | input signed [3:0] x, + | input signed [1:0] y, + | input signed [1:0] z, + | output [6:0] out + |); + | wire [4:0] w = x / y; + | assign out = {z, w}; + |endmodule""".stripMargin + firrtlEquivalenceWithVerilog(input1, expected) + firrtlEquivalenceWithVerilog(input2, expected) + } +} From ad0fd6579dfe5fc19c67c0453adbfbcfbd63122d Mon Sep 17 00:00:00 2001 From: Kevin Laeufer Date: Mon, 1 Feb 2021 10:51:33 -0800 Subject: [PATCH 20/88] ConstantPropagation: make RemoveValidIf an optional dependency (#2027) This allows ConstantPropagation to be used in cases where ValidIfs need to be maintained, e.g., in the formal backend. Co-authored-by: Adam Izraelevitz Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- src/main/scala/firrtl/transforms/ConstantPropagation.scala | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/scala/firrtl/transforms/ConstantPropagation.scala b/src/main/scala/firrtl/transforms/ConstantPropagation.scala index e411544183..0523082dd2 100644 --- a/src/main/scala/firrtl/transforms/ConstantPropagation.scala +++ b/src/main/scala/firrtl/transforms/ConstantPropagation.scala @@ -107,10 +107,9 @@ class ConstantPropagation extends Transform with DependencyAPIMigration { override def prerequisites = ((new mutable.LinkedHashSet()) ++ firrtl.stage.Forms.LowForm - - Dependency(firrtl.passes.Legalize) - + Dependency(firrtl.passes.RemoveValidIf)).toSeq + - Dependency(firrtl.passes.Legalize)).toSeq - override def optionalPrerequisites = Seq.empty + override def optionalPrerequisites = Seq(Dependency(firrtl.passes.RemoveValidIf)) override def optionalPrerequisiteOf = Seq( From fd5d4422df395064c87ec415a27a03d5a052088b Mon Sep 17 00:00:00 2001 From: Schuyler Eldridge Date: Mon, 1 Feb 2021 14:03:43 -0500 Subject: [PATCH 21/88] Deprecate ToWorkingIR (#2028) * Deprecate firrtl.passes.ToWorkingIR Deprecate ToWorkingIR as it is now an identity transform. Signed-off-by: Schuyler Eldridge * Deprecate firrtl.stage.Forms.WorkingIR Signed-off-by: Schuyler Eldridge * Switch from Forms.WorkingIR to Forms.MinimalHighForm Signed-off-by: Schuyler Eldridge Co-authored-by: Jack Koenig Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- src/main/scala/firrtl/LoweringCompilers.scala | 8 ++++++-- src/main/scala/firrtl/passes/CheckFlows.scala | 2 +- src/main/scala/firrtl/passes/CheckHighForm.scala | 2 +- src/main/scala/firrtl/passes/CheckTypes.scala | 2 +- src/main/scala/firrtl/passes/CheckWidths.scala | 2 +- src/main/scala/firrtl/passes/InferTypes.scala | 2 +- src/main/scala/firrtl/passes/InferWidths.scala | 2 +- src/main/scala/firrtl/passes/ResolveFlows.scala | 2 +- src/main/scala/firrtl/passes/ResolveKinds.scala | 2 +- src/main/scala/firrtl/passes/ToWorkingIR.scala | 7 +++++++ src/main/scala/firrtl/passes/Uniquify.scala | 2 +- src/main/scala/firrtl/passes/wiring/WiringTransform.scala | 3 +-- src/main/scala/firrtl/stage/Forms.scala | 6 +++--- src/main/scala/firrtl/transforms/InferResets.scala | 2 +- src/main/scala/firrtl/transforms/LegalizeReductions.scala | 2 +- src/test/scala/firrtlTests/LoweringCompilersSpec.scala | 5 ++++- 16 files changed, 32 insertions(+), 19 deletions(-) diff --git a/src/main/scala/firrtl/LoweringCompilers.scala b/src/main/scala/firrtl/LoweringCompilers.scala index e0bde64fd4..261465e26f 100644 --- a/src/main/scala/firrtl/LoweringCompilers.scala +++ b/src/main/scala/firrtl/LoweringCompilers.scala @@ -31,7 +31,11 @@ class ChirrtlToHighFirrtl extends CoreTransform { class IRToWorkingIR extends CoreTransform { def inputForm = HighForm def outputForm = HighForm - def transforms = new TransformManager(Forms.WorkingIR, Forms.MinimalHighForm).flattenedTransformOrder + def transforms = Seq( + new Transform with firrtl.options.IdentityLike[CircuitState] with DependencyAPIMigration { + override def execute(a: CircuitState) = transform(a) + } + ) } /** Resolves types, kinds, and flows, and checks the circuit legality. @@ -44,7 +48,7 @@ class IRToWorkingIR extends CoreTransform { class ResolveAndCheck extends CoreTransform { def inputForm = HighForm def outputForm = HighForm - def transforms = new TransformManager(Forms.Resolved, Forms.WorkingIR).flattenedTransformOrder + def transforms = new TransformManager(Forms.Resolved, Forms.MinimalHighForm).flattenedTransformOrder } /** Expands aggregate connects, removes dynamic accesses, and when diff --git a/src/main/scala/firrtl/passes/CheckFlows.scala b/src/main/scala/firrtl/passes/CheckFlows.scala index 2816887f55..f78a115a04 100644 --- a/src/main/scala/firrtl/passes/CheckFlows.scala +++ b/src/main/scala/firrtl/passes/CheckFlows.scala @@ -10,7 +10,7 @@ import firrtl.options.Dependency object CheckFlows extends Pass { - override def prerequisites = Dependency(passes.ResolveFlows) +: firrtl.stage.Forms.WorkingIR + override def prerequisites = Dependency(passes.ResolveFlows) +: firrtl.stage.Forms.MinimalHighForm override def optionalPrerequisiteOf = Seq( diff --git a/src/main/scala/firrtl/passes/CheckHighForm.scala b/src/main/scala/firrtl/passes/CheckHighForm.scala index 7e305b3352..e3468c4e8c 100644 --- a/src/main/scala/firrtl/passes/CheckHighForm.scala +++ b/src/main/scala/firrtl/passes/CheckHighForm.scala @@ -348,7 +348,7 @@ trait CheckHighFormLike { this: Pass => object CheckHighForm extends Pass with CheckHighFormLike { - override def prerequisites = firrtl.stage.Forms.WorkingIR + override def prerequisites = firrtl.stage.Forms.MinimalHighForm override def optionalPrerequisiteOf = Seq( diff --git a/src/main/scala/firrtl/passes/CheckTypes.scala b/src/main/scala/firrtl/passes/CheckTypes.scala index bbccb35395..f70db14833 100644 --- a/src/main/scala/firrtl/passes/CheckTypes.scala +++ b/src/main/scala/firrtl/passes/CheckTypes.scala @@ -13,7 +13,7 @@ import firrtl.options.Dependency object CheckTypes extends Pass { - override def prerequisites = Dependency(InferTypes) +: firrtl.stage.Forms.WorkingIR + override def prerequisites = Dependency(InferTypes) +: firrtl.stage.Forms.MinimalHighForm override def optionalPrerequisiteOf = Seq( diff --git a/src/main/scala/firrtl/passes/CheckWidths.scala b/src/main/scala/firrtl/passes/CheckWidths.scala index 3f30e1243d..150a1bc8c0 100644 --- a/src/main/scala/firrtl/passes/CheckWidths.scala +++ b/src/main/scala/firrtl/passes/CheckWidths.scala @@ -13,7 +13,7 @@ import firrtl.options.Dependency object CheckWidths extends Pass { - override def prerequisites = Dependency[passes.InferWidths] +: firrtl.stage.Forms.WorkingIR + override def prerequisites = Dependency[passes.InferWidths] +: firrtl.stage.Forms.MinimalHighForm override def optionalPrerequisiteOf = Seq(Dependency[transforms.InferResets]) diff --git a/src/main/scala/firrtl/passes/InferTypes.scala b/src/main/scala/firrtl/passes/InferTypes.scala index 01f0b823bc..8ab78fee6b 100644 --- a/src/main/scala/firrtl/passes/InferTypes.scala +++ b/src/main/scala/firrtl/passes/InferTypes.scala @@ -10,7 +10,7 @@ import firrtl.options.Dependency object InferTypes extends Pass { - override def prerequisites = Dependency(ResolveKinds) +: firrtl.stage.Forms.WorkingIR + override def prerequisites = Dependency(ResolveKinds) +: firrtl.stage.Forms.MinimalHighForm override def invalidates(a: Transform) = false @deprecated("This should never have been public", "FIRRTL 1.3.2") diff --git a/src/main/scala/firrtl/passes/InferWidths.scala b/src/main/scala/firrtl/passes/InferWidths.scala index 56cd4dd290..d0677fadd5 100644 --- a/src/main/scala/firrtl/passes/InferWidths.scala +++ b/src/main/scala/firrtl/passes/InferWidths.scala @@ -71,7 +71,7 @@ class InferWidths extends Transform with ResolvedAnnotationPaths with Dependency Dependency(passes.ResolveFlows), Dependency[passes.InferBinaryPoints], Dependency[passes.TrimIntervals] - ) ++ firrtl.stage.Forms.WorkingIR + ) ++ firrtl.stage.Forms.MinimalHighForm override def invalidates(a: Transform) = false val annotationClasses = Seq(classOf[WidthGeqConstraintAnnotation]) diff --git a/src/main/scala/firrtl/passes/ResolveFlows.scala b/src/main/scala/firrtl/passes/ResolveFlows.scala index 1af87c10dc..a8672f55b5 100644 --- a/src/main/scala/firrtl/passes/ResolveFlows.scala +++ b/src/main/scala/firrtl/passes/ResolveFlows.scala @@ -9,7 +9,7 @@ import firrtl.options.Dependency object ResolveFlows extends Pass { - override def prerequisites = Seq(Dependency(passes.InferTypes)) ++ firrtl.stage.Forms.WorkingIR + override def prerequisites = Seq(Dependency(passes.InferTypes)) ++ firrtl.stage.Forms.MinimalHighForm override def invalidates(a: Transform) = false diff --git a/src/main/scala/firrtl/passes/ResolveKinds.scala b/src/main/scala/firrtl/passes/ResolveKinds.scala index 25be003dc9..e3218467c1 100644 --- a/src/main/scala/firrtl/passes/ResolveKinds.scala +++ b/src/main/scala/firrtl/passes/ResolveKinds.scala @@ -9,7 +9,7 @@ import firrtl.traversals.Foreachers._ object ResolveKinds extends Pass { - override def prerequisites = firrtl.stage.Forms.WorkingIR + override def prerequisites = firrtl.stage.Forms.MinimalHighForm override def invalidates(a: Transform) = false diff --git a/src/main/scala/firrtl/passes/ToWorkingIR.scala b/src/main/scala/firrtl/passes/ToWorkingIR.scala index 2a81449762..12a9bdd762 100644 --- a/src/main/scala/firrtl/passes/ToWorkingIR.scala +++ b/src/main/scala/firrtl/passes/ToWorkingIR.scala @@ -5,8 +5,15 @@ package firrtl.passes import firrtl.ir._ import firrtl.Transform +@deprecated( + "This pass is an identity transform. For an equivalent dependency, use firrtl.stage.forms.MinimalHighForm", + "FIRRTL 1.4.2" +) object ToWorkingIR extends Pass { override def prerequisites = firrtl.stage.Forms.MinimalHighForm + override def optionalPrerequisites = Seq.empty + override def optionalPrerequisiteOf = + (firrtl.stage.Forms.LowFormOptimized.toSet -- firrtl.stage.Forms.MinimalHighForm).toSeq override def invalidates(a: Transform) = false def run(c: Circuit): Circuit = c } diff --git a/src/main/scala/firrtl/passes/Uniquify.scala b/src/main/scala/firrtl/passes/Uniquify.scala index 8191628246..20ae8db538 100644 --- a/src/main/scala/firrtl/passes/Uniquify.scala +++ b/src/main/scala/firrtl/passes/Uniquify.scala @@ -35,7 +35,7 @@ import MemPortUtils.memType object Uniquify extends Transform with DependencyAPIMigration { override def prerequisites = - Seq(Dependency(ResolveKinds), Dependency(InferTypes)) ++ firrtl.stage.Forms.WorkingIR + Seq(Dependency(ResolveKinds), Dependency(InferTypes)) ++ firrtl.stage.Forms.MinimalHighForm override def invalidates(a: Transform): Boolean = a match { case ResolveKinds | InferTypes => true diff --git a/src/main/scala/firrtl/passes/wiring/WiringTransform.scala b/src/main/scala/firrtl/passes/wiring/WiringTransform.scala index 4f6705ec3f..86afe520ad 100644 --- a/src/main/scala/firrtl/passes/wiring/WiringTransform.scala +++ b/src/main/scala/firrtl/passes/wiring/WiringTransform.scala @@ -47,8 +47,7 @@ class WiringTransform extends Transform with DependencyAPIMigration { /** Defines the sequence of Transform that should be applied */ private def transforms(w: Seq[WiringInfo]): Seq[Transform] = Seq( - new Wiring(w), - ToWorkingIR + new Wiring(w) ) def execute(state: CircuitState): CircuitState = { val annos = state.annotations.collect { diff --git a/src/main/scala/firrtl/stage/Forms.scala b/src/main/scala/firrtl/stage/Forms.scala index a6077f525c..ab08215102 100644 --- a/src/main/scala/firrtl/stage/Forms.scala +++ b/src/main/scala/firrtl/stage/Forms.scala @@ -25,7 +25,8 @@ object Forms { Dependency[annotations.transforms.CleanupNamedTargets] ) - val WorkingIR: Seq[TransformDependency] = MinimalHighForm :+ Dependency(passes.ToWorkingIR) + @deprecated("Use firrtl.stage.forms.MinimalHighForm", "FIRRTL 1.4.2") + val WorkingIR: Seq[TransformDependency] = MinimalHighForm val Checks: Seq[TransformDependency] = Seq( @@ -35,7 +36,7 @@ object Forms { Dependency(passes.CheckWidths) ) - val Resolved: Seq[TransformDependency] = WorkingIR ++ Checks ++ + val Resolved: Seq[TransformDependency] = MinimalHighForm ++ Checks ++ Seq( Dependency(passes.ResolveKinds), Dependency(passes.InferTypes), @@ -50,7 +51,6 @@ object Forms { val HighForm: Seq[TransformDependency] = ChirrtlForm ++ MinimalHighForm ++ - WorkingIR ++ Resolved ++ Deduped diff --git a/src/main/scala/firrtl/transforms/InferResets.scala b/src/main/scala/firrtl/transforms/InferResets.scala index 4c6ffde2d4..8fb556fd8c 100644 --- a/src/main/scala/firrtl/transforms/InferResets.scala +++ b/src/main/scala/firrtl/transforms/InferResets.scala @@ -117,7 +117,7 @@ class InferResets extends Transform with DependencyAPIMigration { Dependency(passes.InferTypes), Dependency(passes.ResolveFlows), Dependency[passes.InferWidths] - ) ++ stage.Forms.WorkingIR + ) ++ stage.Forms.MinimalHighForm override def invalidates(a: Transform): Boolean = a match { case _: checks.CheckResets | passes.CheckTypes => true diff --git a/src/main/scala/firrtl/transforms/LegalizeReductions.scala b/src/main/scala/firrtl/transforms/LegalizeReductions.scala index b5751b14e9..94cef65bfb 100644 --- a/src/main/scala/firrtl/transforms/LegalizeReductions.scala +++ b/src/main/scala/firrtl/transforms/LegalizeReductions.scala @@ -35,7 +35,7 @@ object LegalizeAndReductionsTransform { class LegalizeAndReductionsTransform extends Transform with DependencyAPIMigration { override def prerequisites = - firrtl.stage.Forms.WorkingIR ++ + firrtl.stage.Forms.MinimalHighForm ++ Seq(Dependency(passes.CheckTypes), Dependency(passes.CheckWidths)) override def optionalPrerequisites = Nil diff --git a/src/test/scala/firrtlTests/LoweringCompilersSpec.scala b/src/test/scala/firrtlTests/LoweringCompilersSpec.scala index 54f0af8e42..bdc72e7b80 100644 --- a/src/test/scala/firrtlTests/LoweringCompilersSpec.scala +++ b/src/test/scala/firrtlTests/LoweringCompilersSpec.scala @@ -155,7 +155,10 @@ class LoweringCompilersSpec extends AnyFlatSpec with Matchers { it should "replicate the old order" in { val tm = new TransformManager(Forms.WorkingIR, Forms.MinimalHighForm) - compare(legacyTransforms(new firrtl.IRToWorkingIR), tm) + val patches = Seq( + Del(1) + ) + compare(legacyTransforms(new firrtl.IRToWorkingIR), tm, patches) } behavior.of("ResolveAndCheck") From 81ff0fe30f7dd63cffd0c4cd93b72ebee9c8bec9 Mon Sep 17 00:00:00 2001 From: Schuyler Eldridge Date: Mon, 1 Feb 2021 15:53:39 -0500 Subject: [PATCH 22/88] Suport ir.SubAccess in Utils.splitRef (#2021) * Add SubAccess case to Utils.splitRef Signed-off-by: Schuyler Eldridge * Update Utils.splitRef to use IR types Change Utils.splitRef to use the actual IR types instead of their WIR aliases. Update the Scaladoc note to reflect this. Signed-off-by: Schuyler Eldridge Co-authored-by: Jack Koenig --- src/main/scala/firrtl/Utils.scala | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/main/scala/firrtl/Utils.scala b/src/main/scala/firrtl/Utils.scala index 886ee986c4..d187ea5ff9 100644 --- a/src/main/scala/firrtl/Utils.scala +++ b/src/main/scala/firrtl/Utils.scala @@ -659,19 +659,24 @@ object Utils extends LazyLogging { * Given: SubField(SubIndex(Ref("b"), 2), "c") * Returns: (Ref("b"), SubField(SubIndex(EmptyExpression, 2), "c")) * b[2].c -> (b, EMPTY[2].c) - * @note This function only supports WRef, WSubField, and WSubIndex + * @note This function only supports [[firrtl.ir.RefLikeExpression RefLikeExpression]]s: [[firrtl.ir.Reference + * Reference]], [[firrtl.ir.SubField SubField]], [[firrtl.ir.SubIndex SubIndex]], and [[firrtl.ir.SubAccess + * SubAccess]] */ def splitRef(e: Expression): (WRef, Expression) = e match { - case e: WRef => (e, EmptyExpression) - case e: WSubIndex => + case e: Reference => (e, EmptyExpression) + case e: SubIndex => val (root, tail) = splitRef(e.expr) - (root, WSubIndex(tail, e.value, e.tpe, e.flow)) - case e: WSubField => + (root, SubIndex(tail, e.value, e.tpe, e.flow)) + case e: SubField => val (root, tail) = splitRef(e.expr) tail match { - case EmptyExpression => (root, WRef(e.name, e.tpe, root.kind, e.flow)) - case exp => (root, WSubField(tail, e.name, e.tpe, e.flow)) + case EmptyExpression => (root, Reference(e.name, e.tpe, root.kind, e.flow)) + case exp => (root, SubField(tail, e.name, e.tpe, e.flow)) } + case e: SubAccess => + val (root, tail) = splitRef(e.expr) + (root, SubAccess(tail, e.index, e.tpe, e.flow)) } /** Adds a root reference to some SubField/SubIndex chain */ From 9c59c3d94ae3f922ee538d593304cdf2976685aa Mon Sep 17 00:00:00 2001 From: Kevin Laeufer Date: Wed, 3 Feb 2021 17:56:02 -0800 Subject: [PATCH 23/88] IR: turn some IR nodes into data classes (#2071) * build: add data-class dependency * ir: turn Print, Stop and Verification nodes into data classes This is in preparation to add a name field to them. Co-authored-by: Jack Koenig --- build.sbt | 17 +++++++++++- src/main/scala/firrtl/ir/IR.scala | 44 ++++++++++++++++++++++++++++--- 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/build.sbt b/build.sbt index 76908bb77d..374bc273e8 100644 --- a/build.sbt +++ b/build.sbt @@ -22,6 +22,11 @@ lazy val commonSettings = Seq( crossScalaVersions := Seq("2.13.4", "2.12.13", "2.11.12") ) +lazy val isAtLeastScala213 = Def.setting { + import Ordering.Implicits._ + CrossVersion.partialVersion(scalaVersion.value).exists(_ >= (2, 13)) +} + lazy val firrtlSettings = Seq( name := "firrtl", version := "1.5-SNAPSHOT", @@ -42,8 +47,18 @@ lazy val firrtlSettings = Seq( "com.github.scopt" %% "scopt" % "3.7.1", "net.jcazevedo" %% "moultingyaml" % "0.4.2", "org.json4s" %% "json4s-native" % "3.6.9", - "org.apache.commons" % "commons-text" % "1.8" + "org.apache.commons" % "commons-text" % "1.8", + "io.github.alexarchambault" %% "data-class" % "0.2.5", ), + // macros for the data-class library + libraryDependencies ++= { + if (isAtLeastScala213.value) Nil + else Seq(compilerPlugin("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full)) + }, + scalacOptions ++= { + if (isAtLeastScala213.value) Seq("-Ymacro-annotations") + else Nil + }, // starting with scala 2.13 the parallel collections are separate from the standard library libraryDependencies ++= { CrossVersion.partialVersion(scalaVersion.value) match { diff --git a/src/main/scala/firrtl/ir/IR.scala b/src/main/scala/firrtl/ir/IR.scala index 7d091176a1..1b564d4208 100644 --- a/src/main/scala/firrtl/ir/IR.scala +++ b/src/main/scala/firrtl/ir/IR.scala @@ -4,6 +4,7 @@ package firrtl package ir import Utils.{dec2string, trim} +import dataclass.data import firrtl.constraint.{Constraint, IsKnown, IsVar} import org.apache.commons.text.translate.{AggregateTranslator, JavaUnicodeEscaper, LookupTranslator} @@ -593,7 +594,7 @@ case class Attach(info: Info, exprs: Seq[Expression]) extends Statement with Has def foreachString(f: String => Unit): Unit = () def foreachInfo(f: Info => Unit): Unit = f(info) } -case class Stop(info: Info, ret: Int, clk: Expression, en: Expression) +@data class Stop(info: Info, ret: Int, clk: Expression, en: Expression) extends Statement with HasInfo with UseSerializer { @@ -607,8 +608,16 @@ case class Stop(info: Info, ret: Int, clk: Expression, en: Expression) def foreachType(f: Type => Unit): Unit = () def foreachString(f: String => Unit): Unit = () def foreachInfo(f: Info => Unit): Unit = f(info) + def copy(info: Info = info, ret: Int = ret, clk: Expression = clk, en: Expression = en): Stop = { + Stop(info, ret, clk, en) + } +} +object Stop { + def unapply(s: Stop): Some[(Info, Int, Expression, Expression)] = { + Some((s.info, s.ret, s.clk, s.en)) + } } -case class Print( +@data class Print( info: Info, string: StringLit, args: Seq[Expression], @@ -627,6 +636,20 @@ case class Print( def foreachType(f: Type => Unit): Unit = () def foreachString(f: String => Unit): Unit = () def foreachInfo(f: Info => Unit): Unit = f(info) + def copy( + info: Info = info, + string: StringLit = string, + args: Seq[Expression] = args, + clk: Expression = clk, + en: Expression = en + ): Print = { + Print(info, string, args, clk, en) + } +} +object Print { + def unapply(s: Print): Some[(Info, StringLit, Seq[Expression], Expression, Expression)] = { + Some((s.info, s.string, s.args, s.clk, s.en)) + } } // formal @@ -636,7 +659,7 @@ object Formal extends Enumeration { val Cover = Value("cover") } -case class Verification( +@data class Verification( op: Formal.Value, info: Info, clk: Expression, @@ -657,6 +680,21 @@ case class Verification( def foreachType(f: Type => Unit): Unit = () def foreachString(f: String => Unit): Unit = () def foreachInfo(f: Info => Unit): Unit = f(info) + def copy( + op: Formal.Value = op, + info: Info = info, + clk: Expression = clk, + pred: Expression = pred, + en: Expression = en, + msg: StringLit = msg + ): Verification = { + Verification(op, info, clk, pred, en, msg) + } +} +object Verification { + def unapply(s: Verification): Some[(Formal.Value, Info, Expression, Expression, Expression, StringLit)] = { + Some((s.op, s.info, s.clk, s.pred, s.en, s.msg)) + } } // end formal From 6e0e760526090c694ce6507db71122654ffc3000 Mon Sep 17 00:00:00 2001 From: John's Brew <46595442+johnsbrew@users.noreply.github.com> Date: Fri, 5 Feb 2021 19:35:59 +0100 Subject: [PATCH 24/88] Add file line to source link from scaladoc (#2072) Signed-off-by: Jean Bruant --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 374bc273e8..d779920ba8 100644 --- a/build.sbt +++ b/build.sbt @@ -173,7 +173,7 @@ lazy val docSettings = Seq( } else { s"v${version.value}" } - s"https://github.com/freechipsproject/firrtl/tree/$branch€{FILE_PATH}.scala" + s"https://github.com/chipsalliance/firrtl/tree/$branch€{FILE_PATH_EXT}#L€{FILE_LINE}" } ) ++ scalacDocOptionsVersion(scalaVersion.value) ) From 5ea823025054086953a4ccdb7950c7ee1918917e Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Wed, 3 Feb 2021 19:16:02 -0800 Subject: [PATCH 25/88] Add DiGraph factory method and prettyTree * New factory method enables direct construction of DiGraphs from edges * DiGraph.prettyTree enables visualization of tree or multi-tree diagraphs --- src/main/scala/firrtl/graph/DiGraph.scala | 47 +++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/main/scala/firrtl/graph/DiGraph.scala b/src/main/scala/firrtl/graph/DiGraph.scala index 3a08d05e1c..9f6ffeb2b3 100644 --- a/src/main/scala/firrtl/graph/DiGraph.scala +++ b/src/main/scala/firrtl/graph/DiGraph.scala @@ -4,6 +4,7 @@ package firrtl.graph import scala.collection.{mutable, Map, Set} import scala.collection.mutable.{LinkedHashMap, LinkedHashSet} +import firrtl.options.DependencyManagerUtils.{CharSet, PrettyCharSet} /** An exception that is raised when an assumed DAG has a cycle */ class CyclicException(val node: Any) extends Exception(s"No valid linearization for cyclic graph, found at $node") @@ -31,6 +32,16 @@ object DiGraph { } new DiGraph(edgeDataCopy) } + + /** Create a DiGraph from edges */ + def apply[T](edges: (T, T)*): DiGraph[T] = { + val edgeMap = new LinkedHashMap[T, LinkedHashSet[T]] + for ((from, to) <- edges) { + val set = edgeMap.getOrElseUpdate(from, new LinkedHashSet[T]) + set += to + } + new DiGraph(edgeMap) + } } /** Represents common behavior of all directed graphs */ @@ -386,6 +397,42 @@ class DiGraph[T](private[graph] val edges: LinkedHashMap[T, LinkedHashSet[T]]) { that.edges.foreach({ case (k, v) => eprime.getOrElseUpdate(k, new LinkedHashSet[T]) ++= v }) new DiGraph(eprime) } + + /** Serializes a `DiGraph[String]` as a pretty tree + * + * Multiple roots are supported, but cycles are not. + */ + def prettyTree(charSet: CharSet = PrettyCharSet)(implicit ev: T =:= String): String = { + // Set up characters for building the tree + val (l, n, c) = (charSet.lastNode, charSet.notLastNode, charSet.continuation) + val ctab = " " * c.size + " " + + // Recursively adds each node of the DiGraph to accumulating List[String] + // Uses List because prepend is cheap and this prevents quadratic behavior of String + // concatenations or even flatMapping on Seqs + def rec(tab: String, node: T, mark: String, prev: List[String]): List[String] = { + val here = s"$mark$node" + val children = this.getEdges(node) + val last = children.size - 1 + children.toList // Convert LinkedHashSet to List to avoid determinism issues + .zipWithIndex // Find last + .foldLeft(here :: prev) { + case (acc, (nodex, idx)) => + val nextTab = if (idx == last) tab + ctab else tab + c + " " + val nextMark = if (idx == last) tab + l else tab + n + rec(nextTab, nodex, nextMark + " ", acc) + } + } + this.findSources + .toList // Convert LinkedHashSet to List to avoid determinism issues + .sortBy(_.toString) // Make order deterministic + .foldLeft(Nil: List[String]) { + case (acc, root) => rec("", root, "", acc) + } + .reverse + .mkString("\n") + } + } class MutableDiGraph[T] extends DiGraph[T](new LinkedHashMap[T, LinkedHashSet[T]]) { From 5903e6a3bfce415c57c19865675db131b733c159 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Tue, 2 Feb 2021 18:54:13 -0800 Subject: [PATCH 26/88] Add MustDeduplicateTransform This enables marking modules as "must deduplicate". If modules marked as such do not deduplicate, the transform will create error reports and make suggestions as to why deduplication failed. --- .../scala/firrtl/transforms/MustDedup.scala | 245 ++++++++++++++++ .../transforms/MustDedupSpec.scala | 267 ++++++++++++++++++ 2 files changed, 512 insertions(+) create mode 100644 src/main/scala/firrtl/transforms/MustDedup.scala create mode 100644 src/test/scala/firrtlTests/transforms/MustDedupSpec.scala diff --git a/src/main/scala/firrtl/transforms/MustDedup.scala b/src/main/scala/firrtl/transforms/MustDedup.scala new file mode 100644 index 0000000000..3e7629cda7 --- /dev/null +++ b/src/main/scala/firrtl/transforms/MustDedup.scala @@ -0,0 +1,245 @@ +// See LICENSE for license details. + +package firrtl.transforms + +import firrtl._ +import firrtl.annotations._ +import firrtl.annotations.TargetToken.OfModule +import firrtl.analyses.InstanceKeyGraph +import firrtl.analyses.InstanceKeyGraph.InstanceKey +import firrtl.options.Dependency +import firrtl.stage.Forms +import firrtl.graph.DiGraph + +import java.io.{File, FileWriter} + +/** Marks modules as "must deduplicate" */ +case class MustDeduplicateAnnotation(modules: Seq[IsModule]) extends MultiTargetAnnotation { + def targets: Seq[Seq[IsModule]] = modules.map(Seq(_)) + + def duplicate(n: Seq[Seq[Target]]): MustDeduplicateAnnotation = { + val newModules = n.map { + case Seq(mod: IsModule) => mod + case _ => + val msg = "Something went wrong! This anno should only rename to single IsModules! " + + s"Got: $modules -> $n" + throw new Exception(msg) + } + MustDeduplicateAnnotation(newModules) + } +} + +/** Specifies the directory where errors for modules that "must deduplicate" will be reported */ +case class MustDeduplicateReportDirectory(directory: String) extends NoTargetAnnotation + +object MustDeduplicateTransform { + sealed trait DedupFailureCandidate { + def message: String + def modules: Seq[OfModule] + } + case class LikelyShouldMatch(a: OfModule, b: OfModule) extends DedupFailureCandidate { + def message: String = s"Modules '${a.value}' and '${b.value}' likely should dedup but do not." + def modules = Seq(a, b) + } + object DisjointChildren { + sealed trait Reason + case object Left extends Reason + case object Right extends Reason + case object Both extends Reason + } + import DisjointChildren._ + case class DisjointChildren(a: OfModule, b: OfModule, reason: Reason) extends DedupFailureCandidate { + def message: String = { + def helper(x: OfModule, y: OfModule): String = s"'${x.value}' contains instances not found in '${y.value}'" + val why = reason match { + case Left => helper(a, b) + case Right => helper(b, a) + case Both => s"${helper(a, b)} and ${helper(b, a)}" + } + s"Modules '${a.value}' and '${b.value}' cannot be deduplicated because $why." + } + def modules = Seq(a, b) + } + + final class DeduplicationFailureException(msg: String) extends FirrtlUserException(msg) + + case class DedupFailure( + shouldDedup: Seq[OfModule], + relevantMods: Set[OfModule], + candidates: Seq[DedupFailureCandidate]) + + /** Reports deduplication failures two Modules + * + * @return (Set of Modules that only appear in one hierarchy or the other, candidate pairs of Module names) + */ + def findDedupFailures(shouldDedup: Seq[OfModule], graph: InstanceKeyGraph): DedupFailure = { + val instLookup = graph.getChildInstances.toMap + def recurse(a: OfModule, b: OfModule): Seq[DedupFailureCandidate] = { + val as = instLookup(a.value) + val bs = instLookup(b.value) + if (as.length != bs.length) { + val aa = as.toSet + val bb = bs.toSet + val reason = (aa.diff(bb).nonEmpty, bb.diff(aa).nonEmpty) match { + case (true, true) => Both + case (true, false) => Left + case (false, true) => Right + case _ => Utils.error("Impossible!") + } + Seq(DisjointChildren(a, b, reason)) + } else { + val fromChildren = as.zip(bs).flatMap { + case (ax, bx) => recurse(ax.OfModule, bx.OfModule) + } + if (fromChildren.nonEmpty) fromChildren + else if (a != b) Seq(LikelyShouldMatch(a, b)) + else Nil + } + } + + val allMismatches = { + // Recalculating this every time is a little wasteful, but we're on a failure path anyway + val digraph = graph.graph.transformNodes(_.OfModule) + val froms = shouldDedup.map(x => digraph.reachableFrom(x) + x) + val union = froms.reduce(_ union _) + val intersection = froms.reduce(_ intersect _) + union.diff(intersection) + }.toSet + val pairs = shouldDedup.tail.map(n => (shouldDedup.head, n)) + val candidates = pairs.flatMap { case (a, b) => recurse(a, b) } + DedupFailure(shouldDedup, allMismatches, candidates) + } + + // Find the minimal number of vertices in the graph to show paths from "mustDedup" to failure + // candidates and their context (eg. children for DisjoinChildren) + private def findNodesToKeep(failure: DedupFailure, graph: DiGraph[String]): collection.Set[String] = { + val shouldDedup = failure.shouldDedup.map(_.value).toSet + val nodeOfInterest: Set[String] = + shouldDedup ++ failure.candidates.flatMap { + case LikelyShouldMatch(OfModule(a), OfModule(b)) => Seq(a, b) + case DisjointChildren(OfModule(a), OfModule(b), _) => + Seq(a, b) ++ graph.getEdges(a) ++ graph.getEdges(b) + } + // Depth-first search looking for relevant nodes + def dfs(node: String): collection.Set[String] = { + val deeper = graph.getEdges(node).flatMap(dfs) + if (deeper.nonEmpty || nodeOfInterest(node)) deeper + node else deeper + } + shouldDedup.flatMap(dfs) + } + + /** Turn a [[DedupFailure]] into a pretty graph for visualization + * + * @param failure Failure to visualize + * @param graph DiGraph of module names (no instance information) + */ + def makeDedupFailureDiGraph(failure: DedupFailure, graph: DiGraph[String]): DiGraph[String] = { + // Recalculating this every time is a little wasteful, but we're on a failure path anyway + // Lookup the parent Module name of any Module + val getParents: String => Seq[String] = + graph.reverse.getEdgeMap + .mapValues(_.toSeq) + + val candidates = failure.candidates + val shouldDedup = failure.shouldDedup.map(_.value) + val shouldDedupSet = shouldDedup.toSet + val mygraph = { + // Create a graph of paths from "shouldDedup" nodes to the candidates + // rooted at the "shouldDedup" nodes + val nodesToKeep = findNodesToKeep(failure, graph) + graph.subgraph(nodesToKeep) + + // Add fake nodes to represent parents of the "shouldDedup" nodes + DiGraph(shouldDedup.map(n => getParents(n).mkString(", ") -> n): _*) + } + // Gather candidate modules and assign indices for reference + val candidateIdx: Map[String, Int] = + candidates.zipWithIndex.flatMap { case (c, idx) => c.modules.map(_.value -> idx) }.toMap + // Now mark the graph for modules of interest + val markedGraph = mygraph.transformNodes { n => + val next = if (shouldDedupSet(n)) s"($n)" else n + candidateIdx + .get(n) + .map(i => s"$next [$i]") + .getOrElse(next) + } + markedGraph + } +} + +/** Checks for modules that have been marked as "must deduplicate" + * + * In cases where marked modules did not deduplicate, this transform attempts to provide context on + * what went wrong for debugging. + */ +class MustDeduplicateTransform extends Transform with DependencyAPIMigration { + import MustDeduplicateTransform._ + + override def prerequisites = Seq(Dependency[DedupModules]) + + // Make this run as soon after Dedup as possible + override def optionalPrerequisiteOf = (Forms.MidForm.toSet -- Forms.HighForm).toSeq + + override def invalidates(a: Transform) = false + + def execute(state: CircuitState): CircuitState = { + + lazy val igraph = InstanceKeyGraph(state.circuit) + + val dedupFailures: Seq[DedupFailure] = + state.annotations.flatMap { + case MustDeduplicateAnnotation(mods) => + val moduleNames = mods.map(_.leafModule).distinct + if (moduleNames.size <= 1) None + else { + val modNames = moduleNames.map(OfModule) + Some(findDedupFailures(modNames, igraph)) + } + case _ => None + } + if (dedupFailures.nonEmpty) { + val modgraph = igraph.graph.transformNodes(_.module) + // Create and log reports + val reports = dedupFailures.map { + case fail @ DedupFailure(shouldDedup, _, candidates) => + val graph = makeDedupFailureDiGraph(fail, modgraph).prettyTree() + val mods = shouldDedup.map("'" + _.value + "'").mkString(", ") + val msg = + s"""===== $mods are marked as "must deduplicate", but did not deduplicate. ===== + |$graph + |Failure candidates: + |${candidates.zipWithIndex.map { case (c, i) => s" - [$i] " + c.message }.mkString("\n")} + |""".stripMargin + logger.error(msg) + msg + } + + // Write reports and modules to disk + val dirName = state.annotations.collectFirst { case MustDeduplicateReportDirectory(dir) => dir } + .getOrElse("dedup_failures") + val dir = new File(dirName) + logger.error(s"Writing error report(s) to ${dir}...") + FileUtils.makeDirectory(dir.toString) + for ((report, idx) <- reports.zipWithIndex) { + val f = new File(dir, s"report_$idx.rpt") + logger.error(s"Writing $f...") + val fw = new FileWriter(f) + fw.write(report) + fw.close() + } + + val modsDir = new File(dir, "modules") + FileUtils.makeDirectory(modsDir.toString) + logger.error(s"Writing relevant modules to $modsDir...") + val relevantModule = dedupFailures.flatMap(_.relevantMods.map(_.value)).toSet + for (mod <- state.circuit.modules if relevantModule(mod.name)) { + val fw = new FileWriter(new File(modsDir, s"${mod.name}.fir")) + fw.write(mod.serialize) + fw.close() + } + + val msg = s"Modules marked 'must deduplicate' failed to deduplicate! See error reports in $dirName" + throw new DeduplicationFailureException(msg) + } + state + } +} diff --git a/src/test/scala/firrtlTests/transforms/MustDedupSpec.scala b/src/test/scala/firrtlTests/transforms/MustDedupSpec.scala new file mode 100644 index 0000000000..2f633e0e52 --- /dev/null +++ b/src/test/scala/firrtlTests/transforms/MustDedupSpec.scala @@ -0,0 +1,267 @@ +// SPDX-License-Identifier: Apache-2.0 + +package firrtlTests.transforms + +import org.scalatest.featurespec.AnyFeatureSpec +import org.scalatest.GivenWhenThen +import firrtl.testutils.FirrtlMatchers +import java.io.File + +import firrtl.graph.DiGraph +import firrtl.analyses.InstanceKeyGraph +import firrtl.annotations.CircuitTarget +import firrtl.annotations.TargetToken.OfModule +import firrtl.transforms._ +import firrtl.transforms.MustDeduplicateTransform._ +import firrtl.transforms.MustDeduplicateTransform.DisjointChildren._ +import firrtl.util.BackendCompilationUtilities.createTestDirectory +import firrtl.stage.{FirrtlSourceAnnotation, RunFirrtlTransformAnnotation} +import firrtl.options.{TargetDirAnnotation} +import logger.{LogLevel, LogLevelAnnotation, Logger} + +class MustDedupSpec extends AnyFeatureSpec with FirrtlMatchers with GivenWhenThen { + + Feature("When you have a simple non-deduping hierarcy") { + val text = """ + |circuit A : + | module C : + | output io : { flip in : UInt<8>, out : UInt<8> } + | io.out <= io.in + | module C_1 : + | output io : { flip in : UInt<8>, out : UInt<8> } + | io.out <= and(io.in, UInt("hff")) + | module B : + | output io : { flip in : UInt<8>, out : UInt<8> } + | inst c of C + | io <= c.io + | module B_1 : + | output io : { flip in : UInt<8>, out : UInt<8> } + | inst c of C_1 + | io <= c.io + | module A : + | output io : { flip in : UInt<8>, out : UInt<8> } + | inst b of B + | inst b_1 of B_1 + | io.out <= and(b.io.out, b_1.io.out) + | b.io.in <= io.in + | b_1.io.in <= io.in + """.stripMargin + val top = CircuitTarget("A") + val bdedup = MustDeduplicateAnnotation(Seq(top.module("B"), top.module("B_1"))) + val igraph = InstanceKeyGraph(parse(text)) + + Scenario("Full compilation should fail and dump reports to disk") { + val testDir = createTestDirectory("must_dedup") + val reportDir = new File(testDir, "reports") + val annos = Seq( + TargetDirAnnotation(testDir.toString), + FirrtlSourceAnnotation(text), + RunFirrtlTransformAnnotation(new MustDeduplicateTransform), + MustDeduplicateReportDirectory(reportDir.toString), + bdedup + ) + + a[DeduplicationFailureException] shouldBe thrownBy { + (new firrtl.stage.FirrtlPhase).transform(annos) + } + + reportDir should exist + + val report0 = new File(reportDir, "report_0.rpt") + report0 should exist + + val expectedModules = Seq("B", "B_1", "C", "C_1") + for (mod <- expectedModules) { + new File(reportDir, s"modules/$mod.fir") should exist + } + } + + Scenario("Non-deduping children should give actionable debug information") { + When("Finding dedup failures") + val failure = findDedupFailures(Seq(OfModule("B"), OfModule("B_1")), igraph) + + Then("The children should appear as a failure candidate") + failure.candidates should be(Seq(LikelyShouldMatch(OfModule("C"), OfModule("C_1")))) + + And("There should be a pretty DiGraph showing context") + val got = makeDedupFailureDiGraph(failure, igraph.graph.transformNodes(_.module)) + val expected = DiGraph("A" -> "(B)", "A" -> "(B_1)", "(B)" -> "C [0]", "(B_1)" -> "C_1 [0]") + // DiGraph uses referential equality so compare serialized form + got.prettyTree() should be(expected.prettyTree()) + } + + Scenario("Unrelated hierarchies should give actionable debug information") { + When("Finding dedup failures") + val failure = findDedupFailures(Seq(OfModule("B"), OfModule("C_1")), igraph) + + Then("The failure should note the hierarchies don't match") + failure.candidates should be(Seq(DisjointChildren(OfModule("B"), OfModule("C_1"), Left))) + + And("There should be a pretty DiGraph showing context") + val got = makeDedupFailureDiGraph(failure, igraph.graph.transformNodes(_.module)) + val expected = DiGraph("A" -> "(B) [0]", "(B) [0]" -> "C", "B_1" -> "(C_1) [0]") + // DiGraph uses referential equality so compare serialized form + got.prettyTree() should be(expected.prettyTree()) + } + } + + Feature("When you have a deep, non-deduping hierarchy") { + // Shadow hierarchy just to get an InstanceKeyGraph which can only be made from a circuit + val text = parse(""" + |circuit A : + | module E: + | skip + | module F : + | skip + | module F_1 : + | inst e of E + | module D : + | skip + | module D_1 : + | skip + | module C : + | inst d of D + | inst f of F + | module C_1 : + | inst d of D_1 + | inst f of F_1 + | module B : + | inst c of C + | inst e of E + | module B_1 : + | inst c of C_1 + | inst e of E + | module A : + | inst b of B + | inst b_1 of B_1 + |""".stripMargin) + val igraph = InstanceKeyGraph(text) + + Scenario("Non-deduping children should give actionable debug information") { + When("Finding dedup failures") + val failure = findDedupFailures(Seq(OfModule("B"), OfModule("B_1")), igraph) + + Then("The children should appear as a failure candidate") + failure.candidates should be( + Seq(LikelyShouldMatch(OfModule("D"), OfModule("D_1")), DisjointChildren(OfModule("F"), OfModule("F_1"), Right)) + ) + + And("There should be a pretty DiGraph showing context") + val got = makeDedupFailureDiGraph(failure, igraph.graph.transformNodes(_.module)) + val expected = DiGraph( + "A" -> "(B)", + "A" -> "(B_1)", + "(B)" -> "C", + "C" -> "D [0]", + "C" -> "F [1]", + "(B_1)" -> "C_1", + "C_1" -> "D_1 [0]", + "C_1" -> "F_1 [1]", + "F_1 [1]" -> "E", + // These last 2 are undesirable but E is included because it's a submodule of disjoint F and F_1 + "(B)" -> "E", + "(B_1)" -> "E" + ) + // DiGraph uses referential equality so compare serialized form + got.prettyTree() should be(expected.prettyTree()) + } + } + + Feature("When you have multiple modules that should dedup, but don't") { + // Shadow hierarchy just to get an InstanceKeyGraph which can only be made from a circuit + val text = parse(""" + |circuit A : + | module D : + | skip + | module D_1 : + | skip + | module C : + | skip + | module C_1 : + | skip + | module B : + | inst c of C + | inst d of D + | module B_1 : + | inst c of C_1 + | inst d of D + | module B_2 : + | inst c of C + | inst d of D_1 + | module A : + | inst b of B + | inst b_1 of B_1 + | inst b_2 of B_2 + |""".stripMargin) + val igraph = InstanceKeyGraph(text) + + Scenario("Non-deduping children should give actionable debug information") { + When("Finding dedup failures") + val failure = findDedupFailures(Seq(OfModule("B"), OfModule("B_1"), OfModule("B_2")), igraph) + + Then("The children should appear as a failure candidate") + failure.candidates should be( + Seq(LikelyShouldMatch(OfModule("C"), OfModule("C_1")), LikelyShouldMatch(OfModule("D"), OfModule("D_1"))) + ) + + And("There should be a pretty DiGraph showing context") + val got = makeDedupFailureDiGraph(failure, igraph.graph.transformNodes(_.module)) + val expected = DiGraph( + "A" -> "(B)", + "A" -> "(B_1)", + "A" -> "(B_2)", + "(B)" -> "C [0]", + "(B)" -> "D [1]", + "(B_1)" -> "C_1 [0]", + "(B_1)" -> "D [1]", + "(B_2)" -> "C [0]", + "(B_2)" -> "D_1 [1]" + ) + // DiGraph uses referential equality so compare serialized form + got.prettyTree() should be(expected.prettyTree()) + } + } + + Feature("When you have modules that should dedup, and they do") { + val text = """ + |circuit A : + | module C : + | output io : { flip in : UInt<8>, out : UInt<8> } + | io.out <= io.in + | module C_1 : + | output io : { flip in : UInt<8>, out : UInt<8> } + | io.out <= io.in + | module B : + | output io : { flip in : UInt<8>, out : UInt<8> } + | inst c of C + | io <= c.io + | module B_1 : + | output io : { flip in : UInt<8>, out : UInt<8> } + | inst c of C_1 + | io <= c.io + | module A : + | output io : { flip in : UInt<8>, out : UInt<8> } + | inst b of B + | inst b_1 of B_1 + | io.out <= and(b.io.out, b_1.io.out) + | b.io.in <= io.in + | b_1.io.in <= io.in + """.stripMargin + val top = CircuitTarget("A") + val bdedup = MustDeduplicateAnnotation(Seq(top.module("B"), top.module("B_1"))) + + Scenario("Full compilation should succeed") { + val testDir = createTestDirectory("must_dedup") + val reportDir = new File(testDir, "reports") + val annos = Seq( + TargetDirAnnotation(testDir.toString), + FirrtlSourceAnnotation(text), + RunFirrtlTransformAnnotation(new MustDeduplicateTransform), + MustDeduplicateReportDirectory(reportDir.toString), + bdedup + ) + + (new firrtl.stage.FirrtlPhase).transform(annos) + } + } +} From 5a89fca6090948d0a99c217a09c692e58a20d1df Mon Sep 17 00:00:00 2001 From: Kevin Laeufer Date: Wed, 17 Feb 2021 12:16:52 -0800 Subject: [PATCH 27/88] Allow Side Effecting Statement to have Names (#2057) * firrtl: add optional statement labels for stop, printf, assert, assume and cover * test: parsing of statement labels * ir: ensure that name is properly retained * SymbolTable: add support for labled statements * test: parsing statement labels * test: lower types name collisions with named statements * ignore empty names * Inline: deal with named and unnamed statements * RemoveWires: treat stop, printf and verification statements as "others" * test: fix InlineInstance tests * DeadCodeEliminations: statements are now als declarations * CheckHighForm: ensure that statement names are not used as references * CheckSpec: throw error if statement name collides * add pass to automatically add missing statement names * check: make sure that two statements cannot have the same name * stmtLabel -> stmtName * scalafmt * add statement names to spec * spec: meta data -> metadata * EnsureStatementNames: explain naming algorithm * remove returns * better namespace use * ir: add CanBeReferenced trait * ir: add newline as jack requested --- spec/spec.pdf | Bin 338200 -> 338944 bytes spec/spec.tex | 29 +++- src/main/antlr4/FIRRTL.g4 | 14 +- src/main/scala/firrtl/Namespace.scala | 8 +- src/main/scala/firrtl/Visitor.scala | 16 +- src/main/scala/firrtl/WIR.scala | 3 + .../scala/firrtl/analyses/SymbolTable.scala | 4 + src/main/scala/firrtl/graph/DiGraph.scala | 3 +- src/main/scala/firrtl/ir/IR.scala | 56 ++++-- src/main/scala/firrtl/ir/Serializer.scala | 18 +- .../scala/firrtl/passes/CheckHighForm.scala | 27 ++- src/main/scala/firrtl/passes/Inline.scala | 27 +-- .../transforms/DeadCodeElimination.scala | 10 +- .../transforms/EnsureNamedStatements.scala | 39 +++++ .../scala/firrtl/transforms/RemoveWires.scala | 5 +- .../scala/firrtl/testutils/FirrtlSpec.scala | 11 +- .../EnsureNamedStatementsSpec.scala | 39 +++++ src/test/scala/firrtlTests/CheckSpec.scala | 44 +++++ .../firrtlTests/InlineInstancesTests.scala | 54 ++++++ .../scala/firrtlTests/LowerTypesSpec.scala | 19 +++ src/test/scala/firrtlTests/ParserSpec.scala | 21 +++ .../firrtlTests/VerilogEquivalenceSpec.scala | 160 +++++++++--------- 22 files changed, 456 insertions(+), 151 deletions(-) create mode 100644 src/main/scala/firrtl/transforms/EnsureNamedStatements.scala create mode 100644 src/test/scala/firrtl/transforms/EnsureNamedStatementsSpec.scala diff --git a/spec/spec.pdf b/spec/spec.pdf index 45dd4879d423bc70fbd222f9d5fc88e9154fa91c..e392dec686b2eee068393217d3699deaafad0238 100644 GIT binary patch delta 44188 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 CY?NmJ delta 43434 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

Iy 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 diff --git a/spec/spec.tex b/spec/spec.tex index 41de5d5e1c..8201b32ed6 100644 --- a/spec/spec.tex +++ b/spec/spec.tex @@ -989,10 +989,15 @@ \subsection{Stops} \label{stop_stmt} side-effect-having statements in different modules or with different clocks that trigger concurrently is undefined. +The stop statement has an optional name attribute which can be used to +attach metadata to the statement. The name is part of the module level +namespace. However it can never be used in a reference since it is not of +any valid type. + \begin{lstlisting} wire clk:Clock wire halt:UInt<1> -stop(clk,halt,42) +stop(clk,halt,42) : optional_name ... \end{lstlisting} @@ -1004,12 +1009,17 @@ \subsection{Formatted Prints} For information about execution ordering of clocked statements with observable environmental side effects, see section \ref{stop_stmt}. +The printf statement has an optional name attribute which can be used to +attach metadata to the statement. The name is part of the module level +namespace. However it can never be used in a reference since it is not of +any valid type. + \begin{lstlisting} wire clk:Clock wire condition:UInt<1> wire a:UInt wire b:UInt -printf(clk, condition, "a in hex: %x, b in decimal:%d.\n", a, b) +printf(clk, condition, "a in hex: %x, b in decimal:%d.\n", a, b) : optional_name ... \end{lstlisting} @@ -1043,6 +1053,11 @@ \subsection{Verification} For information about execution ordering of clocked statements with observable environmental side effects, see section \ref{stop_stmt}. +Any verification statement has an optional name attribute which can be used to +attach metadata to the statement. The name is part of the module level +namespace. However it can never be used in a reference since it is not of +any valid type. + \subsubsection{Assert} The assert statement verifies that the predicate is true on the rising edge of any clock cycle when the enable is true. In other words, it verifies that enable implies predicate. @@ -1053,7 +1068,7 @@ \subsubsection{Assert} wire en:UInt<1> pred <= eq(X, Y) en <= Z_valid -assert(clk, pred, en, "X equals Y when Z is valid") +assert(clk, pred, en, "X equals Y when Z is valid") : optional_name \end{lstlisting} \subsubsection{Assume} @@ -1066,7 +1081,7 @@ \subsubsection{Assume} wire en:UInt<1> pred <= eq(X, Y) en <= Z_valid -assume(clk, pred, en, "X equals Y when Z is valid") +assume(clk, pred, en, "X equals Y when Z is valid") : optional_name \end{lstlisting} \subsubsection{Cover} @@ -1079,7 +1094,7 @@ \subsubsection{Cover} wire en:UInt<1> pred <= eq(X, Y) en <= Z_valid -cover(clk, pred, en, "X equals Y when Z is valid") +cover(clk, pred, en, "X equals Y when Z is valid") : optional_name \end{lstlisting} \section{Expressions} @@ -2108,8 +2123,8 @@ \subsection{Concrete Syntax Tree} \label{syntax_tree} &\pipe &\pd{exp} \vv{is invalid} \opt{\pd{info}} &\text{Invalidate}\\ &\pipe &\vv{attach}\vv{(}\rpt{\pd{exp}}\vv{)} \opt{\pd{info}} &\text{Attach}\\ &\pipe &\vv{when} \pd{exp} \vv{:} \opt{\pd{info}} \pd{stmt} \opt{\vv{else} \vv{:} \pd{stmt}} &\text{Conditional}\\ - &\pipe &\vv{stop(}\pd{exp}, \pd{exp}, \pd{int})\opt{\pd{info}} &\text{Stop}\\ - &\pipe &\vv{printf(}\pd{exp}, \pd{exp}, \pd{string}, \rpt{\pd{exp}}\vv{)} \opt{\pd{info}} &\text{Printf}\\ + &\pipe &\vv{stop(}\pd{exp}, \pd{exp}, \pd{int}) \opt{\vv{:}\pd{id}} \opt{\pd{info}} &\text{Stop}\\ + &\pipe &\vv{printf(}\pd{exp}, \pd{exp}, \pd{string}, \rpt{\pd{exp}}\vv{)} \opt{\vv{:}\pd{id}} \opt{\pd{info}} &\text{Printf}\\ &\pipe &\vv{skip} \opt{\pd{info}} &\text{Empty}\\ &\pipe &\vv{(}\rpt{\pd{stmt}}\vv{)} &\text{Statement Group}\\ \pd{ruw} &= &\vv{old} \pipe \vv{ new} \pipe \vv{ undefined} &\text{Read Under Write Flag}\\ diff --git a/src/main/antlr4/FIRRTL.g4 b/src/main/antlr4/FIRRTL.g4 index 54ad8d0eec..aa53f2f0b1 100644 --- a/src/main/antlr4/FIRRTL.g4 +++ b/src/main/antlr4/FIRRTL.g4 @@ -103,13 +103,17 @@ stmt | exp '<-' exp info? | exp 'is' 'invalid' info? | when - | 'stop(' exp exp intLit ')' info? - | 'printf(' exp exp StringLit ( exp)* ')' info? + | 'stop(' exp exp intLit ')' stmtName? info? + | 'printf(' exp exp StringLit ( exp)* ')' stmtName? info? | 'skip' info? | 'attach' '(' exp+ ')' info? - | 'assert' '(' exp exp exp StringLit ')' info? - | 'assume' '(' exp exp exp StringLit ')' info? - | 'cover' '(' exp exp exp StringLit ')' info? + | 'assert' '(' exp exp exp StringLit ')' stmtName? info? + | 'assume' '(' exp exp exp StringLit ')' stmtName? info? + | 'cover' '(' exp exp exp StringLit ')' stmtName? info? + ; + +stmtName + : ':' id ; memField diff --git a/src/main/scala/firrtl/Namespace.scala b/src/main/scala/firrtl/Namespace.scala index 25f4a805f5..a4b7bc7a52 100644 --- a/src/main/scala/firrtl/Namespace.scala +++ b/src/main/scala/firrtl/Namespace.scala @@ -53,9 +53,11 @@ object Namespace { val namespace = new Namespace def buildNamespaceStmt(s: Statement): Seq[String] = s match { - case s: IsDeclaration => Seq(s.name) - case s: Conditionally => buildNamespaceStmt(s.conseq) ++ buildNamespaceStmt(s.alt) - case s: Block => s.stmts.flatMap(buildNamespaceStmt) + // Empty names are allowed for backwards compatibility reasons and + // indicate that the entity has essentially no name. + case s: IsDeclaration if s.name.nonEmpty => Seq(s.name) + case s: Conditionally => buildNamespaceStmt(s.conseq) ++ buildNamespaceStmt(s.alt) + case s: Block => s.stmts.flatMap(buildNamespaceStmt) case _ => Nil } namespace.namespace ++= m.ports.map(_.name) diff --git a/src/main/scala/firrtl/Visitor.scala b/src/main/scala/firrtl/Visitor.scala index dadcac4649..f1b3a5c201 100644 --- a/src/main/scala/firrtl/Visitor.scala +++ b/src/main/scala/firrtl/Visitor.scala @@ -318,6 +318,7 @@ class Visitor(infoMode: InfoMode) extends AbstractParseTreeVisitor[FirrtlNode] w private def visitStmt(ctx: StmtContext): Statement = { val ctx_exp = ctx.exp.asScala val info = visitInfo(Option(ctx.info), ctx) + def stmtName = Option(ctx.stmtName).map(_.id.getText).getOrElse("") ctx.getChild(0) match { case when: WhenContext => visitWhen(when) case term: TerminalNode => @@ -346,7 +347,8 @@ class Visitor(infoMode: InfoMode) extends AbstractParseTreeVisitor[FirrtlNode] w case "inst" => DefInstance(info, ctx.id(0).getText, ctx.id(1).getText) case "node" => DefNode(info, ctx.id(0).getText, visitExp(ctx_exp(0))) - case "stop(" => Stop(info, string2Int(ctx.intLit().getText), visitExp(ctx_exp(0)), visitExp(ctx_exp(1))) + case "stop(" => + Stop(info, string2Int(ctx.intLit().getText), visitExp(ctx_exp(0)), visitExp(ctx_exp(1)), name = stmtName) case "attach" => Attach(info, ctx_exp.map(visitExp).toSeq) case "printf(" => Print( @@ -354,7 +356,8 @@ class Visitor(infoMode: InfoMode) extends AbstractParseTreeVisitor[FirrtlNode] w visitStringLit(ctx.StringLit), ctx_exp.drop(2).map(visitExp).toSeq, visitExp(ctx_exp(0)), - visitExp(ctx_exp(1)) + visitExp(ctx_exp(1)), + name = stmtName ) // formal case "assert" => @@ -364,7 +367,8 @@ class Visitor(infoMode: InfoMode) extends AbstractParseTreeVisitor[FirrtlNode] w visitExp(ctx_exp(0)), visitExp(ctx_exp(1)), visitExp(ctx_exp(2)), - visitStringLit(ctx.StringLit) + visitStringLit(ctx.StringLit), + name = stmtName ) case "assume" => Verification( @@ -373,7 +377,8 @@ class Visitor(infoMode: InfoMode) extends AbstractParseTreeVisitor[FirrtlNode] w visitExp(ctx_exp(0)), visitExp(ctx_exp(1)), visitExp(ctx_exp(2)), - visitStringLit(ctx.StringLit) + visitStringLit(ctx.StringLit), + name = stmtName ) case "cover" => Verification( @@ -382,7 +387,8 @@ class Visitor(infoMode: InfoMode) extends AbstractParseTreeVisitor[FirrtlNode] w visitExp(ctx_exp(0)), visitExp(ctx_exp(1)), visitExp(ctx_exp(2)), - visitStringLit(ctx.StringLit) + visitStringLit(ctx.StringLit), + name = stmtName ) // end formal case "skip" => EmptyStmt diff --git a/src/main/scala/firrtl/WIR.scala b/src/main/scala/firrtl/WIR.scala index a0b85007ac..78536a3632 100644 --- a/src/main/scala/firrtl/WIR.scala +++ b/src/main/scala/firrtl/WIR.scala @@ -119,6 +119,7 @@ case class WDefInstanceConnector( portCons: Seq[(Expression, Expression)]) extends Statement with IsDeclaration + with CanBeReferenced with UseSerializer { def mapExpr(f: Expression => Expression): Statement = this.copy(portCons = portCons.map { case (e1, e2) => (f(e1), f(e2)) }) @@ -346,6 +347,7 @@ case class CDefMemory( readUnderWrite: ReadUnderWrite.Value = ReadUnderWrite.Undefined) extends Statement with HasInfo + with CanBeReferenced with UseSerializer { def mapExpr(f: Expression => Expression): Statement = this def mapStmt(f: Statement => Statement): Statement = this @@ -361,6 +363,7 @@ case class CDefMemory( case class CDefMPort(info: Info, name: String, tpe: Type, mem: String, exps: Seq[Expression], direction: MPortDir) extends Statement with HasInfo + with CanBeReferenced with UseSerializer { def mapExpr(f: Expression => Expression): Statement = this.copy(exps = exps.map(f)) def mapStmt(f: Statement => Statement): Statement = this diff --git a/src/main/scala/firrtl/analyses/SymbolTable.scala b/src/main/scala/firrtl/analyses/SymbolTable.scala index 3b304bc1f3..e4a534444f 100644 --- a/src/main/scala/firrtl/analyses/SymbolTable.scala +++ b/src/main/scala/firrtl/analyses/SymbolTable.scala @@ -87,6 +87,10 @@ object SymbolTable { case d: DefNode => table.declare(d) case d: DefWire => table.declare(d) case d: DefRegister => table.declare(d) + // Matches named statements like printf, stop, assert, assume, cover if the name is not empty. + // Empty names are allowed for backwards compatibility reasons and + // indicate that the entity has essentially no name. + case s: IsDeclaration if s.name.nonEmpty => table.declare(s.name, UnknownType, firrtl.UnknownKind) case other => other.foreachStmt(scanStatement) } } diff --git a/src/main/scala/firrtl/graph/DiGraph.scala b/src/main/scala/firrtl/graph/DiGraph.scala index 9f6ffeb2b3..99bf84038d 100644 --- a/src/main/scala/firrtl/graph/DiGraph.scala +++ b/src/main/scala/firrtl/graph/DiGraph.scala @@ -423,8 +423,7 @@ class DiGraph[T](private[graph] val edges: LinkedHashMap[T, LinkedHashSet[T]]) { rec(nextTab, nodex, nextMark + " ", acc) } } - this.findSources - .toList // Convert LinkedHashSet to List to avoid determinism issues + this.findSources.toList // Convert LinkedHashSet to List to avoid determinism issues .sortBy(_.toString) // Make order deterministic .foldLeft(Nil: List[String]) { case (acc, root) => rec("", root, "", acc) diff --git a/src/main/scala/firrtl/ir/IR.scala b/src/main/scala/firrtl/ir/IR.scala index 1b564d4208..9c3d6186f3 100644 --- a/src/main/scala/firrtl/ir/IR.scala +++ b/src/main/scala/firrtl/ir/IR.scala @@ -4,7 +4,7 @@ package firrtl package ir import Utils.{dec2string, trim} -import dataclass.data +import dataclass.{data, since} import firrtl.constraint.{Constraint, IsKnown, IsVar} import org.apache.commons.text.translate.{AggregateTranslator, JavaUnicodeEscaper, LookupTranslator} @@ -227,6 +227,13 @@ abstract class Expression extends FirrtlNode { */ sealed trait RefLikeExpression extends Expression { def flow: Flow } +/** Represents a statement that can be referenced in a firrtl expression. + * This explicitly excludes named side-effecting statements like Print, Stop and Verification. + * Note: This trait cannot be sealed since the memory ports are declared in WIR.scala. + * Once we fully remove all WIR, this trait could be sealed. + */ +trait CanBeReferenced + object Reference { /** Creates a Reference from a Wire */ @@ -387,7 +394,11 @@ abstract class Statement extends FirrtlNode { def foreachString(f: String => Unit): Unit def foreachInfo(f: Info => Unit): Unit } -case class DefWire(info: Info, name: String, tpe: Type) extends Statement with IsDeclaration with UseSerializer { +case class DefWire(info: Info, name: String, tpe: Type) + extends Statement + with IsDeclaration + with CanBeReferenced + with UseSerializer { def mapStmt(f: Statement => Statement): Statement = this def mapExpr(f: Expression => Expression): Statement = this def mapType(f: Type => Type): Statement = DefWire(info, name, f(tpe)) @@ -408,6 +419,7 @@ case class DefRegister( init: Expression) extends Statement with IsDeclaration + with CanBeReferenced with UseSerializer { def mapStmt(f: Statement => Statement): Statement = this def mapExpr(f: Expression => Expression): Statement = @@ -429,6 +441,7 @@ object DefInstance { case class DefInstance(info: Info, name: String, module: String, tpe: Type = UnknownType) extends Statement with IsDeclaration + with CanBeReferenced with UseSerializer { def mapExpr(f: Expression => Expression): Statement = this def mapStmt(f: Statement => Statement): Statement = this @@ -462,6 +475,7 @@ case class DefMemory( readUnderWrite: ReadUnderWrite.Value = ReadUnderWrite.Undefined) extends Statement with IsDeclaration + with CanBeReferenced with UseSerializer { def mapStmt(f: Statement => Statement): Statement = this def mapExpr(f: Expression => Expression): Statement = this @@ -477,6 +491,7 @@ case class DefMemory( case class DefNode(info: Info, name: String, value: Expression) extends Statement with IsDeclaration + with CanBeReferenced with UseSerializer { def mapStmt(f: Statement => Statement): Statement = this def mapExpr(f: Expression => Expression): Statement = DefNode(info, name, f(value)) @@ -594,22 +609,24 @@ case class Attach(info: Info, exprs: Seq[Expression]) extends Statement with Has def foreachString(f: String => Unit): Unit = () def foreachInfo(f: Info => Unit): Unit = f(info) } -@data class Stop(info: Info, ret: Int, clk: Expression, en: Expression) + +@data class Stop(info: Info, ret: Int, clk: Expression, en: Expression, @since("FIRRTL 1.5") name: String = "") extends Statement with HasInfo + with IsDeclaration with UseSerializer { def mapStmt(f: Statement => Statement): Statement = this - def mapExpr(f: Expression => Expression): Statement = Stop(info, ret, f(clk), f(en)) + def mapExpr(f: Expression => Expression): Statement = Stop(info, ret, f(clk), f(en), name) def mapType(f: Type => Type): Statement = this - def mapString(f: String => String): Statement = this + def mapString(f: String => String): Statement = withName(f(name)) def mapInfo(f: Info => Info): Statement = this.copy(info = f(info)) def foreachStmt(f: Statement => Unit): Unit = () def foreachExpr(f: Expression => Unit): Unit = { f(clk); f(en) } def foreachType(f: Type => Unit): Unit = () - def foreachString(f: String => Unit): Unit = () + def foreachString(f: String => Unit): Unit = f(name) def foreachInfo(f: Info => Unit): Unit = f(info) def copy(info: Info = info, ret: Int = ret, clk: Expression = clk, en: Expression = en): Stop = { - Stop(info, ret, clk, en) + Stop(info, ret, clk, en, name) } } object Stop { @@ -622,19 +639,22 @@ object Stop { string: StringLit, args: Seq[Expression], clk: Expression, - en: Expression) + en: Expression, + @since("FIRRTL 1.5") + name: String = "") extends Statement with HasInfo + with IsDeclaration with UseSerializer { def mapStmt(f: Statement => Statement): Statement = this - def mapExpr(f: Expression => Expression): Statement = Print(info, string, args.map(f), f(clk), f(en)) + def mapExpr(f: Expression => Expression): Statement = Print(info, string, args.map(f), f(clk), f(en), name) def mapType(f: Type => Type): Statement = this - def mapString(f: String => String): Statement = this + def mapString(f: String => String): Statement = withName(f(name)) def mapInfo(f: Info => Info): Statement = this.copy(info = f(info)) def foreachStmt(f: Statement => Unit): Unit = () def foreachExpr(f: Expression => Unit): Unit = { args.foreach(f); f(clk); f(en) } def foreachType(f: Type => Unit): Unit = () - def foreachString(f: String => Unit): Unit = () + def foreachString(f: String => Unit): Unit = f(name) def foreachInfo(f: Info => Unit): Unit = f(info) def copy( info: Info = info, @@ -643,7 +663,7 @@ object Stop { clk: Expression = clk, en: Expression = en ): Print = { - Print(info, string, args, clk, en) + Print(info, string, args, clk, en, name) } } object Print { @@ -665,20 +685,23 @@ object Formal extends Enumeration { clk: Expression, pred: Expression, en: Expression, - msg: StringLit) + msg: StringLit, + @since("FIRRTL 1.5") + name: String = "") extends Statement with HasInfo + with IsDeclaration with UseSerializer { def mapStmt(f: Statement => Statement): Statement = this def mapExpr(f: Expression => Expression): Statement = copy(clk = f(clk), pred = f(pred), en = f(en)) def mapType(f: Type => Type): Statement = this - def mapString(f: String => String): Statement = this + def mapString(f: String => String): Statement = withName(f(name)) def mapInfo(f: Info => Info): Statement = copy(info = f(info)) def foreachStmt(f: Statement => Unit): Unit = () def foreachExpr(f: Expression => Unit): Unit = { f(clk); f(pred); f(en); } def foreachType(f: Type => Unit): Unit = () - def foreachString(f: String => Unit): Unit = () + def foreachString(f: String => Unit): Unit = f(name) def foreachInfo(f: Info => Unit): Unit = f(info) def copy( op: Formal.Value = op, @@ -688,7 +711,7 @@ object Formal extends Enumeration { en: Expression = en, msg: StringLit = msg ): Verification = { - Verification(op, info, clk, pred, en, msg) + Verification(op, info, clk, pred, en, msg, name) } } object Verification { @@ -1016,6 +1039,7 @@ case class Port( tpe: Type) extends FirrtlNode with IsDeclaration + with CanBeReferenced with UseSerializer { def mapType(f: Type => Type): Port = Port(info, name, direction, f(tpe)) def mapString(f: String => String): Port = Port(info, f(name), direction, tpe) diff --git a/src/main/scala/firrtl/ir/Serializer.scala b/src/main/scala/firrtl/ir/Serializer.scala index 983a786620..caea0a9cae 100644 --- a/src/main/scala/firrtl/ir/Serializer.scala +++ b/src/main/scala/firrtl/ir/Serializer.scala @@ -102,11 +102,13 @@ object Serializer { s(it.next()) if (it.hasNext) newLineAndIndent() } - case Stop(info, ret, clk, en) => - b ++= "stop("; s(clk); b ++= ", "; s(en); b ++= ", "; b ++= ret.toString; b += ')'; s(info) - case Print(info, string, args, clk, en) => + case stop @ Stop(info, ret, clk, en) => + b ++= "stop("; s(clk); b ++= ", "; s(en); b ++= ", "; b ++= ret.toString; b += ')' + sStmtName(stop.name); s(info) + case print @ Print(info, string, args, clk, en) => b ++= "printf("; s(clk); b ++= ", "; s(en); b ++= ", "; b ++= string.escape - if (args.nonEmpty) b ++= ", "; s(args, ", "); b += ')'; s(info) + if (args.nonEmpty) b ++= ", "; s(args, ", "); b += ')' + sStmtName(print.name); s(info) case IsInvalid(info, expr) => s(expr); b ++= " is invalid"; s(info) case DefWire(info, name, tpe) => b ++= "wire "; b ++= name; b ++= " : "; s(tpe); s(info) case DefRegister(info, name, tpe, clock, reset, init) => @@ -138,9 +140,9 @@ object Serializer { case Attach(info, exprs) => // exprs should never be empty since the attach statement takes *at least* two signals according to the spec b ++= "attach ("; s(exprs, ", "); b += ')'; s(info) - case Verification(op, info, clk, pred, en, msg) => + case veri @ Verification(op, info, clk, pred, en, msg) => b ++= op.toString; b += '('; s(List(clk, pred, en), ", ", false); b ++= msg.escape - b += ')'; s(info) + b += ')'; sStmtName(veri.name); s(info) // WIR case firrtl.CDefMemory(info, name, tpe, size, seq, readUnderWrite) => @@ -155,6 +157,10 @@ object Serializer { case other => b ++= other.serialize // Handle user-defined nodes } + private def sStmtName(lbl: String)(implicit b: StringBuilder): Unit = { + if (lbl.nonEmpty) { b ++= s" : $lbl" } + } + private def s(node: Width)(implicit b: StringBuilder, indent: Int): Unit = node match { case IntWidth(width) => b += '<'; b ++= width.toString(); b += '>' case UnknownWidth => // empty string diff --git a/src/main/scala/firrtl/passes/CheckHighForm.scala b/src/main/scala/firrtl/passes/CheckHighForm.scala index e3468c4e8c..05635d0054 100644 --- a/src/main/scala/firrtl/passes/CheckHighForm.scala +++ b/src/main/scala/firrtl/passes/CheckHighForm.scala @@ -22,6 +22,10 @@ trait CheckHighFormLike { this: Pass => moduleNS += name scopes.head += name } + // ensures that the name cannot be used again, but prevent references to this name + def addToNamespace(name: String): Unit = { + moduleNS += name + } def expandMPortVisibility(port: CDefMPort): Unit = { // Legacy CHIRRTL ports are visible in any scope where their parent memory is visible scopes.find(_.contains(port.mem)).getOrElse(scopes.head) += port.name @@ -250,10 +254,19 @@ trait CheckHighFormLike { this: Pass => e.foreach(checkHighFormE(info, mname, names)) } - def checkName(info: Info, mname: String, names: ScopeView)(name: String): Unit = { - if (!names.legalDecl(name)) - errors.append(new NotUniqueException(info, mname, name)) - names.declare(name) + def checkName(info: Info, mname: String, names: ScopeView, canBeReference: Boolean)(name: String): Unit = { + // Empty names are allowed for backwards compatibility reasons and + // indicate that the entity has essentially no name. + if (name.isEmpty) { assert(!canBeReference, "A statement with an empty name cannot be used as a reference!") } + else { + if (!names.legalDecl(name)) + errors.append(new NotUniqueException(info, mname, name)) + if (canBeReference) { + names.declare(name) + } else { + names.addToNamespace(name) + } + } } def checkInstance(info: Info, child: String, parent: String): Unit = { @@ -270,7 +283,11 @@ trait CheckHighFormLike { this: Pass => case NoInfo => minfo case x => x } - s.foreach(checkName(info, mname, names)) + val canBeReference = s match { + case _: CanBeReferenced => true + case _ => false + } + s.foreach(checkName(info, mname, names, canBeReference)) s match { case DefRegister(info, name, tpe, _, reset, init) => if (hasFlip(tpe)) diff --git a/src/main/scala/firrtl/passes/Inline.scala b/src/main/scala/firrtl/passes/Inline.scala index 4eba5d5901..912acf8ef1 100644 --- a/src/main/scala/firrtl/passes/Inline.scala +++ b/src/main/scala/firrtl/passes/Inline.scala @@ -187,18 +187,23 @@ class InlineInstances extends Transform with DependencyAPIMigration with Registe renameMap: RenameMap )(s: Statement ): Statement = { - def onName(ofModuleOpt: Option[String])(name: String) = { - if (prefix.nonEmpty && !ns.tryName(prefix + name)) { - throw new Exception(s"Inlining failed. Inlined name '${prefix + name}' already exists") - } - ofModuleOpt match { - case None => - renameMap.record(currentModule.ref(name), nextModule.ref(prefix + name)) - case Some(ofModule) => - renameMap.record(currentModule.instOf(name, ofModule), nextModule.instOf(prefix + name, ofModule)) + def onName(ofModuleOpt: Option[String])(name: String): String = { + // Empty names are allowed for backwards compatibility reasons and + // indicate that the entity has essentially no name and thus cannot be prefixed. + if (name.isEmpty) { name } + else { + if (prefix.nonEmpty && !ns.tryName(prefix + name)) { + throw new Exception(s"Inlining failed. Inlined name '${prefix + name}' already exists") + } + ofModuleOpt match { + case None => + renameMap.record(currentModule.ref(name), nextModule.ref(prefix + name)) + case Some(ofModule) => + renameMap.record(currentModule.instOf(name, ofModule), nextModule.instOf(prefix + name, ofModule)) + } + renames(name) = prefix + name + prefix + name } - renames(name) = prefix + name - prefix + name } s match { diff --git a/src/main/scala/firrtl/transforms/DeadCodeElimination.scala b/src/main/scala/firrtl/transforms/DeadCodeElimination.scala index 13173fddbc..1d9bfd0e1f 100644 --- a/src/main/scala/firrtl/transforms/DeadCodeElimination.scala +++ b/src/main/scala/firrtl/transforms/DeadCodeElimination.scala @@ -258,6 +258,11 @@ class DeadCodeElimination extends Transform with RegisteredTransform with Depend renames.delete(inst.name) EmptyStmt } + case print: Print => deleteIfNotEnabled(print, print.en) + case stop: Stop => deleteIfNotEnabled(stop, stop.en) + case formal: Verification => deleteIfNotEnabled(formal, formal.en) + // Statements are also declarations and thus this case needs to come *after* checking the + // print, stop and verification statements. case decl: IsDeclaration => val node = LogicNode(mod.name, decl.name) if (deadNodes.contains(node)) { @@ -265,10 +270,7 @@ class DeadCodeElimination extends Transform with RegisteredTransform with Depend renames.delete(decl.name) EmptyStmt } else decl - case print: Print => deleteIfNotEnabled(print, print.en) - case stop: Stop => deleteIfNotEnabled(stop, stop.en) - case formal: Verification => deleteIfNotEnabled(formal, formal.en) - case con: Connect => + case con: Connect => val node = getDeps(con.loc) match { case Seq(elt) => elt } if (deadNodes.contains(node)) EmptyStmt else con case Attach(info, exprs) => // If any exprs are dead then all are diff --git a/src/main/scala/firrtl/transforms/EnsureNamedStatements.scala b/src/main/scala/firrtl/transforms/EnsureNamedStatements.scala new file mode 100644 index 0000000000..a40409f9ff --- /dev/null +++ b/src/main/scala/firrtl/transforms/EnsureNamedStatements.scala @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: Apache-2.0 + +package firrtl.transforms + +import firrtl._ +import firrtl.ir._ + +/** Adds default names to print, stop and verification statements if their name is empty. */ +object EnsureNamedStatements extends Transform with DependencyAPIMigration { + override def invalidates(a: Transform) = false + + override protected def execute(state: CircuitState): CircuitState = { + val c = state.circuit.mapModule(onModule) + state.copy(circuit = c) + } + + private def onModule(m: DefModule): DefModule = m match { + case e: ExtModule => e + case mod: Module => + val namespace = Namespace(mod) + // Ensure we always start with _0 suffix + val prefixes = Seq("cover", "assert", "assume", "print", "stop") + prefixes.filterNot(namespace.contains).foreach(namespace.newName) + mod.mapStmt(onStmt(namespace)) + } + + private def onStmt(namespace: Namespace)(stmt: Statement): Statement = stmt match { + case s: Print if s.name.isEmpty => s.withName(namespace.newName("print")) + case s: Stop if s.name.isEmpty => s.withName(namespace.newName("stop")) + case s: Verification if s.name.isEmpty => + val baseName = s.op match { + case Formal.Cover => "cover" + case Formal.Assert => "assert" + case Formal.Assume => "assume" + } + s.withName(namespace.newName(baseName)) + case other => other.mapStmt(onStmt(namespace)) + } +} diff --git a/src/main/scala/firrtl/transforms/RemoveWires.scala b/src/main/scala/firrtl/transforms/RemoveWires.scala index ee03ad300c..f2907db27f 100644 --- a/src/main/scala/firrtl/transforms/RemoveWires.scala +++ b/src/main/scala/firrtl/transforms/RemoveWires.scala @@ -115,7 +115,10 @@ class RemoveWires extends Transform with DependencyAPIMigration { val initDep = Some(reg.init).filter(we(WRef(reg)) != we(_)) // Dependency exists IF reg doesn't init itself regInfo(we(WRef(reg))) = reg netlist(we(WRef(reg))) = (Seq(reg.clock) ++ resetDep ++ initDep, reg.info) - case decl: IsDeclaration => // Keep all declarations except for nodes and non-Analog wires + case decl: CanBeReferenced => + // Keep all declarations except for nodes and non-Analog wires and "other" statements. + // Thus this is expected to match DefInstance and DefMemory which both do not connect to + // any signals directly (instead a separate Connect is used). decls += decl case con @ Connect(cinfo, lhs, rhs) => kind(lhs) match { diff --git a/src/test/scala/firrtl/testutils/FirrtlSpec.scala b/src/test/scala/firrtl/testutils/FirrtlSpec.scala index 6de2af1e9a..24793437d4 100644 --- a/src/test/scala/firrtl/testutils/FirrtlSpec.scala +++ b/src/test/scala/firrtl/testutils/FirrtlSpec.scala @@ -123,21 +123,21 @@ trait FirrtlRunners extends BackendCompilationUtilities { } /** Check equivalence of Firrtl with reference Verilog - * + * * @note the name of the reference Verilog module is grabbed via regex * @param inputFirrtl string containing Firrtl source * @param referenceVerilog Verilog that will be used as reference for LEC * @param timesteps the maximum number of timesteps to consider */ def firrtlEquivalenceWithVerilog( - inputFirrtl: String, - referenceVerilog: String, - timesteps: Int = 1 + inputFirrtl: String, + referenceVerilog: String, + timesteps: Int = 1 ): Unit = { val VerilogModule = """(?s).*module\s(\w+).*""".r val refName = referenceVerilog match { case VerilogModule(name) => name - case _ => throw new Exception(s"Reference Verilog must match simple regex! $VerilogModule") + case _ => throw new Exception(s"Reference Verilog must match simple regex! $VerilogModule") } val circuit = Parser.parse(inputFirrtl.split("\n").toIterator) val inputName = circuit.main @@ -163,7 +163,6 @@ trait FirrtlRunners extends BackendCompilationUtilities { assert(BackendCompilationUtilities.yosysExpectSuccess(inputName, refName, testDir, timesteps)) } - /** Compiles input Firrtl to Verilog */ def compileToVerilog(input: String, annotations: AnnotationSeq = Seq.empty): String = { val circuit = Parser.parse(input.split("\n").toIterator) diff --git a/src/test/scala/firrtl/transforms/EnsureNamedStatementsSpec.scala b/src/test/scala/firrtl/transforms/EnsureNamedStatementsSpec.scala new file mode 100644 index 0000000000..4c99399482 --- /dev/null +++ b/src/test/scala/firrtl/transforms/EnsureNamedStatementsSpec.scala @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: Apache-2.0 + +package firrtl.transforms + +import firrtl.options.Dependency +import firrtl.testutils.LeanTransformSpec + +class EnsureNamedStatementsSpec extends LeanTransformSpec(Seq(Dependency(EnsureNamedStatements))) { + behavior.of("EnsureNamedStatements") + + it should "automatically name statements that do not have a name yet" in { + val src = """circuit test : + | module test : + | input clock : Clock + | input stop_ : UInt<1> + | assert(clock, UInt(1), not(UInt(0)), "") + | stop(clock, UInt(1), 1) : stop_123 + | stop(clock, UInt(1), 1) + | assert(clock, UInt(0), UInt(0), "") + | assume(clock, UInt(0), UInt(0), "") + | cover(clock, UInt(0), UInt(0), "") + | cover(clock, UInt(0), UInt(0), "") + | + |""".stripMargin + + val result = compile(src, List()).circuit.serialize.split('\n').map(_.trim) + + val expected = List( + """assert(clock, UInt<1>("h1"), not(UInt<1>("h0")), "") : assert_0""", + """stop(clock, UInt<1>("h1"), 1) : stop_123""", + """stop(clock, UInt<1>("h1"), 1) : stop_0""", + """assert(clock, UInt<1>("h0"), UInt<1>("h0"), "") : assert_1""", + """assume(clock, UInt<1>("h0"), UInt<1>("h0"), "") : assume_0""", + """cover(clock, UInt<1>("h0"), UInt<1>("h0"), "") : cover_0""", + """cover(clock, UInt<1>("h0"), UInt<1>("h0"), "") : cover_1""" + ) + expected.foreach(e => assert(result.contains(e))) + } +} diff --git a/src/test/scala/firrtlTests/CheckSpec.scala b/src/test/scala/firrtlTests/CheckSpec.scala index 1137f8cd3b..a3efc78410 100644 --- a/src/test/scala/firrtlTests/CheckSpec.scala +++ b/src/test/scala/firrtlTests/CheckSpec.scala @@ -384,6 +384,36 @@ class CheckSpec extends AnyFlatSpec with Matchers { } } + "Attempting to shadow a statement name" should "throw an error" in { + val input = + s"""|circuit scopes: + | module scopes: + | input c: Clock + | input i: UInt<1> + | output o: UInt<1> + | wire x: UInt<1> + | when i: + | stop(c, UInt(1), 1) : x + | o <= and(x, i) + |""".stripMargin + assertThrows[CheckHighForm.NotUniqueException] { + checkHighInput(input) + } + } + + "Colliding statement names" should "throw an error" in { + val input = + s"""|circuit test: + | module test: + | input c: Clock + | stop(c, UInt(1), 1) : x + | stop(c, UInt(1), 1) : x + |""".stripMargin + assertThrows[CheckHighForm.NotUniqueException] { + checkHighInput(input) + } + } + "Conditionally statements" should "create separate consequent and alternate scopes" in { val input = s"""|circuit scopes: @@ -536,6 +566,20 @@ class CheckSpec extends AnyFlatSpec with Matchers { } } + it should "throw an exception if a statement name is used as a reference" in { + val src = """ + |circuit test: + | module test: + | input clock: Clock + | output a: UInt<2> + | stop(clock, UInt(1), 1) : hello + | a <= hello + |""".stripMargin + assertThrows[CheckHighForm.UndeclaredReferenceException] { + checkHighInput(src) + } + } + } object CheckSpec { diff --git a/src/test/scala/firrtlTests/InlineInstancesTests.scala b/src/test/scala/firrtlTests/InlineInstancesTests.scala index 6bee2b7759..cc7257d242 100644 --- a/src/test/scala/firrtlTests/InlineInstancesTests.scala +++ b/src/test/scala/firrtlTests/InlineInstancesTests.scala @@ -460,6 +460,60 @@ class InlineInstancesTests extends LowTransformSpec { ) } + "inlining named statements" should "work" in { + val input = + """circuit Top : + | module Top : + | input clock : Clock + | input a : UInt<32> + | output b : UInt<32> + | inst i of Inline + | i.clock <= clock + | i.a <= a + | b <= i.b + | module Inline : + | input clock : Clock + | input a : UInt<32> + | output b : UInt<32> + | b <= a + | assert(clock, UInt(1), eq(a,b), "a == b") : assert1 + | assert(clock, UInt(1), not(eq(a,b)), "a != b") + | stop(clock, UInt(0), 0) + |""".stripMargin + val check = + """circuit Top : + | module Top : + | input clock : Clock + | input a : UInt<32> + | output b : UInt<32> + | wire i_clock : Clock + | wire i_a : UInt<32> + | wire i_b : UInt<32> + | i_b <= i_a + | assert(i_clock, UInt(1), eq(i_a, i_b), "a == b") : i_assert1 + | assert(i_clock, UInt(1), not(eq(i_a, i_b)), "a != b") + | stop(i_clock, UInt(0), 0) + | b <= i_b + | i_clock <= clock + | i_a <= a + |""".stripMargin + val top = CircuitTarget("Top").module("Top") + val inlined = top.instOf("i", "Inline") + + executeWithAnnos( + input, + check, + Seq( + inline("Inline"), + NoCircuitDedupAnnotation, + DummyAnno(inlined.ref("assert1")) + ), + Seq( + DummyAnno(top.ref("i_assert1")) + ) + ) + } + "inlining both grandparent and grandchild" should "should work" in { val input = """circuit Top : diff --git a/src/test/scala/firrtlTests/LowerTypesSpec.scala b/src/test/scala/firrtlTests/LowerTypesSpec.scala index da84b362c5..78d03e68ac 100644 --- a/src/test/scala/firrtlTests/LowerTypesSpec.scala +++ b/src/test/scala/firrtlTests/LowerTypesSpec.scala @@ -321,6 +321,25 @@ class LowerTypesUniquifySpec extends FirrtlFlatSpec { executeTest(input, expected) } + it should "rename nodes colliding with labled statements" in { + val input = + """circuit Test : + | module Test : + | input clock : Clock + | reg x : { b : UInt<1>, c : { d : UInt<2>, e : UInt<3>}[2], c_1_e : UInt<4>}[2], clock + | node a = x + | printf(clock, UInt(1), "") : a_0_c_ + | assert(clock, UInt(1), UInt(1), "") : a__0 + """.stripMargin + val expected = Seq( + "node a___0_b = x_0_b", + "node a___1_c__1_e = x_1_c__1_e", + "node a___1_c_1_e = x_1_c_1_e" + ) + + executeTest(input, expected) + } + it should "rename DefRegister expressions: clock, reset, and init" in { val input = """circuit Test : diff --git a/src/test/scala/firrtlTests/ParserSpec.scala b/src/test/scala/firrtlTests/ParserSpec.scala index 373b960c65..ba61b134e4 100644 --- a/src/test/scala/firrtlTests/ParserSpec.scala +++ b/src/test/scala/firrtlTests/ParserSpec.scala @@ -147,6 +147,27 @@ class ParserSpec extends FirrtlFlatSpec { } } + // ********** Statement labels ********** + it should "allow certain statement to have a label" in { + val prelude = Seq("circuit top :", " module top :", " input c : Clock") + val statements = Seq("stop(c, UInt(1), 0)", "printf(c, UInt(1), \"\")") ++ + Seq("assert", "assume", "cover").map(_ + "(c, UInt(1), UInt(1), \"\")") + val validLabels = Seq(":test" -> "test", " :test" -> "test", " : test" -> "test", " : test01" -> "test01") + statements.foreach { stmt => + validLabels.foreach { + case (lbl, expected) => + val line = " " + stmt + lbl + val src = (prelude :+ line).mkString("\n") + "\n" + val res = firrtl.Parser.parse(src) + CircuitState(res, Nil) should containTree { + case s: Stop => s.name == expected + case s: Print => s.name == expected + case s: Verification => s.name == expected + } + } + } + } + // ********** Keywords ********** "Keywords" should "be allowed as Ids" in { import KeywordTests._ diff --git a/src/test/scala/firrtlTests/VerilogEquivalenceSpec.scala b/src/test/scala/firrtlTests/VerilogEquivalenceSpec.scala index d88309ced0..747f668990 100644 --- a/src/test/scala/firrtlTests/VerilogEquivalenceSpec.scala +++ b/src/test/scala/firrtlTests/VerilogEquivalenceSpec.scala @@ -7,116 +7,116 @@ import firrtl.testutils._ class VerilogEquivalenceSpec extends FirrtlFlatSpec { "mul followed by cat" should "be correct" in { val header = s""" - |circuit Multiply : - | module Multiply : - | input x : UInt<4> - | input y : UInt<2> - | input z : UInt<2> - | output out : UInt<8> - |""".stripMargin + |circuit Multiply : + | module Multiply : + | input x : UInt<4> + | input y : UInt<2> + | input z : UInt<2> + | output out : UInt<8> + |""".stripMargin val input1 = header + """ - | out <= cat(z, mul(x, y))""".stripMargin + | out <= cat(z, mul(x, y))""".stripMargin val input2 = header + """ - | node n = mul(x, y) - | node m = cat(z, n) - | out <= m""".stripMargin + | node n = mul(x, y) + | node m = cat(z, n) + | out <= m""".stripMargin val expected = s""" - |module MultiplyRef( - | input [3:0] x, - | input [1:0] y, - | input [1:0] z, - | output [7:0] out - |); - | wire [5:0] w = x * y; - | assign out = {z, w}; - |endmodule""".stripMargin + |module MultiplyRef( + | input [3:0] x, + | input [1:0] y, + | input [1:0] z, + | output [7:0] out + |); + | wire [5:0] w = x * y; + | assign out = {z, w}; + |endmodule""".stripMargin firrtlEquivalenceWithVerilog(input1, expected) firrtlEquivalenceWithVerilog(input2, expected) } "div followed by cat" should "be correct" in { val header = s""" - |circuit Divide : - | module Divide : - | input x : UInt<4> - | input y : UInt<2> - | input z : UInt<2> - | output out : UInt<6> - |""".stripMargin + |circuit Divide : + | module Divide : + | input x : UInt<4> + | input y : UInt<2> + | input z : UInt<2> + | output out : UInt<6> + |""".stripMargin val input1 = header + """ - | out <= cat(z, div(x, y))""".stripMargin + | out <= cat(z, div(x, y))""".stripMargin val input2 = header + """ - | node n = div(x, y) - | node m = cat(z, n) - | out <= m""".stripMargin + | node n = div(x, y) + | node m = cat(z, n) + | out <= m""".stripMargin val expected = s""" - |module DivideRef( - | input [3:0] x, - | input [1:0] y, - | input [1:0] z, - | output [5:0] out - |); - | wire [3:0] w = x / y; - | assign out = {z, w}; - |endmodule""".stripMargin + |module DivideRef( + | input [3:0] x, + | input [1:0] y, + | input [1:0] z, + | output [5:0] out + |); + | wire [3:0] w = x / y; + | assign out = {z, w}; + |endmodule""".stripMargin firrtlEquivalenceWithVerilog(input1, expected) firrtlEquivalenceWithVerilog(input2, expected) } "signed mul followed by cat" should "be correct" in { val header = s""" - |circuit SignedMultiply : - | module SignedMultiply : - | input x : SInt<4> - | input y : SInt<2> - | input z : SInt<2> - | output out : UInt<8> - |""".stripMargin + |circuit SignedMultiply : + | module SignedMultiply : + | input x : SInt<4> + | input y : SInt<2> + | input z : SInt<2> + | output out : UInt<8> + |""".stripMargin val input1 = header + """ - | out <= cat(z, mul(x, y))""".stripMargin + | out <= cat(z, mul(x, y))""".stripMargin val input2 = header + """ - | node n = mul(x, y) - | node m = cat(z, n) - | out <= m""".stripMargin + | node n = mul(x, y) + | node m = cat(z, n) + | out <= m""".stripMargin val expected = s""" - |module SignedMultiplyRef( - | input signed [3:0] x, - | input signed [1:0] y, - | input signed [1:0] z, - | output [7:0] out - |); - | wire [5:0] w = x * y; - | assign out = {z, w}; - |endmodule""".stripMargin + |module SignedMultiplyRef( + | input signed [3:0] x, + | input signed [1:0] y, + | input signed [1:0] z, + | output [7:0] out + |); + | wire [5:0] w = x * y; + | assign out = {z, w}; + |endmodule""".stripMargin firrtlEquivalenceWithVerilog(input1, expected) firrtlEquivalenceWithVerilog(input2, expected) } "signed div followed by cat" should "be correct" in { val header = s""" - |circuit SignedDivide : - | module SignedDivide : - | input x : SInt<4> - | input y : SInt<2> - | input z : SInt<2> - | output out : UInt<7> - |""".stripMargin + |circuit SignedDivide : + | module SignedDivide : + | input x : SInt<4> + | input y : SInt<2> + | input z : SInt<2> + | output out : UInt<7> + |""".stripMargin val input1 = header + """ - | out <= cat(z, div(x, y))""".stripMargin + | out <= cat(z, div(x, y))""".stripMargin val input2 = header + """ - | node n = div(x, y) - | node m = cat(z, n) - | out <= m""".stripMargin + | node n = div(x, y) + | node m = cat(z, n) + | out <= m""".stripMargin val expected = s""" - |module SignedDivideRef( - | input signed [3:0] x, - | input signed [1:0] y, - | input signed [1:0] z, - | output [6:0] out - |); - | wire [4:0] w = x / y; - | assign out = {z, w}; - |endmodule""".stripMargin + |module SignedDivideRef( + | input signed [3:0] x, + | input signed [1:0] y, + | input signed [1:0] z, + | output [6:0] out + |); + | wire [4:0] w = x / y; + | assign out = {z, w}; + |endmodule""".stripMargin firrtlEquivalenceWithVerilog(input1, expected) firrtlEquivalenceWithVerilog(input2, expected) } From edb91f7bc613026f824519786c3ce25740bb21c3 Mon Sep 17 00:00:00 2001 From: Kevin Laeufer Date: Wed, 17 Feb 2021 14:42:45 -0800 Subject: [PATCH 28/88] ExpandWhens: ensure that statement names are maintained (#2082) --- src/main/scala/firrtl/passes/ExpandWhens.scala | 6 +++--- .../scala/firrtlTests/ExpandWhensSpec.scala | 18 ++++++++++++++++-- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/main/scala/firrtl/passes/ExpandWhens.scala b/src/main/scala/firrtl/passes/ExpandWhens.scala index 7456d2ab06..8fb4e5fbd6 100644 --- a/src/main/scala/firrtl/passes/ExpandWhens.scala +++ b/src/main/scala/firrtl/passes/ExpandWhens.scala @@ -125,13 +125,13 @@ object ExpandWhens extends Pass { EmptyStmt // For simulation constructs, update simlist with predicated statement and return EmptyStmt case sx: Print => - simlist += (if (weq(p, one)) sx else Print(sx.info, sx.string, sx.args, sx.clk, AND(p, sx.en))) + simlist += (if (weq(p, one)) sx else sx.withEn(AND(p, sx.en))) EmptyStmt case sx: Stop => - simlist += (if (weq(p, one)) sx else Stop(sx.info, sx.ret, sx.clk, AND(p, sx.en))) + simlist += (if (weq(p, one)) sx else sx.withEn(AND(p, sx.en))) EmptyStmt case sx: Verification => - simlist += (if (weq(p, one)) sx else sx.copy(en = AND(p, sx.en))) + simlist += (if (weq(p, one)) sx else sx.withEn(AND(p, sx.en))) EmptyStmt // Expand conditionally, see comments below case sx: Conditionally => diff --git a/src/test/scala/firrtlTests/ExpandWhensSpec.scala b/src/test/scala/firrtlTests/ExpandWhensSpec.scala index c186b5168c..3c685734c4 100644 --- a/src/test/scala/firrtlTests/ExpandWhensSpec.scala +++ b/src/test/scala/firrtlTests/ExpandWhensSpec.scala @@ -142,10 +142,24 @@ class ExpandWhensSpec extends FirrtlFlatSpec { | input in : UInt<32> | input p : UInt<1> | when p : - | assert(clock, eq(in, UInt<1>("h1")), UInt<1>("h1"), "assert0") + | assert(clock, eq(in, UInt<1>("h1")), UInt<1>("h1"), "assert0") : test_assert | else : | skip""".stripMargin - val check = "assert(clock, eq(in, UInt<1>(\"h1\")), and(and(UInt<1>(\"h1\"), p), UInt<1>(\"h1\")), \"assert0\")" + val check = "assert(clock, eq(in, UInt<1>(\"h1\")), and(and(UInt<1>(\"h1\"), p), UInt<1>(\"h1\")), \"assert0\") : test_assert" + executeTest(input, check, true) + } + it should "handle stops" in { + val input = + """circuit Test : + | module Test : + | input clock : Clock + | input in : UInt<32> + | input p : UInt<1> + | when p : + | stop(clock, UInt(1), 1) : test_stop + | else : + | skip""".stripMargin + val check = """stop(clock, and(and(UInt<1>("h1"), p), UInt<1>("h1")), 1) : test_stop""" executeTest(input, check, true) } } From 89e9ab0bb0fd3f1f4a79eaf6209727684a2fa23f Mon Sep 17 00:00:00 2001 From: Albert Magyar Date: Thu, 25 Feb 2021 13:28:17 -0800 Subject: [PATCH 29/88] Emit space after 'if' for all Verilog conditional synchronous assignments (#2091) --- src/main/scala/firrtl/backends/verilog/VerilogEmitter.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/firrtl/backends/verilog/VerilogEmitter.scala b/src/main/scala/firrtl/backends/verilog/VerilogEmitter.scala index 3ecd127974..f1650ad741 100644 --- a/src/main/scala/firrtl/backends/verilog/VerilogEmitter.scala +++ b/src/main/scala/firrtl/backends/verilog/VerilogEmitter.scala @@ -756,7 +756,7 @@ class VerilogEmitter extends SeqTransform with Emitter { val lines = noResetAlwaysBlocks.getOrElseUpdate(clk, ArrayBuffer[Seq[Any]]()) if (weq(en, one)) lines += Seq(e, " <= ", value, ";") else { - lines += Seq("if(", en, ") begin") + lines += Seq("if (", en, ") begin") lines += Seq(tab, e, " <= ", value, ";", info) lines += Seq("end") } From 541a70c9489ec90118d45d4c953af0d0a33f8316 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Tue, 2 Mar 2021 13:42:18 -0800 Subject: [PATCH 30/88] Fix CI Checks (#2097) Bumping Scala minor version but not bumping CI guards on the version causes tests to no longer run. Change to using startsWith(...) so that minor version bumps won't cause issues in the future. Also run ScalaFmt --- .github/workflows/test.yml | 6 +++--- src/test/scala/firrtlTests/ExpandWhensSpec.scala | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 31f7ca6af7..134670b83b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -32,12 +32,12 @@ jobs: - name: Cache Scala uses: coursier/cache-action@v5 - name: Check Formatting (Scala 2.12 only) - if: matrix.scala == '2.12.12' + if: startsWith(matrix.scala, '2.12') run: sbt ++${{ matrix.scala }} scalafmtCheckAll - name: Unidoc run: sbt ++${{ matrix.scala }} unidoc - - name: Sanity check benchmarking scripts (Scala 2.12 only) - if: matrix.scala == '2.12.12' + - name: Sanity check benchmarking scripts (Scala 2.13 only) + if: startsWith(matrix.scala, '2.13') run: | benchmark/scripts/benchmark_cold_compile.py -N 2 --designs regress/ICache.fir --versions HEAD benchmark/scripts/find_heap_bound.py -- -cp firrtl*jar firrtl.stage.FirrtlMain -i regress/ICache.fir -o out -X verilog diff --git a/src/test/scala/firrtlTests/ExpandWhensSpec.scala b/src/test/scala/firrtlTests/ExpandWhensSpec.scala index 3c685734c4..52c87ffbc6 100644 --- a/src/test/scala/firrtlTests/ExpandWhensSpec.scala +++ b/src/test/scala/firrtlTests/ExpandWhensSpec.scala @@ -145,7 +145,8 @@ class ExpandWhensSpec extends FirrtlFlatSpec { | assert(clock, eq(in, UInt<1>("h1")), UInt<1>("h1"), "assert0") : test_assert | else : | skip""".stripMargin - val check = "assert(clock, eq(in, UInt<1>(\"h1\")), and(and(UInt<1>(\"h1\"), p), UInt<1>(\"h1\")), \"assert0\") : test_assert" + val check = + "assert(clock, eq(in, UInt<1>(\"h1\")), and(and(UInt<1>(\"h1\"), p), UInt<1>(\"h1\")), \"assert0\") : test_assert" executeTest(input, check, true) } it should "handle stops" in { From 38dc5401ea875037e23bbbe998fb1b0f9aef7334 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Tue, 2 Mar 2021 15:32:24 -0800 Subject: [PATCH 31/88] Remove Scala 2.11 (#2062) --- .github/workflows/test.yml | 2 +- build.sbt | 34 +++---------------- build.sc | 14 ++------ .../firrtl/stage/FirrtlAnnotations.scala | 9 +++-- .../stage/transforms/CheckScalaVersion.scala | 17 ++-------- .../smt/end2end/SMTCompilationTest.scala | 3 +- .../firrtlTests/stage/FirrtlMainSpec.scala | 28 +-------------- 7 files changed, 21 insertions(+), 86 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 134670b83b..f6af7cbd98 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - scala: [2.13.4, 2.12.13, 2.11.12] + scala: [2.13.4, 2.12.13] container: image: ucbbar/chisel3-tools options: --user github --entrypoint /bin/bash diff --git a/build.sbt b/build.sbt index d779920ba8..f047f35b8c 100644 --- a/build.sbt +++ b/build.sbt @@ -2,24 +2,10 @@ enablePlugins(SiteScaladocPlugin) -def javacOptionsVersion(scalaVersion: String): Seq[String] = { - Seq() ++ { - // Scala 2.12 requires Java 8, but we continue to generate - // Java 7 compatible code until we need Java 8 features - // for compatibility with old clients. - CrossVersion.partialVersion(scalaVersion) match { - case Some((2, scalaMajor: Long)) if scalaMajor < 12 => - Seq("-source", "1.7", "-target", "1.7") - case _ => - Seq("-source", "1.8", "-target", "1.8") - } - } -} - lazy val commonSettings = Seq( organization := "edu.berkeley.cs", scalaVersion := "2.12.13", - crossScalaVersions := Seq("2.13.4", "2.12.13", "2.11.12") + crossScalaVersions := Seq("2.13.4", "2.12.13") ) lazy val isAtLeastScala213 = Def.setting { @@ -39,7 +25,8 @@ lazy val firrtlSettings = Seq( "-language:implicitConversions", "-Yrangepos", // required by SemanticDB compiler plugin ), - javacOptions ++= javacOptionsVersion(scalaVersion.value), + // Always target Java8 for maximum compatibility + javacOptions ++= Seq("-source", "1.8", "-target", "1.8"), libraryDependencies ++= Seq( "org.scala-lang" % "scala-reflect" % scalaVersion.value, "org.scalatest" %% "scalatest" % "3.2.0" % "test", @@ -142,22 +129,11 @@ lazy val publishSettings = Seq( ) -def scalacDocOptionsVersion(scalaVersion: String): Seq[String] = { - Seq() ++ { - // If we're building with Scala > 2.11, enable the compile option - // to flag warnings as errors. This must be disabled for 2.11 since - // references to the Java class library from Java 9 on generate warnings. - // https://github.com/scala/bug/issues/10675 - CrossVersion.partialVersion(scalaVersion) match { - case Some((2, scalaMajor: Long)) if scalaMajor < 12 => Seq() - case _ => Seq("-Xfatal-warnings") - } - } -} lazy val docSettings = Seq( doc in Compile := (doc in ScalaUnidoc).value, autoAPIMappings := true, scalacOptions in Compile in doc ++= Seq( + "-Xfatal-warnings", "-feature", "-diagrams", "-diagrams-max-classes", "25", @@ -175,7 +151,7 @@ lazy val docSettings = Seq( } s"https://github.com/chipsalliance/firrtl/tree/$branch€{FILE_PATH_EXT}#L€{FILE_LINE}" } - ) ++ scalacDocOptionsVersion(scalaVersion.value) + ) ) lazy val firrtl = (project in file(".")) diff --git a/build.sc b/build.sc index ebef2b3d09..89796964c9 100644 --- a/build.sc +++ b/build.sc @@ -7,7 +7,7 @@ import mill.modules.Util import $ivy.`com.lihaoyi::mill-contrib-buildinfo:$MILL_VERSION` import mill.contrib.buildinfo.BuildInfo -object firrtl extends mill.Cross[firrtlCrossModule]("2.11.12", "2.12.12", "2.13.2") +object firrtl extends mill.Cross[firrtlCrossModule]("2.12.12", "2.13.2") class firrtlCrossModule(val crossScalaVersion: String) extends CrossSbtModule with PublishModule with BuildInfo { override def millSourcePath = super.millSourcePath / os.up @@ -21,10 +21,7 @@ class firrtlCrossModule(val crossScalaVersion: String) extends CrossSbtModule wi Some("firrtl.stage.FirrtlMain") } - private def javacCrossOptions = majorVersion match { - case i if i < 12 => Seq("-source", "1.7", "-target", "1.7") - case _ => Seq("-source", "1.8", "-target", "1.8") - } + private def javacCrossOptions = Seq("-source", "1.8", "-target", "1.8") override def scalacOptions = T { super.scalacOptions() ++ Seq( @@ -56,16 +53,11 @@ class firrtlCrossModule(val crossScalaVersion: String) extends CrossSbtModule wi } object test extends Tests { - private def ivyCrossDeps = majorVersion match { - case i if i < 12 => Agg(ivy"junit:junit:4.13.1") - case _ => Agg() - } - override def ivyDeps = T { Agg( ivy"org.scalatest::scalatest:3.2.0", ivy"org.scalatestplus::scalacheck-1-14:3.1.3.0" - ) ++ ivyCrossDeps + ) } def testFrameworks = T { diff --git a/src/main/scala/firrtl/stage/FirrtlAnnotations.scala b/src/main/scala/firrtl/stage/FirrtlAnnotations.scala index 7d0d237a9f..26655efd1c 100644 --- a/src/main/scala/firrtl/stage/FirrtlAnnotations.scala +++ b/src/main/scala/firrtl/stage/FirrtlAnnotations.scala @@ -255,13 +255,18 @@ case class FirrtlCircuitAnnotation(circuit: Circuit) extends NoTargetAnnotation * * - set with `--warn:no-scala-version-deprecation` */ +@deprecated("Support for Scala 2.11 has been dropped, this object no longer does anything", "FIRRTL 1.5") case object WarnNoScalaVersionDeprecation extends NoTargetAnnotation with FirrtlOption with HasShellOptions { def longOption: String = "warn:no-scala-version-deprecation" val options = Seq( new ShellOption[Unit]( longOption = longOption, - toAnnotationSeq = { _ => Seq(this) }, - helpText = "Suppress Scala 2.11 deprecation warning (ignored in Scala 2.12+)" + toAnnotationSeq = { _ => + val msg = s"'$longOption' no longer does anything and will be removed in FIRRTL 1.6" + firrtl.options.StageUtils.dramaticWarning(msg) + Seq(this) + }, + helpText = "(deprecated, this option does nothing)" ) ) } diff --git a/src/main/scala/firrtl/stage/transforms/CheckScalaVersion.scala b/src/main/scala/firrtl/stage/transforms/CheckScalaVersion.scala index 9d894905f3..6ed900f1f2 100644 --- a/src/main/scala/firrtl/stage/transforms/CheckScalaVersion.scala +++ b/src/main/scala/firrtl/stage/transforms/CheckScalaVersion.scala @@ -6,14 +6,10 @@ import firrtl.{BuildInfo, CircuitState, DependencyAPIMigration, Transform} import firrtl.stage.WarnNoScalaVersionDeprecation import firrtl.options.StageUtils.dramaticWarning +@deprecated("Support for 2.11 has been dropped, this logic no longer does anything", "FIRRTL 1.5") object CheckScalaVersion { def migrationDocumentLink: String = "https://www.chisel-lang.org/chisel3/upgrading-from-scala-2-11.html" - private def getScalaMajorVersion: Int = { - val "2" :: major :: _ :: Nil = BuildInfo.scalaVersion.split("\\.").toList - major.toInt - } - final def deprecationMessage(version: String, option: String) = s"""|FIRRTL support for Scala $version is deprecated, please upgrade to Scala 2.12. | Migration guide: $migrationDocumentLink @@ -21,17 +17,10 @@ object CheckScalaVersion { } +@deprecated("Support for 2.11 has been dropped, this transform no longer does anything", "FIRRTL 1.5") class CheckScalaVersion extends Transform with DependencyAPIMigration { - import CheckScalaVersion._ override def invalidates(a: Transform) = false - def execute(state: CircuitState): CircuitState = { - def suppress = state.annotations.contains(WarnNoScalaVersionDeprecation) - if (getScalaMajorVersion == 11 && !suppress) { - val option = s"--${WarnNoScalaVersionDeprecation.longOption}" - dramaticWarning(deprecationMessage("2.11", option)) - } - state - } + def execute(state: CircuitState): CircuitState = state } diff --git a/src/test/scala/firrtl/backends/experimental/smt/end2end/SMTCompilationTest.scala b/src/test/scala/firrtl/backends/experimental/smt/end2end/SMTCompilationTest.scala index 729498301a..f846e19cc0 100644 --- a/src/test/scala/firrtl/backends/experimental/smt/end2end/SMTCompilationTest.scala +++ b/src/test/scala/firrtl/backends/experimental/smt/end2end/SMTCompilationTest.scala @@ -15,8 +15,7 @@ import scala.sys.process.{Process, ProcessLogger} class SMTCompilationTest extends AnyFlatSpec with LazyLogging { it should "generate valid SMTLib for AddNot" taggedAs (RequiresZ3) in { compileAndParse("AddNot") } it should "generate valid SMTLib for FPU" taggedAs (RequiresZ3) in { compileAndParse("FPU") } - // we get a stack overflow in Scala 2.11 because of a deeply nested and(...) expression in the sequencer - it should "generate valid SMTLib for HwachaSequencer" taggedAs (RequiresZ3) ignore { + it should "generate valid SMTLib for HwachaSequencer" taggedAs (RequiresZ3) in { compileAndParse("HwachaSequencer") } it should "generate valid SMTLib for ICache" taggedAs (RequiresZ3) in { compileAndParse("ICache") } diff --git a/src/test/scala/firrtlTests/stage/FirrtlMainSpec.scala b/src/test/scala/firrtlTests/stage/FirrtlMainSpec.scala index ea590d26fd..eaf48b4990 100644 --- a/src/test/scala/firrtlTests/stage/FirrtlMainSpec.scala +++ b/src/test/scala/firrtlTests/stage/FirrtlMainSpec.scala @@ -164,16 +164,6 @@ class FirrtlMainSpec |""".stripMargin } - /** This returns a string containing the default standard out string based on the Scala version. E.g., if there are - * version-specific deprecation warnings, those are available here and can be passed to tests that should have them. - */ - val defaultStdOut: Option[String] = BuildInfo.scalaVersion.split("\\.").toList match { - case "2" :: v :: _ :: Nil if v.toInt <= 11 => - Some(CheckScalaVersion.deprecationMessage("2.11", s"--${WarnNoScalaVersionDeprecation.longOption}")) - case x => - None - } - info("As a FIRRTL command line user") info("I want to compile some FIRRTL") Feature("FirrtlMain command line interface") { @@ -205,58 +195,48 @@ class FirrtlMainSpec Seq( /* Test all standard emitters with and without annotation file outputs */ FirrtlMainTest(args = Array("-X", "none", "-E", "chirrtl"), files = Seq("Top.fir")), - FirrtlMainTest(args = Array("-X", "high", "-E", "high"), stdout = defaultStdOut, files = Seq("Top.hi.fir")), + FirrtlMainTest(args = Array("-X", "high", "-E", "high"), files = Seq("Top.hi.fir")), FirrtlMainTest( args = Array("-X", "middle", "-E", "middle", "-foaf", "Top"), - stdout = defaultStdOut, files = Seq("Top.mid.fir", "Top.anno.json") ), FirrtlMainTest( args = Array("-X", "low", "-E", "low", "-foaf", "annotations.anno.json"), - stdout = defaultStdOut, files = Seq("Top.lo.fir", "annotations.anno.json") ), FirrtlMainTest( args = Array("-X", "verilog", "-E", "verilog", "-foaf", "foo.anno"), - stdout = defaultStdOut, files = Seq("Top.v", "foo.anno.anno.json") ), FirrtlMainTest( args = Array("-X", "sverilog", "-E", "sverilog", "-foaf", "foo.json"), - stdout = defaultStdOut, files = Seq("Top.sv", "foo.json.anno.json") ), /* Test all one file per module emitters */ FirrtlMainTest(args = Array("-X", "none", "-e", "chirrtl"), files = Seq("Top.fir", "Child.fir")), FirrtlMainTest( args = Array("-X", "high", "-e", "high"), - stdout = defaultStdOut, files = Seq("Top.hi.fir", "Child.hi.fir") ), FirrtlMainTest( args = Array("-X", "middle", "-e", "middle"), - stdout = defaultStdOut, files = Seq("Top.mid.fir", "Child.mid.fir") ), FirrtlMainTest( args = Array("-X", "low", "-e", "low"), - stdout = defaultStdOut, files = Seq("Top.lo.fir", "Child.lo.fir") ), FirrtlMainTest( args = Array("-X", "verilog", "-e", "verilog"), - stdout = defaultStdOut, files = Seq("Top.v", "Child.v") ), FirrtlMainTest( args = Array("-X", "sverilog", "-e", "sverilog"), - stdout = defaultStdOut, files = Seq("Top.sv", "Child.sv") ), /* Test mixing of -E with -e */ FirrtlMainTest( args = Array("-X", "middle", "-E", "high", "-e", "middle"), - stdout = defaultStdOut, files = Seq("Top.hi.fir", "Top.mid.fir", "Child.mid.fir"), notFiles = Seq("Child.hi.fir") ), @@ -264,33 +244,27 @@ class FirrtlMainSpec FirrtlMainTest(args = Array("-X", "none", "-E", "chirrtl", "-o", "foo"), files = Seq("foo.fir")), FirrtlMainTest( args = Array("-X", "high", "-E", "high", "-o", "foo"), - stdout = defaultStdOut, files = Seq("foo.hi.fir") ), FirrtlMainTest( args = Array("-X", "middle", "-E", "middle", "-o", "foo.middle"), - stdout = defaultStdOut, files = Seq("foo.middle.mid.fir") ), FirrtlMainTest( args = Array("-X", "low", "-E", "low", "-o", "foo.lo.fir"), - stdout = defaultStdOut, files = Seq("foo.lo.fir") ), FirrtlMainTest( args = Array("-X", "verilog", "-E", "verilog", "-o", "foo.sv"), - stdout = defaultStdOut, files = Seq("foo.sv.v") ), FirrtlMainTest( args = Array("-X", "sverilog", "-E", "sverilog", "-o", "Foo"), - stdout = defaultStdOut, files = Seq("Foo.sv") ), /* Test that an output is generated if no emitter is specified */ FirrtlMainTest( args = Array("-X", "verilog", "-o", "Foo"), - stdout = defaultStdOut, files = Seq("Foo.v") ) ) From 5be1abb4c654279762a463a861526ce4e0c48035 Mon Sep 17 00:00:00 2001 From: Deborah Soung Date: Wed, 3 Mar 2021 16:45:49 -0800 Subject: [PATCH 32/88] Fix ProtoBuf conversions for Verification IR (#2100) --- src/main/proto/firrtl.proto | 1 + src/main/scala/firrtl/proto/FromProto.scala | 7 ++++--- src/main/scala/firrtl/proto/ToProto.scala | 1 + src/test/scala/firrtlTests/ProtoBufSpec.scala | 13 +++++++++++++ 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/main/proto/firrtl.proto b/src/main/proto/firrtl.proto index e8451d7a25..6ce1c10822 100644 --- a/src/main/proto/firrtl.proto +++ b/src/main/proto/firrtl.proto @@ -276,6 +276,7 @@ message Firrtl { IsInvalid is_invalid = 17; MemoryPort memory_port = 18; Attach attach = 20; + Verification verification = 21; } SourceInfo source_info = 19; diff --git a/src/main/scala/firrtl/proto/FromProto.scala b/src/main/scala/firrtl/proto/FromProto.scala index cb9b705e2f..ed641eb2ae 100644 --- a/src/main/scala/firrtl/proto/FromProto.scala +++ b/src/main/scala/firrtl/proto/FromProto.scala @@ -258,9 +258,10 @@ object FromProto { case MEMORY_FIELD_NUMBER => convert(stmt.getMemory, info) case IS_INVALID_FIELD_NUMBER => ir.IsInvalid(convert(info), convert(stmt.getIsInvalid.getExpression)) - case CMEMORY_FIELD_NUMBER => convert(stmt.getCmemory, info) - case MEMORY_PORT_FIELD_NUMBER => convert(stmt.getMemoryPort, info) - case ATTACH_FIELD_NUMBER => convert(stmt.getAttach, info) + case CMEMORY_FIELD_NUMBER => convert(stmt.getCmemory, info) + case MEMORY_PORT_FIELD_NUMBER => convert(stmt.getMemoryPort, info) + case ATTACH_FIELD_NUMBER => convert(stmt.getAttach, info) + case VERIFICATION_FIELD_NUMBER => convert(stmt.getVerification, info) } } diff --git a/src/main/scala/firrtl/proto/ToProto.scala b/src/main/scala/firrtl/proto/ToProto.scala index 4cdf6b85ce..f5ade0e3e6 100644 --- a/src/main/scala/firrtl/proto/ToProto.scala +++ b/src/main/scala/firrtl/proto/ToProto.scala @@ -307,6 +307,7 @@ object ToProto { .setCond(convert(cond)) .setEn(convert(en)) .setMsg(msg.string) + sb.setVerification(vb) case ir.IsInvalid(_, expr) => val ib = Firrtl.Statement.IsInvalid .newBuilder() diff --git a/src/test/scala/firrtlTests/ProtoBufSpec.scala b/src/test/scala/firrtlTests/ProtoBufSpec.scala index d56ef7b1c1..e590994492 100644 --- a/src/test/scala/firrtlTests/ProtoBufSpec.scala +++ b/src/test/scala/firrtlTests/ProtoBufSpec.scala @@ -218,6 +218,19 @@ class ProtoBufSpec extends FirrtlFlatSpec { FromProto.convert(ToProto.convert(vi).build) should equal(expected) } + it should "support Verification" in { + val clk = ir.Reference("clk", UnknownType) + val pred = ir.Reference("pred", UnknownType) + val en = ir.Reference("en", UnknownType) + val assert = ir.Verification(ir.Formal.Assert, ir.NoInfo, clk, pred, en, ir.StringLit("my assert message")) + val assume = ir.Verification(ir.Formal.Assume, ir.NoInfo, clk, pred, en, ir.StringLit("my assume message")) + val cover = ir.Verification(ir.Formal.Cover, ir.NoInfo, clk, pred, en, ir.StringLit("my cover message")) + + FromProto.convert(ToProto.convert(assert).head.build) should equal(assert) + FromProto.convert(ToProto.convert(assume).head.build) should equal(assume) + FromProto.convert(ToProto.convert(cover).head.build) should equal(cover) + } + it should "appropriately escape and unescape FileInfo strings" in { val pairs = Seq( "test\\ntest" -> "test\ntest", From e58ba0c12e5d650983c70a61a45542f0cd43fb88 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Wed, 3 Mar 2021 17:01:53 -0800 Subject: [PATCH 33/88] CSE SubAccesses (#2099) Fixes n^2 performance problem when dynamically indexing Vecs of aggregate types. Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- src/main/scala/firrtl/Utils.scala | 32 +++ .../scala/firrtl/passes/RemoveAccesses.scala | 4 +- src/main/scala/firrtl/stage/Forms.scala | 1 + .../firrtl/transforms/CSESubAccesses.scala | 164 +++++++++++++++ .../scala/firrtlTests/LowerTypesSpec.scala | 3 +- .../firrtlTests/LoweringCompilersSpec.scala | 1 + .../transforms/CSESubAccessesSpec.scala | 187 ++++++++++++++++++ 7 files changed, 390 insertions(+), 2 deletions(-) create mode 100644 src/main/scala/firrtl/transforms/CSESubAccesses.scala create mode 100644 src/test/scala/firrtlTests/transforms/CSESubAccessesSpec.scala diff --git a/src/main/scala/firrtl/Utils.scala b/src/main/scala/firrtl/Utils.scala index d187ea5ff9..72884e2591 100644 --- a/src/main/scala/firrtl/Utils.scala +++ b/src/main/scala/firrtl/Utils.scala @@ -5,6 +5,7 @@ package firrtl import firrtl.ir._ import firrtl.PrimOps._ import firrtl.Mappers._ +import firrtl.traversals.Foreachers._ import firrtl.WrappedExpression._ import scala.collection.mutable @@ -210,6 +211,24 @@ object Utils extends LazyLogging { case _ => false } + /** Selects all the elements of this list ignoring the duplicates as determined by == after + * applying the transforming function f + * + * @note In Scala Standard Library starting in 2.13 + */ + def distinctBy[A, B](xs: List[A])(f: A => B): List[A] = { + val buf = new mutable.ListBuffer[A] + val seen = new mutable.HashSet[B] + for (x <- xs) { + val y = f(x) + if (!seen(y)) { + buf += x + seen += y + } + } + buf.toList + } + /** Provide a nice name to create a temporary * */ def niceName(e: Expression): String = niceName(1)(e) def niceName(depth: Int)(e: Expression): String = { @@ -649,6 +668,19 @@ object Utils extends LazyLogging { case _ => NoInfo } + /** Finds all root References in a nested Expression */ + def getAllRefs(expr: Expression): Seq[Reference] = { + val refs = mutable.ListBuffer.empty[Reference] + def rec(e: Expression): Unit = { + e match { + case ref: Reference => refs += ref + case other => other.foreach(rec) + } + } + rec(expr) + refs.toList + } + /** Splits an Expression into root Ref and tail * * @example diff --git a/src/main/scala/firrtl/passes/RemoveAccesses.scala b/src/main/scala/firrtl/passes/RemoveAccesses.scala index 90437e56f4..7449db5167 100644 --- a/src/main/scala/firrtl/passes/RemoveAccesses.scala +++ b/src/main/scala/firrtl/passes/RemoveAccesses.scala @@ -9,6 +9,7 @@ import firrtl.Mappers._ import firrtl.Utils._ import firrtl.WrappedExpression._ import firrtl.options.Dependency +import firrtl.transforms.CSESubAccesses import scala.collection.mutable @@ -21,7 +22,8 @@ object RemoveAccesses extends Pass { Dependency(PullMuxes), Dependency(ZeroLengthVecs), Dependency(ReplaceAccesses), - Dependency(ExpandConnects) + Dependency(ExpandConnects), + Dependency[CSESubAccesses] ) ++ firrtl.stage.Forms.Deduped override def invalidates(a: Transform): Boolean = a match { diff --git a/src/main/scala/firrtl/stage/Forms.scala b/src/main/scala/firrtl/stage/Forms.scala index ab08215102..ba27b55238 100644 --- a/src/main/scala/firrtl/stage/Forms.scala +++ b/src/main/scala/firrtl/stage/Forms.scala @@ -57,6 +57,7 @@ object Forms { val MidForm: Seq[TransformDependency] = HighForm ++ Seq( Dependency(passes.PullMuxes), + Dependency[firrtl.transforms.CSESubAccesses], Dependency(passes.ReplaceAccesses), Dependency(passes.ExpandConnects), Dependency(passes.RemoveAccesses), diff --git a/src/main/scala/firrtl/transforms/CSESubAccesses.scala b/src/main/scala/firrtl/transforms/CSESubAccesses.scala new file mode 100644 index 0000000000..6ed3a5b5cf --- /dev/null +++ b/src/main/scala/firrtl/transforms/CSESubAccesses.scala @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: Apache-2.0 + +package firrtl +package transforms + +import firrtl.ir._ +import firrtl.traversals.Foreachers._ +import firrtl.Mappers._ +import firrtl.PrimOps._ +import firrtl.WrappedExpression._ +import firrtl.options.Dependency +import firrtl.passes._ +import firrtl.Utils.{distinctBy, flow, getAllRefs, get_info, niceName} + +import scala.collection.mutable + +object CSESubAccesses { + + // Get all SubAccesses used on the right-hand side along with the info from the outer Statement + private def collectRValueSubAccesses(mod: Module): Seq[(SubAccess, Info)] = { + val acc = new mutable.ListBuffer[(SubAccess, Info)] + def onExpr(outer: Statement)(expr: Expression): Unit = { + // Need postorder because we want to visit inner SubAccesses first + expr.foreach(onExpr(outer)) + expr match { + case e: SubAccess if flow(e) == SourceFlow => acc += e -> get_info(outer) + case _ => // Do nothing + } + } + def onStmt(stmt: Statement): Unit = { + stmt.foreach(onStmt) + stmt match { + // Don't record SubAccesses that are already assigned to a Node, but *do* record any nested + // inside of the SubAccess. This makes the transform idempotent and avoids unnecessary work. + case DefNode(_, _, acc: SubAccess) => acc.foreach(onExpr(stmt)) + case other => other.foreach(onExpr(stmt)) + } + } + onStmt(mod.body) + distinctBy(acc.toList)(_._1) + } + + // Replaces all right-hand side SubAccesses with References + private def replaceOnSourceExpr(replace: SubAccess => Reference)(expr: Expression): Expression = expr match { + // Don't traverse children of SubAccess, just replace it + // Nested SubAccesses are handled during creation of the nodes that the references refer to + case acc: SubAccess if flow(acc) == SourceFlow => replace(acc) + case other => other.map(replaceOnSourceExpr(replace)) + } + + private def hoistSubAccesses( + hoist: String => List[DefNode], + replace: SubAccess => Reference + )(stmt: Statement + ): Statement = { + val onExpr = replaceOnSourceExpr(replace) _ + def onStmt(s: Statement): Statement = s.map(onExpr).map(onStmt) match { + case decl: IsDeclaration => + val nodes = hoist(decl.name) + if (nodes.isEmpty) decl else Block(decl :: nodes) + case other => other + } + onStmt(stmt) + } + + // Given some nodes, determine after which String declaration each node should be inserted + // This function is *mutable*, it keeps track of which declarations each node is sensitive to and + // returns nodes in groups once the last declaration they depend on is seen + private def getSensitivityLookup(nodes: Iterable[DefNode]): String => List[DefNode] = { + case class ReferenceCount(var n: Int, node: DefNode) + // Gather names of declarations each node depends on + val nodeDeps = nodes.map(node => getAllRefs(node.value).view.map(_.name).toSet -> node) + // Map from declaration names to the indices of nodeDeps that depend on it + val lookup = new mutable.HashMap[String, mutable.ArrayBuffer[Int]] + for (((decls, _), idx) <- nodeDeps.zipWithIndex) { + for (d <- decls) { + val indices = lookup.getOrElseUpdate(d, new mutable.ArrayBuffer[Int]) + indices += idx + } + } + // Now we can just associate each List of nodes with how many declarations they need to see + // We use an Array because we're mutating anyway and might as well be quick about it + val nodeLists: Array[ReferenceCount] = + nodeDeps.view.map { case (deps, node) => ReferenceCount(deps.size, node) }.toArray + + // Must be a def because it's recursive + def func(decl: String): List[DefNode] = { + if (lookup.contains(decl)) { + val indices = lookup(decl) + val result = new mutable.ListBuffer[DefNode] + lookup -= decl + for (i <- indices) { + val refCount = nodeLists(i) + refCount.n -= 1 + assert(refCount.n >= 0, "Internal Error!") + if (refCount.n == 0) result += refCount.node + } + // DefNodes can depend on each other, recurse + result.toList.flatMap { node => node :: func(node.name) } + } else { + Nil + } + } + func _ + } + + /** Performs [[CSESubAccesses]] on a single [[ir.Module Module]] */ + def onMod(mod: Module): Module = { + // ***** Pre-Analyze (do we even need to do anything) ***** + val accesses = collectRValueSubAccesses(mod) + if (accesses.isEmpty) mod + else { + // ***** Analyze ***** + val namespace = Namespace(mod) + val replace = new mutable.HashMap[SubAccess, Reference] + val nodes = new mutable.ArrayBuffer[DefNode] + for ((acc, info) <- accesses) { + val name = namespace.newName(niceName(acc)) + // SubAccesses can be nested, so replace any nested ones with prior references + // This is why post-order traversal in collectRValueSubAccesses is important + val accx = acc.map(replaceOnSourceExpr(replace)) + val node = DefNode(info, name, accx) + val ref = Reference(node) + // Record in replace + replace(acc) = ref + // Record node + nodes += node + } + val hoist = getSensitivityLookup(nodes) + + // ***** Transform ***** + val portStmts = mod.ports.flatMap(x => hoist(x.name)) + val bodyx = hoistSubAccesses(hoist, replace)(mod.body) + mod.copy(body = if (portStmts.isEmpty) bodyx else Block(Block(portStmts), bodyx)) + } + } +} + +/** Performs Common Subexpression Elimination (CSE) on right-hand side [[ir.SubAccess SubAccess]]es + * + * This avoids quadratic node creation behavior in [[passes.RemoveAccesses RemoveAccesses]]. For + * simplicity of implementation, all SubAccesses on the right-hand side are also split into + * individual nodes. + */ +class CSESubAccesses extends Transform with DependencyAPIMigration { + + override def prerequisites = Dependency(ResolveFlows) :: Dependency(CheckHighForm) :: Nil + + // Faster to run after these + override def optionalPrerequisites = Dependency(ReplaceAccesses) :: Dependency[DedupModules] :: Nil + + // Running before ExpandConnects is an optimization + override def optionalPrerequisiteOf = Dependency(ExpandConnects) :: Nil + + override def invalidates(a: Transform) = false + + def execute(state: CircuitState): CircuitState = { + val modulesx = state.circuit.modules.map { + case ext: ExtModule => ext + case mod: Module => CSESubAccesses.onMod(mod) + } + state.copy(circuit = state.circuit.copy(modules = modulesx)) + } +} diff --git a/src/test/scala/firrtlTests/LowerTypesSpec.scala b/src/test/scala/firrtlTests/LowerTypesSpec.scala index 78d03e68ac..9425a58222 100644 --- a/src/test/scala/firrtlTests/LowerTypesSpec.scala +++ b/src/test/scala/firrtlTests/LowerTypesSpec.scala @@ -486,7 +486,8 @@ class LowerTypesUniquifySpec extends FirrtlFlatSpec { | out <= in0[in1[in2[0]]][in1[in2[1]]] |""".stripMargin val expected = Seq( - "out <= _in0_in1_in1_in2_1" + "node _in0_in1_in1 = _in0_in1_in1_in2_1", + "out <= _in0_in1_in1" ) executeTest(input, expected) diff --git a/src/test/scala/firrtlTests/LoweringCompilersSpec.scala b/src/test/scala/firrtlTests/LoweringCompilersSpec.scala index bdc72e7b80..ee6077d322 100644 --- a/src/test/scala/firrtlTests/LoweringCompilersSpec.scala +++ b/src/test/scala/firrtlTests/LoweringCompilersSpec.scala @@ -180,6 +180,7 @@ class LoweringCompilersSpec extends AnyFlatSpec with Matchers { it should "replicate the old order" in { val tm = new TransformManager(Forms.MidForm, Forms.Deduped) val patches = Seq( + Add(2, Seq(Dependency[firrtl.transforms.CSESubAccesses])), Add(4, Seq(Dependency(firrtl.passes.ResolveFlows))), Add(5, Seq(Dependency(firrtl.passes.ResolveKinds))), // Uniquify is now part of [[firrtl.passes.LowerTypes]] diff --git a/src/test/scala/firrtlTests/transforms/CSESubAccessesSpec.scala b/src/test/scala/firrtlTests/transforms/CSESubAccessesSpec.scala new file mode 100644 index 0000000000..55ce07dfc5 --- /dev/null +++ b/src/test/scala/firrtlTests/transforms/CSESubAccessesSpec.scala @@ -0,0 +1,187 @@ +// SPDX-License-Identifier: Apache-2.0 + +package firrtlTests +package transforms + +import firrtl._ +import firrtl.testutils._ +import firrtl.stage.TransformManager +import firrtl.options.Dependency +import firrtl.transforms.CSESubAccesses + +class CSESubAccessesSpec extends FirrtlFlatSpec { + def compile(input: String): String = { + val manager = new TransformManager(Dependency[CSESubAccesses] :: Nil) + val result = manager.execute(CircuitState(parse(input), Nil)) + result.circuit.serialize + } + def circuit(body: String): String = { + """|circuit Test : + | module Test : + |""".stripMargin + body.stripMargin.split("\n").mkString(" ", "\n ", "\n") + } + + behavior.of("CSESubAccesses") + + it should "hoist a single RHS subaccess" in { + val input = circuit( + s"""|input in : UInt<8>[4] + |input idx : UInt<2> + |output out : UInt<8> + |out <= in[idx]""" + ) + val expected = circuit( + s"""|input in : UInt<8>[4] + |input idx : UInt<2> + |output out : UInt<8> + |node _in_idx = in[idx] + |out <= _in_idx""" + ) + compile(input) should be(parse(expected).serialize) + } + + it should "be idempotent" in { + val input = circuit( + s"""|input in : UInt<8>[4] + |input idx : UInt<2> + |output out : UInt<8> + |out <= in[idx]""" + ) + val expected = circuit( + s"""|input in : UInt<8>[4] + |input idx : UInt<2> + |output out : UInt<8> + |node _in_idx = in[idx] + |out <= _in_idx""" + ) + val first = compile(input) + val second = compile(first) + first should be(second) + first should be(parse(expected).serialize) + } + + it should "hoist a redundant RHS subaccess" in { + val input = circuit( + s"""|input in : { foo : UInt<8>, bar : UInt<8> }[4] + |input idx : UInt<2> + |output out : { foo : UInt<8>, bar : UInt<8> } + |out.foo <= in[idx].foo + |out.bar <= in[idx].bar""" + ) + val expected = circuit( + s"""|input in : { foo : UInt<8>, bar : UInt<8> }[4] + |input idx : UInt<2> + |output out : { foo : UInt<8>, bar : UInt<8> } + |node _in_idx = in[idx] + |out.foo <= _in_idx.foo + |out.bar <= _in_idx.bar""" + ) + compile(input) should be(parse(expected).serialize) + } + + it should "correctly place hosited subaccess after last declaration it depends on" in { + val input = circuit( + s"""|input in : UInt<8>[4] + |input idx : UInt<2> + |output out : UInt<8> + |out is invalid + |when UInt(1) : + | node nidx = not(idx) + | out <= in[nidx] + |""" + ) + val expected = circuit( + s"""|input in : UInt<8>[4] + |input idx : UInt<2> + |output out : UInt<8> + |out is invalid + |when UInt(1) : + | node nidx = not(idx) + | node _in_nidx = in[nidx] + | out <= _in_nidx + |""" + ) + compile(input) should be(parse(expected).serialize) + } + + it should "support complex expressions" in { + val input = circuit( + s"""|input clock : Clock + |input in : UInt<8>[4] + |input idx : UInt<2> + |input sel : UInt<1> + |output out : UInt<8> + |reg r : UInt<2>, clock + |out <= in[mux(sel, r, idx)] + |r <= not(idx)""" + ) + val expected = circuit( + s"""|input clock : Clock + |input in : UInt<8>[4] + |input idx : UInt<2> + |input sel : UInt<1> + |output out : UInt<8> + |reg r : UInt<2>, clock + |node _in_mux = in[mux(sel, r, idx)] + |out <= _in_mux + |r <= not(idx)""" + ) + compile(input) should be(parse(expected).serialize) + } + + it should "support nested subaccesses" in { + val input = circuit( + s"""|input in : UInt<8>[4] + |input idx : UInt<2>[4] + |input jdx : UInt<2> + |output out : UInt<8> + |out <= in[idx[jdx]]""" + ) + val expected = circuit( + s"""|input in : UInt<8>[4] + |input idx : UInt<2>[4] + |input jdx : UInt<2> + |output out : UInt<8> + |node _idx_jdx = idx[jdx] + |node _in_idx = in[_idx_jdx] + |out <= _in_idx""" + ) + compile(input) should be(parse(expected).serialize) + } + + it should "avoid name collisions" in { + val input = circuit( + s"""|input in : UInt<8>[4] + |input idx : UInt<2> + |output out : UInt<8> + |out <= in[idx] + |node _in_idx = not(idx)""" + ) + val expected = circuit( + s"""|input in : UInt<8>[4] + |input idx : UInt<2> + |output out : UInt<8> + |node _in_idx_0 = in[idx] + |out <= _in_idx_0 + |node _in_idx = not(idx)""" + ) + compile(input) should be(parse(expected).serialize) + } + + it should "have no effect on LHS SubAccesses" in { + val input = circuit( + s"""|input in : UInt<8> + |input idx : UInt<2> + |output out : UInt<8>[4] + |out[idx] <= in""" + ) + val expected = circuit( + s"""|input in : UInt<8> + |input idx : UInt<2> + |output out : UInt<8>[4] + |out[idx] <= in""" + ) + compile(input) should be(parse(expected).serialize) + } + +} From c93d6f5319efd7ba42147180c6e2b6f3796ef943 Mon Sep 17 00:00:00 2001 From: Kevin Laeufer Date: Thu, 4 Mar 2021 11:23:51 -0800 Subject: [PATCH 34/88] SMT Backend: move undefined memory behavior modelling to firrtl IR level (#2095) With this PR the smt backend now supports memories with more than two write ports and the conservative memory modelling can be selectively turned off with a new annotation. --- src/main/scala/firrtl/Utils.scala | 73 +++ src/main/scala/firrtl/WIR.scala | 1 + .../smt/FirrtlToTransitionSystem.scala | 312 ++++-------- .../experimental/smt/random/DefRandom.scala | 31 ++ .../random/UndefinedMemoryBehaviorPass.scala | 457 ++++++++++++++++++ src/main/scala/firrtl/ir/IR.scala | 4 + src/main/scala/firrtl/ir/Serializer.scala | 7 + .../scala/firrtl/passes/ResolveKinds.scala | 2 + .../passes/memlib/VerilogMemDelays.scala | 3 +- .../transforms/DeadCodeElimination.scala | 7 + .../scala/firrtl/transforms/RemoveWires.scala | 20 +- .../random/UndefinedMemoryBehaviorSpec.scala | 360 ++++++++++++++ 12 files changed, 1060 insertions(+), 217 deletions(-) create mode 100644 src/main/scala/firrtl/backends/experimental/smt/random/DefRandom.scala create mode 100644 src/main/scala/firrtl/backends/experimental/smt/random/UndefinedMemoryBehaviorPass.scala create mode 100644 src/test/scala/firrtl/backends/experimental/smt/random/UndefinedMemoryBehaviorSpec.scala diff --git a/src/main/scala/firrtl/Utils.scala b/src/main/scala/firrtl/Utils.scala index 72884e2591..3d0f19b80b 100644 --- a/src/main/scala/firrtl/Utils.scala +++ b/src/main/scala/firrtl/Utils.scala @@ -900,6 +900,79 @@ object Utils extends LazyLogging { def maskBigInt(value: BigInt, width: Int): BigInt = { value & ((BigInt(1) << width) - 1) } + + /** Returns true iff the expression is a Literal or a Literal cast to a different type. */ + def isLiteral(e: Expression): Boolean = e match { + case _: Literal => true + case DoPrim(op, args, _, _) if isCast(op) => args.exists(isLiteral) + case _ => false + } + + /** Applies the firrtl And primop. Automatically constant propagates when one of the expressions is True or False. */ + def and(e1: Expression, e2: Expression): Expression = { + assert(e1.tpe == e2.tpe) + (e1, e2) match { + case (a: UIntLiteral, b: UIntLiteral) => UIntLiteral(a.value | b.value, a.width) + case (True(), b) => b + case (a, True()) => a + case (False(), _) => False() + case (_, False()) => False() + case (a, b) if a == b => a + case (a, b) => DoPrim(PrimOps.And, Seq(a, b), Nil, BoolType) + } + } + + /** Applies the firrtl Eq primop. */ + def eq(e1: Expression, e2: Expression): Expression = DoPrim(PrimOps.Eq, Seq(e1, e2), Nil, BoolType) + + /** Applies the firrtl Or primop. Automatically constant propagates when one of the expressions is True or False. */ + def or(e1: Expression, e2: Expression): Expression = { + assert(e1.tpe == e2.tpe) + (e1, e2) match { + case (a: UIntLiteral, b: UIntLiteral) => UIntLiteral(a.value | b.value, a.width) + case (True(), _) => True() + case (_, True()) => True() + case (False(), b) => b + case (a, False()) => a + case (a, b) if a == b => a + case (a, b) => DoPrim(PrimOps.Or, Seq(a, b), Nil, BoolType) + } + } + + /** Applies the firrtl Not primop. Automatically constant propagates when the expressions is True or False. */ + def not(e: Expression): Expression = e match { + case True() => False() + case False() => True() + case a => DoPrim(PrimOps.Not, Seq(a), Nil, BoolType) + } + + /** implies(e1, e2) = or(not(e1), e2). Automatically constant propagates when one of the expressions is True or False. */ + def implies(e1: Expression, e2: Expression): Expression = or(not(e1), e2) + + /** Builds a Mux expression with the correct type. */ + def mux(cond: Expression, tval: Expression, fval: Expression): Expression = { + require(tval.tpe == fval.tpe) + Mux(cond, tval, fval, tval.tpe) + } + + object True { + private val _True = UIntLiteral(1, IntWidth(1)) + + /** Matches `UInt<1>(1)` */ + def unapply(e: UIntLiteral): Boolean = e.value == 1 && e.width == _True.width + + /** Returns `UInt<1>(1)` */ + def apply(): UIntLiteral = _True + } + object False { + private val _False = UIntLiteral(0, IntWidth(1)) + + /** Matches `UInt<1>(0)` */ + def unapply(e: UIntLiteral): Boolean = e.value == 0 && e.width == _False.width + + /** Returns `UInt<1>(0)` */ + def apply(): UIntLiteral = _False + } } object MemoizedHash { diff --git a/src/main/scala/firrtl/WIR.scala b/src/main/scala/firrtl/WIR.scala index 78536a3632..e9dd95bc31 100644 --- a/src/main/scala/firrtl/WIR.scala +++ b/src/main/scala/firrtl/WIR.scala @@ -13,6 +13,7 @@ trait Kind case object WireKind extends Kind case object PoisonKind extends Kind case object RegKind extends Kind +case object RandomKind extends Kind case object InstanceKind extends Kind case object PortKind extends Kind case object NodeKind extends Kind diff --git a/src/main/scala/firrtl/backends/experimental/smt/FirrtlToTransitionSystem.scala b/src/main/scala/firrtl/backends/experimental/smt/FirrtlToTransitionSystem.scala index c185766733..d3a1ed6840 100644 --- a/src/main/scala/firrtl/backends/experimental/smt/FirrtlToTransitionSystem.scala +++ b/src/main/scala/firrtl/backends/experimental/smt/FirrtlToTransitionSystem.scala @@ -5,9 +5,12 @@ package firrtl.backends.experimental.smt import firrtl.annotations.{MemoryInitAnnotation, NoTargetAnnotation, PresetRegAnnotation} import FirrtlExpressionSemantics.getWidth +import firrtl.backends.experimental.smt.random._ import firrtl.graph.MutableDiGraph import firrtl.options.Dependency +import firrtl.passes.MemPortUtils.memPortField import firrtl.passes.PassException +import firrtl.passes.memlib.VerilogMemDelays import firrtl.stage.Forms import firrtl.stage.TransformManager.TransformDependency import firrtl.transforms.{DeadCodeElimination, PropagatePresetAnnotations} @@ -58,7 +61,8 @@ object FirrtlToTransitionSystem extends Transform with DependencyAPIMigration { // TODO: We only really need [[Forms.MidForm]] + LowerTypes, but we also want to fail if there are CombLoops // TODO: We also would like to run some optimization passes, but RemoveValidIf won't allow us to model DontCare // precisely and PadWidths emits ill-typed firrtl. - override def prerequisites: Seq[Dependency[Transform]] = Forms.LowForm + override def prerequisites: Seq[Dependency[Transform]] = Forms.LowForm ++ + Seq(Dependency(UndefinedMemoryBehaviorPass), Dependency(VerilogMemDelays)) override def invalidates(a: Transform): Boolean = false // since this pass only runs on the main module, inlining needs to happen before override def optionalPrerequisites: Seq[TransformDependency] = Seq(Dependency[firrtl.passes.InlineInstances]) @@ -169,8 +173,7 @@ private class ModuleToTransitionSystem extends LazyLogging { onRegister(name, width, resetExpr, initExpr, nextExpr, presetRegs) } // turn memories into state - val memoryEncoding = new MemoryEncoding(makeRandom, scan.namespace) - val memoryStatesAndOutputs = scan.memories.map(m => memoryEncoding.onMemory(m, scan.connects, memInit.get(m.name))) + val memoryStatesAndOutputs = scan.memories.map(m => onMemory(m, scan.connects, memInit.get(m.name))) // replace pseudo assigns for memory outputs val memOutputs = memoryStatesAndOutputs.flatMap(_._2).toMap val signalsWithMem = signals.map { s => @@ -185,7 +188,7 @@ private class ModuleToTransitionSystem extends LazyLogging { case _ => true } ) - val states = regStates.toArray ++ memoryStatesAndOutputs.flatMap(_._1) + val states = regStates.toArray ++ memoryStatesAndOutputs.map(_._1) // generate comments from infos val comments = mutable.HashMap[String, String]() @@ -247,233 +250,116 @@ private class ModuleToTransitionSystem extends LazyLogging { } } - private val InfoSeparator = ", " - private val InfoPrefix = "@ " - private def serializeInfo(info: ir.Info): Option[String] = info match { - case ir.NoInfo => None - case f: ir.FileInfo => Some(f.escaped) - case m: ir.MultiInfo => - val infos = m.flatten - if (infos.isEmpty) { None } - else { Some(infos.map(_.escaped).mkString(InfoSeparator)) } - } - - private[firrtl] val randoms = mutable.LinkedHashMap[String, BVSymbol]() - private def makeRandom(baseName: String, width: Int): BVExpr = { - // TODO: actually ensure that there cannot be any name clashes with other identifiers - val suffixes = Iterator(baseName) ++ (0 until 200).map(ii => baseName + "_" + ii) - val name = suffixes.map(s => "RANDOM." + s).find(!randoms.contains(_)).get - val sym = BVSymbol(name, width) - randoms(name) = sym - sym - } -} - -private class MemoryEncoding(makeRandom: (String, Int) => BVExpr, namespace: Namespace) extends LazyLogging { type Connects = Iterable[(String, BVExpr)] - def onMemory( - defMem: ir.DefMemory, - connects: Connects, - initValue: Option[MemoryInitValue] - ): (Iterable[State], Connects) = { - // we can only work on appropriately lowered memories - assert( - defMem.dataType.isInstanceOf[ir.GroundType], - s"Memory $defMem is of type ${defMem.dataType} which is not a ground type!" - ) - assert(defMem.readwriters.isEmpty, "Combined read/write ports are not supported! Please split them up.") + private def onMemory(m: ir.DefMemory, connects: Connects, initValue: Option[MemoryInitValue]): (State, Connects) = { + checkMem(m) - // collect all memory meta-data in a custom class - val m = new MemInfo(defMem) + // map of inputs to the memory + val inputs = connects.filter(_._1.startsWith(m.name)).toMap - // find all connections related to this memory - val inputs = connects.filter(_._1.startsWith(m.prefix)).toMap + // derive the type of the memory from the dataType and depth + val dataWidth = getWidth(m.dataType) + val indexWidth = Utils.getUIntWidth(m.depth - 1).max(1) + val memSymbol = ArraySymbol(m.name, indexWidth, dataWidth) // there could be a constant init - val init = initValue.map(getInit(m, _)) - - // parse and check read and write ports - val writers = defMem.writers.map(w => new WritePort(m, w, inputs)) - val readers = defMem.readers.map(r => new ReadPort(m, r, inputs)) - - // derive next state from all write ports - assert(defMem.writeLatency == 1, "Only memories with write-latency of one are supported.") - val next: ArrayExpr = if (writers.isEmpty) { m.sym } - else { - if (writers.length > 2) { - throw new UnsupportedFeatureException(s"memories with 3+ write ports (${m.name})") - } - val validData = writers.foldLeft[ArrayExpr](m.sym) { case (sym, w) => w.writeTo(sym) } - if (writers.length == 1) { validData } - else { - assert(writers.length == 2) - val conflict = writers.head.doesConflict(writers.last) - val conflictData = writers.head.makeRandomData("_write_write_collision") - val conflictStore = ArrayStore(m.sym, writers.head.addr, conflictData) - ArrayIte(conflict, conflictStore, validData) - } - } - val state = State(m.sym, init, Some(next)) + val init = initValue.map(getInit(m, indexWidth, dataWidth, _)) + init.foreach(e => assert(e.dataWidth == memSymbol.dataWidth && e.indexWidth == memSymbol.indexWidth)) - // derive data signals from all read ports - assert(defMem.readLatency >= 0) - if (defMem.readLatency > 1) { - throw new UnsupportedFeatureException(s"memories with read latency 2+ (${m.name})") - } - val readPortSignals = if (defMem.readLatency == 0) { - readers.map { r => - // combinatorial read - if (defMem.readUnderWrite != ir.ReadUnderWrite.New) { - logger.warn( - s"WARN: Memory ${m.name} with combinatorial read port will always return the most recently written entry." + - s" The read-under-write => ${defMem.readUnderWrite} setting will be ignored." - ) - } - // since we do a combinatorial read, the "old" data is the current data - val data = r.read() - r.data.name -> data - } - } else { Seq() } - val readPortSignalsAndStates = if (defMem.readLatency == 1) { - readers.map { r => - defMem.readUnderWrite match { - case ir.ReadUnderWrite.New => - // create a state to save the address and the enable signal - val enPrev = BVSymbol(namespace.newName(r.en.name + "_prev"), r.en.width) - val addrPrev = BVSymbol(namespace.newName(r.addr.name + "_prev"), r.addr.width) - val signal = r.data.name -> r.read(addr = addrPrev, en = enPrev) - val states = Seq(State(enPrev, None, next = Some(r.en)), State(addrPrev, None, next = Some(r.addr))) - (Seq(signal), states) - case ir.ReadUnderWrite.Undefined => - // check for potential read/write conflicts in which case we need to return an arbitrary value - val anyWriteToTheSameAddress = any(writers.map(_.doesConflict(r))) - val next = if (anyWriteToTheSameAddress == False) { r.read() } - else { - val readUnderWriteData = r.makeRandomData("_read_under_write_undefined") - BVIte(anyWriteToTheSameAddress, readUnderWriteData, r.read()) - } - (Seq(), Seq(State(r.data, init = None, next = Some(next)))) - case ir.ReadUnderWrite.Old => - // we create a register for the read port data - (Seq(), Seq(State(r.data, init = None, next = Some(r.read())))) - } + // derive next state expression + val next = if (m.writers.isEmpty) { + memSymbol + } else { + m.writers.foldLeft[ArrayExpr](memSymbol) { + case (prev, write) => + // update + val addr = BVSymbol(memPortField(m, write, "addr").serialize, indexWidth) + val data = BVSymbol(memPortField(m, write, "data").serialize, dataWidth) + val update = ArrayStore(prev, index = addr, data = data) + + // update guard + val en = BVSymbol(memPortField(m, write, "en").serialize, 1) + val mask = BVSymbol(memPortField(m, write, "mask").serialize, 1) + val alwaysEnabled = Seq(en, mask).forall(s => inputs(s.name) == True) + if (alwaysEnabled) { update } + else { + ArrayIte(and(en, mask), update, prev) + } } - } else { Seq() } + } - val allReadPortSignals = readPortSignals ++ readPortSignalsAndStates.flatMap(_._1) - val readPortStates = readPortSignalsAndStates.flatMap(_._2) + val state = State(memSymbol, init, Some(next)) - (state +: readPortStates, allReadPortSignals) - } + // derive read expressions + val readSignals = m.readers.map { read => + val addr = BVSymbol(memPortField(m, read, "addr").serialize, indexWidth) + memPortField(m, read, "data").serialize -> ArrayRead(memSymbol, addr) + } - private def getInit(m: MemInfo, initValue: MemoryInitValue): ArrayExpr = initValue match { - case MemoryScalarInit(value) => ArrayConstant(BVLiteral(value, m.dataWidth), m.indexWidth) - case MemoryArrayInit(values) => - assert( - values.length == m.depth, - s"Memory ${m.name} of depth ${m.depth} cannot be initialized with an array of length ${values.length}!" - ) - // in order to get a more compact encoding try to find the most common values - val histogram = mutable.LinkedHashMap[BigInt, Int]() - values.foreach(v => histogram(v) = 1 + histogram.getOrElse(v, 0)) - val baseValue = histogram.maxBy(_._2)._1 - val base = ArrayConstant(BVLiteral(baseValue, m.dataWidth), m.indexWidth) - values.zipWithIndex - .filterNot(_._1 == baseValue) - .foldLeft[ArrayExpr](base) { - case (array, (value, index)) => - ArrayStore(array, BVLiteral(index, m.indexWidth), BVLiteral(value, m.dataWidth)) - } - case other => throw new RuntimeException(s"Unsupported memory init option: $other") + (state, readSignals) } - private class MemInfo(m: ir.DefMemory) { - val name = m.name - val depth = m.depth - // derrive the type of the memory from the dataType and depth - val dataWidth = getWidth(m.dataType) - val indexWidth = Utils.getUIntWidth(m.depth - 1).max(1) - val sym = ArraySymbol(m.name, indexWidth, dataWidth) - val prefix = m.name + "." - val fullAddressRange = (BigInt(1) << indexWidth) == m.depth - lazy val depthBV = BVLiteral(m.depth, indexWidth) - def isValidAddress(addr: BVExpr): BVExpr = { - if (fullAddressRange) { True } - else { - BVComparison(Compare.Greater, depthBV, addr, signed = false) - } - } - } - private abstract class MemPort(memory: MemInfo, val name: String, inputs: String => BVExpr) { - val en: BVSymbol = makeField("en", 1) - val data: BVSymbol = makeField("data", memory.dataWidth) - val addr: BVSymbol = makeField("addr", memory.indexWidth) - protected def makeField(field: String, width: Int): BVSymbol = BVSymbol(memory.prefix + name + "." + field, width) - // make sure that all widths are correct - assert(inputs(en.name).width == en.width) - assert(inputs(addr.name).width == addr.width) - val enIsTrue: Boolean = inputs(en.name) == True - def makeRandomData(suffix: String): BVExpr = - makeRandom(memory.name + "_" + name + suffix, memory.dataWidth) - def read(addr: BVSymbol = addr, en: BVSymbol = en): BVExpr = { - val canBeOutOfRange = !memory.fullAddressRange - val canBeDisabled = !enIsTrue - val data = ArrayRead(memory.sym, addr) - val dataWithRangeCheck = if (canBeOutOfRange) { - val outOfRangeData = makeRandomData("_addr_out_of_range") - BVIte(memory.isValidAddress(addr), data, outOfRangeData) - } else { data } - val dataWithEnabledCheck = if (canBeDisabled) { - val disabledData = makeRandomData("_not_enabled") - BVIte(en, dataWithRangeCheck, disabledData) - } else { dataWithRangeCheck } - dataWithEnabledCheck - } - } - private class WritePort(memory: MemInfo, name: String, inputs: String => BVExpr) - extends MemPort(memory, name, inputs) { - assert(inputs(data.name).width == data.width) - val mask: BVSymbol = makeField("mask", 1) - assert(inputs(mask.name).width == mask.width) - val maskIsTrue: Boolean = inputs(mask.name) == True - val doWrite: BVExpr = (enIsTrue, maskIsTrue) match { - case (true, true) => True - case (true, false) => mask - case (false, true) => en - case (false, false) => and(en, mask) - } - def doesConflict(r: ReadPort): BVExpr = { - val sameAddress = BVEqual(r.addr, addr) - if (doWrite == True) { sameAddress } - else { and(doWrite, sameAddress) } - } - def doesConflict(w: WritePort): BVExpr = { - val bothWrite = and(doWrite, w.doWrite) - val sameAddress = BVEqual(addr, w.addr) - if (bothWrite == True) { sameAddress } - else { and(bothWrite, sameAddress) } - } - def writeTo(array: ArrayExpr): ArrayExpr = { - val doUpdate = if (memory.fullAddressRange) doWrite else and(doWrite, memory.isValidAddress(addr)) - val update = ArrayStore(array, index = addr, data = data) - if (doUpdate == True) update else ArrayIte(doUpdate, update, array) + private def getInit(m: ir.DefMemory, indexWidth: Int, dataWidth: Int, initValue: MemoryInitValue): ArrayExpr = + initValue match { + case MemoryScalarInit(value) => ArrayConstant(BVLiteral(value, dataWidth), indexWidth) + case MemoryArrayInit(values) => + assert( + values.length == m.depth, + s"Memory ${m.name} of depth ${m.depth} cannot be initialized with an array of length ${values.length}!" + ) + // in order to get a more compact encoding try to find the most common values + val histogram = mutable.LinkedHashMap[BigInt, Int]() + values.foreach(v => histogram(v) = 1 + histogram.getOrElse(v, 0)) + val baseValue = histogram.maxBy(_._2)._1 + val base = ArrayConstant(BVLiteral(baseValue, dataWidth), indexWidth) + values.zipWithIndex + .filterNot(_._1 == baseValue) + .foldLeft[ArrayExpr](base) { + case (array, (value, index)) => + ArrayStore(array, BVLiteral(index, indexWidth), BVLiteral(value, dataWidth)) + } + case other => throw new RuntimeException(s"Unsupported memory init option: $other") } - } - private class ReadPort(memory: MemInfo, name: String, inputs: String => BVExpr) - extends MemPort(memory, name, inputs) {} - + // TODO: add to BV expression library private def and(a: BVExpr, b: BVExpr): BVExpr = (a, b) match { case (True, True) => True case (True, x) => x case (x, True) => x case _ => BVOp(Op.And, a, b) } - private def or(a: BVExpr, b: BVExpr): BVExpr = BVOp(Op.Or, a, b) + private val True = BVLiteral(1, 1) - private val False = BVLiteral(0, 1) - private def all(b: Iterable[BVExpr]): BVExpr = if (b.isEmpty) False else b.reduce((a, b) => and(a, b)) - private def any(b: Iterable[BVExpr]): BVExpr = if (b.isEmpty) True else b.reduce((a, b) => or(a, b)) + private def checkMem(m: ir.DefMemory): Unit = { + assert(m.readLatency == 0, "Expected read latency to be 0. Did you run VerilogMemDelays?") + assert(m.writeLatency == 1, "Expected read latency to be 1. Did you run VerilogMemDelays?") + assert( + m.dataType.isInstanceOf[ir.GroundType], + s"Memory $m is of type ${m.dataType} which is not a ground type!" + ) + assert(m.readwriters.isEmpty, "Combined read/write ports are not supported! Please split them up.") + } + + private val InfoSeparator = ", " + private val InfoPrefix = "@ " + private def serializeInfo(info: ir.Info): Option[String] = info match { + case ir.NoInfo => None + case f: ir.FileInfo => Some(f.escaped) + case m: ir.MultiInfo => + val infos = m.flatten + if (infos.isEmpty) { None } + else { Some(infos.map(_.escaped).mkString(InfoSeparator)) } + } + + private[firrtl] val randoms = mutable.LinkedHashMap[String, BVSymbol]() + private def makeRandom(baseName: String, width: Int): BVExpr = { + // TODO: actually ensure that there cannot be any name clashes with other identifiers + val suffixes = Iterator(baseName) ++ (0 until 200).map(ii => baseName + "_" + ii) + val name = suffixes.map(s => "RANDOM." + s).find(!randoms.contains(_)).get + val sym = BVSymbol(name, width) + randoms(name) = sym + sym + } } // performas a first pass over the module collecting all connections, wires, registers, input and outputs @@ -526,6 +412,12 @@ private class ModuleScanner( } private[firrtl] def onStatement(s: ir.Statement): Unit = s match { + case DefRandom(info, name, tpe, _, _) => + namespace.newName(name) + assert(!isClock(tpe), "rand should never be a clock!") + // we model random sources as inputs and ignore the enable signal + infos.append(name -> info) + inputs.append(BVSymbol(name, getWidth(tpe))) case ir.DefWire(info, name, tpe) => namespace.newName(name) if (!isClock(tpe)) { diff --git a/src/main/scala/firrtl/backends/experimental/smt/random/DefRandom.scala b/src/main/scala/firrtl/backends/experimental/smt/random/DefRandom.scala new file mode 100644 index 0000000000..7381056e5a --- /dev/null +++ b/src/main/scala/firrtl/backends/experimental/smt/random/DefRandom.scala @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 + +package firrtl.backends.experimental.smt.random + +import firrtl.Utils +import firrtl.ir._ + +/** Named source of random values. If there is no clock expression, than it will be clocked by the global clock. */ +case class DefRandom( + info: Info, + name: String, + tpe: Type, + clock: Option[Expression], + en: Expression = Utils.True()) + extends Statement + with HasInfo + with IsDeclaration + with CanBeReferenced + with UseSerializer { + def mapStmt(f: Statement => Statement): Statement = this + def mapExpr(f: Expression => Expression): Statement = + DefRandom(info, name, tpe, clock.map(f), f(en)) + def mapType(f: Type => Type): Statement = this.copy(tpe = f(tpe)) + def mapString(f: String => String): Statement = this.copy(name = f(name)) + def mapInfo(f: Info => Info): Statement = this.copy(info = f(info)) + def foreachStmt(f: Statement => Unit): Unit = () + def foreachExpr(f: Expression => Unit): Unit = { clock.foreach(f); f(en) } + def foreachType(f: Type => Unit): Unit = f(tpe) + def foreachString(f: String => Unit): Unit = f(name) + def foreachInfo(f: Info => Unit): Unit = f(info) +} diff --git a/src/main/scala/firrtl/backends/experimental/smt/random/UndefinedMemoryBehaviorPass.scala b/src/main/scala/firrtl/backends/experimental/smt/random/UndefinedMemoryBehaviorPass.scala new file mode 100644 index 0000000000..91e77433cd --- /dev/null +++ b/src/main/scala/firrtl/backends/experimental/smt/random/UndefinedMemoryBehaviorPass.scala @@ -0,0 +1,457 @@ +// SPDX-License-Identifier: Apache-2.0 + +package firrtl.backends.experimental.smt.random + +import firrtl.Utils.{isLiteral, kind, BoolType} +import firrtl.WrappedExpression.{we, weq} +import firrtl._ +import firrtl.annotations.NoTargetAnnotation +import firrtl.backends.experimental.smt._ +import firrtl.ir._ +import firrtl.options.Dependency +import firrtl.passes.MemPortUtils.memPortField +import firrtl.passes.memlib.AnalysisUtils.Connects +import firrtl.passes.memlib.InferReadWritePass.checkComplement +import firrtl.passes.memlib.{AnalysisUtils, InferReadWritePass, VerilogMemDelays} +import firrtl.stage.Forms +import firrtl.transforms.{ConstantPropagation, RemoveWires} + +import scala.collection.mutable + +/** Chooses which undefined memory behaviors should be instrumented. */ +case class UndefinedMemoryBehaviorOptions( + randomizeWriteWriteConflicts: Boolean = true, + assertNoOutOfBoundsWrites: Boolean = false, + randomizeOutOfBoundsRead: Boolean = true, + randomizeDisabledReads: Boolean = true, + randomizeReadWriteConflicts: Boolean = true) + extends NoTargetAnnotation + +/** Adds sources of randomness to model the various "undefined behaviors" of firrtl memory. + * - Write/Write conflict: leads to arbitrary value written to write address + * - Out-of-bounds write: assertion failure (disabled by default) + * - Out-Of-bounds read: leads to arbitrary value being read + * - Read w/ en=0: leads to arbitrary value being read + * - Read/Write conflict: leads to arbitrary value being read + */ +object UndefinedMemoryBehaviorPass extends Transform with DependencyAPIMigration { + override def prerequisites = Forms.LowForm + override def optionalPrerequisiteOf = Seq(Dependency(VerilogMemDelays)) + override def invalidates(a: Transform) = a match { + // this pass might destroy SSA form, as we add a wire for the data field of every read port + case _: RemoveWires => true + // TODO: should we add some optimization passes here? we could be generating some dead code. + case _ => false + } + + override protected def execute(state: CircuitState): CircuitState = { + val opts = state.annotations.collect { case o: UndefinedMemoryBehaviorOptions => o } + require(opts.size < 2, s"Multiple options: $opts") + val opt = opts.headOption.getOrElse(UndefinedMemoryBehaviorOptions()) + + val c = state.circuit.mapModule(onModule(_, opt)) + state.copy(circuit = c) + } + + private def onModule(m: DefModule, opt: UndefinedMemoryBehaviorOptions): DefModule = m match { + case mod: Module => + val mems = findMems(mod) + if (mems.isEmpty) { mod } + else { + val namespace = Namespace(mod) + val connects = AnalysisUtils.getConnects(mod) + new InstrumentMems(opt, mems, connects, namespace).run(mod) + } + case other => other + } + + /** finds all memory instantiations in a circuit */ + private def findMems(m: Module): List[DefMemory] = { + val mems = mutable.ListBuffer[DefMemory]() + m.foreachStmt(findMems(_, mems)) + mems.toList + } + private def findMems(s: Statement, mems: mutable.ListBuffer[DefMemory]): Unit = s match { + case mem: DefMemory => mems.append(mem) + case other => other.foreachStmt(findMems(_, mems)) + } +} + +private class InstrumentMems( + opt: UndefinedMemoryBehaviorOptions, + mems: List[DefMemory], + connects: Connects, + namespace: Namespace) { + def run(m: Module): DefModule = { + // ensure that all memories are the kind we can support + mems.foreach(checkSupported(m.name, _)) + + // transform circuit + val body = m.body.mapStmt(transform) + m.copy(body = Block(body +: newStmts.toList)) + } + + // used to replace memory signals like `m.r.data` in RHS expressions + private val exprReplacements = mutable.HashMap[String, Expression]() + // add new statements at the end of the circuit + private val newStmts = mutable.ListBuffer[Statement]() + // disconnect references so that they can be reassigned + private val doDisconnect = mutable.HashSet[String]() + + // generates new expression replacements and immediately uses them + private def transform(s: Statement): Statement = s.mapStmt(transform) match { + case mem: DefMemory => onMem(mem) + case sx: Connect if doDisconnect.contains(sx.loc.serialize) => EmptyStmt // Filter old mem connections + case sx => sx.mapExpr(swapMemRefs) + } + private def swapMemRefs(e: Expression): Expression = e.mapExpr(swapMemRefs) match { + case sf: RefLikeExpression => exprReplacements.getOrElse(sf.serialize, sf) + case ex => ex + } + + private def onMem(m: DefMemory): Statement = { + // collect wire and random statement defines + val declarations = mutable.ListBuffer[Statement]() + + // only for non power of 2 memories do we have to worry about reading or writing out of bounds + val canBeOutOfBounds = !isPow2(m.depth) + + // only if we have at least two write ports, can there be conflicts + val canHaveWriteWriteConflicts = m.writers.size > 1 + + // only certain memory types exhibit undefined read/write conflicts + val readWriteUndefined = (m.readLatency == m.writeLatency) && (m.readUnderWrite == ReadUnderWrite.Undefined) + assert( + m.readLatency == 0 || m.readLatency == m.writeLatency, + "TODO: what happens if a sync read mem has asymmetrical latencies?" + ) + + // a write port is enabled iff mask & en + val writeEn = m.writers.map { write => + val enRef = memPortField(m, write, "en") + val maskRef = memPortField(m, write, "mask") + + val prods = getProductTerms(enRef) ++ getProductTerms(maskRef) + + // if we can have write/write conflicts, we are going to change the mask and enable pins + val expr = if (canHaveWriteWriteConflicts) { + val maskIsOne = isTrue(connects(maskRef.serialize)) + // if the mask is connected to a constant true, we do not need to consider it, this is a common case + if (maskIsOne) { + val enWire = disconnectInput(m.info, enRef) + declarations += enWire + Reference(enWire) + } else { + val maskWire = disconnectInput(m.info, maskRef) + val enWire = disconnectInput(m.info, enRef) + // create a node for the conjunction + val nodeName = namespace.newName(s"${m.name}_${write}_mask_and_en") + val node = DefNode(m.info, nodeName, Utils.and(Reference(maskWire), Reference(enWire))) + declarations ++= List(maskWire, enWire, node) + Reference(node) + } + } else { + Utils.and(enRef, maskRef) + } + (expr, prods) + } + + // implement the three undefined read behaviors + m.readers.foreach { read => + // many memories have their read enable hard wired to true + val canBeDisabled = !isTrue(memPortField(m, read, "en")) + val readEn = if (canBeDisabled) memPortField(m, read, "en") else Utils.True() + val addr = memPortField(m, read, "addr") + + // collect signals that would lead to a randomization + var doRand = List[Expression]() + + // randomize the read value when the address is out of bounds + if (canBeOutOfBounds && opt.randomizeOutOfBoundsRead) { + val cond = Utils.and(readEn, Utils.not(isInBounds(m.depth, addr))) + val node = DefNode(m.info, namespace.newName(s"${m.name}_${read}_oob"), cond) + declarations += node + doRand = Reference(node) +: doRand + } + + if (readWriteUndefined && opt.randomizeReadWriteConflicts) { + val (cond, d) = readWriteConflict(m, read, writeEn) + declarations ++= d + val node = DefNode(m.info, namespace.newName(s"${m.name}_${read}_rwc"), cond) + declarations += node + doRand = Reference(node) +: doRand + } + + // randomize the read value when the read is disabled + if (canBeDisabled && opt.randomizeDisabledReads) { + val cond = Utils.not(readEn) + val node = DefNode(m.info, namespace.newName(s"${m.name}_${read}_disabled"), cond) + declarations += node + doRand = Reference(node) +: doRand + } + + // if there are no signals that would require a randomization, there is nothing to do + if (doRand.isEmpty) { + // nothing to do + } else { + val doRandName = s"${m.name}_${read}_do_rand" + val doRandNode = if (doRand.size == 1) { doRand.head } + else { + val node = DefNode(m.info, namespace.newName(s"${m.name}_${read}_do_rand"), doRand.reduce(Utils.or)) + declarations += node + Reference(node) + } + val doRandSignal = if (m.readLatency == 0) { doRandNode } + else { + val clock = memPortField(m, read, "clk") + val (signal, regDecls) = pipeline(m.info, clock, doRandName, doRandNode, m.readLatency) + declarations ++= regDecls + signal + } + + // all old rhs references to m.r.data need to replace with m_r_data which might be random + val dataRef = memPortField(m, read, "data") + val dataWire = DefWire(m.info, namespace.newName(s"${m.name}_${read}_data"), m.dataType) + declarations += dataWire + exprReplacements(dataRef.serialize) = Reference(dataWire) + + // create a source of randomness and connect the new wire either to the actual data port or to the random value + val randName = namespace.newName(s"${m.name}_${read}_rand_data") + val random = DefRandom(m.info, randName, m.dataType, Some(memPortField(m, read, "clk")), doRandSignal) + declarations += random + val data = Utils.mux(doRandSignal, Reference(random), dataRef) + newStmts.append(Connect(m.info, Reference(dataWire), data)) + } + } + + // write + if (opt.randomizeWriteWriteConflicts) { + declarations ++= writeWriteConflicts(m, writeEn) + } + + // add an assertion that if the write is taking place, then the address must be in range + if (canBeOutOfBounds && opt.assertNoOutOfBoundsWrites) { + m.writers.zip(writeEn).foreach { + case (write, (combinedEn, _)) => + val addr = memPortField(m, write, "addr") + val cond = Utils.implies(combinedEn, isInBounds(m.depth, addr)) + val clk = memPortField(m, write, "clk") + val a = Verification(Formal.Assert, m.info, clk, cond, Utils.True(), StringLit("out of bounds read")) + newStmts.append(a) + } + } + + Block(m +: declarations.toList) + } + + private def pipeline( + info: Info, + clk: Expression, + prefix: String, + e: Expression, + latency: Int + ): (Expression, Seq[Statement]) = { + require(latency > 0) + val regs = (1 to latency).map { i => + val name = namespace.newName(prefix + s"_r$i") + DefRegister(info, name, e.tpe, clk, Utils.False(), Reference(name, e.tpe, RegKind, UnknownFlow)) + } + val expr = regs.foldLeft(e) { + case (prev, reg) => + newStmts.append(Connect(info, Reference(reg), prev)) + Reference(reg) + } + (expr, regs) + } + + private def readWriteConflict( + m: DefMemory, + read: String, + writeEn: Seq[(Expression, ProdTerms)] + ): (Expression, Seq[Statement]) = { + if (m.writers.isEmpty) return (Utils.False(), List()) + val declarations = mutable.ListBuffer[Statement]() + + val readEn = memPortField(m, read, "en") + val readProd = getProductTerms(readEn) + + // create all conflict signals + val conflicts = m.writers.zip(writeEn).map { + case (write, (writeEn, writeProd)) => + if (isMutuallyExclusive(readProd, writeProd)) { + Utils.False() + } else { + val name = namespace.newName(s"${m.name}_${read}_${write}_rwc") + val bothEn = Utils.and(readEn, writeEn) + val sameAddr = Utils.eq(memPortField(m, read, "addr"), memPortField(m, write, "addr")) + // we need a wire because this condition might be used in a random statement + val wire = DefWire(m.info, name, BoolType) + declarations += wire + newStmts.append(Connect(m.info, Reference(wire), Utils.and(bothEn, sameAddr))) + Reference(wire) + } + } + + (conflicts.reduce(Utils.or), declarations.toList) + } + + private type ProdTerms = Seq[Expression] + private def writeWriteConflicts(m: DefMemory, writeEn: Seq[(Expression, ProdTerms)]): Seq[Statement] = { + if (m.writers.size < 2) return List() + val declarations = mutable.ListBuffer[Statement]() + + // we first create all conflict signals: + val conflict = + m.writers + .zip(writeEn) + .zipWithIndex + .flatMap { + case ((w1, (en1, en1Prod)), i1) => + m.writers.zip(writeEn).drop(i1 + 1).map { + case (w2, (en2, en2Prod)) => + if (isMutuallyExclusive(en1Prod, en2Prod)) { + (w1, w2) -> Utils.False() + } else { + val name = namespace.newName(s"${m.name}_${w1}_${w2}_wwc") + val bothEn = Utils.and(en1, en2) + val sameAddr = Utils.eq(memPortField(m, w1, "addr"), memPortField(m, w2, "addr")) + // we need a wire because this condition might be used in a random statement + val wire = DefWire(m.info, name, BoolType) + declarations += wire + newStmts.append(Connect(m.info, Reference(wire), Utils.and(bothEn, sameAddr))) + (w1, w2) -> Reference(wire) + } + } + } + .toMap + + // now we calculate the new enable and data signals + m.writers.zip(writeEn).zipWithIndex.foreach { + case ((w1, (en1, _)), i1) => + val prev = m.writers.take(i1) + val next = m.writers.drop(i1 + 1) + + // the write is enabled if the original enable is true and there are no prior conflicts + val en = if (prev.isEmpty) { + en1 + } else { + val prevConflicts = prev.map(o => conflict(o, w1)).reduce(Utils.or) + Utils.and(en1, Utils.not(prevConflicts)) + } + + // we write random data if there is a conflict with any of the next ports + if (next.isEmpty) { + // nothing to do, leave data as is + } else { + val nextConflicts = next.map(n => conflict(w1, n)).reduce(Utils.or) + // if the conflict expression is more complex, create a node for the signal + val hasConflict = nextConflicts match { + case _: DoPrim | _: Mux => + val node = DefNode(m.info, namespace.newName(s"${m.name}_${w1}_wwc_active"), nextConflicts) + declarations += node + Reference(node) + case _ => nextConflicts + } + + // create the source of randomness + val name = namespace.newName(s"${m.name}_${w1}_wwc_data") + val random = DefRandom(m.info, name, m.dataType, Some(memPortField(m, w1, "clk")), hasConflict) + declarations.append(random) + // replace the old data input + val dataWire = disconnectInput(m.info, memPortField(m, w1, "data")) + declarations += dataWire + // generate new data input + val data = Utils.mux(hasConflict, Reference(random), Reference(dataWire)) + newStmts.append(Connect(m.info, memPortField(m, w1, "data"), data)) + } + + // connect data enable signals + val maskIsOne = isTrue(connects(memPortField(m, w1, "mask").serialize)) + if (!maskIsOne) { + newStmts.append(Connect(m.info, memPortField(m, w1, "mask"), Utils.True())) + } + newStmts.append(Connect(m.info, memPortField(m, w1, "en"), en)) + } + + declarations.toList + } + + /** check whether two signals can be proven to be mutually exclusive */ + private def isMutuallyExclusive(prodA: ProdTerms, prodB: ProdTerms): Boolean = { + // this uses the same approach as the InferReadWrite pass + val proofOfMutualExclusion = prodA.find(a => prodB.exists(b => checkComplement(a, b))) + proofOfMutualExclusion.nonEmpty + } + + /** replace a memory port with a wire */ + private def disconnectInput(info: Info, signal: RefLikeExpression): DefWire = { + // disconnect the old value + doDisconnect.add(signal.serialize) + + // if the old value is a literal, we just replace all references to it with this literal + val oldValue = connects(signal.serialize) + if (isLiteral(oldValue)) { + println("TODO: better code for literal") + } + + // create a new wire and replace all references to the original port with this wire + val wire = DefWire(info, copyName(signal), signal.tpe) + exprReplacements(signal.serialize) = Reference(wire) + // connect the old expression to the new wire + val con = Connect(info, Reference(wire), connects(signal.serialize)) + newStmts.append(con) + + // the wire definition should end up right after the memory definition + wire + } + + private def copyName(ref: RefLikeExpression): String = + namespace.newName(ref.serialize.replace('.', '_')) + + private def isInBounds(depth: BigInt, addr: Expression): Expression = { + val width = getWidth(addr) + // depth >= addr + DoPrim(PrimOps.Geq, List(UIntLiteral(depth, width), addr), List(), BoolType) + } + + private def isPow2(v: BigInt): Boolean = ((v - 1) & v) == 0 + + private def checkSupported(modName: String, m: DefMemory): Unit = { + assert(m.readwriters.isEmpty, s"[$modName] Combined read/write ports are currently not supported!") + if (m.writeLatency != 1) { + throw new UnsupportedFeatureException(s"[$modName] memories with write latency > 1 (${m.name})") + } + if (m.readLatency > 1) { + throw new UnsupportedFeatureException(s"[$modName] memories with read latency > 1 (${m.name})") + } + } + + private def getProductTerms(e: Expression): ProdTerms = + InferReadWritePass.getProductTerms(connects)(e) + + /** tries to expand the expression based on the connects we collected */ + private def expandExpr(e: Expression, fuel: Int): Expression = { + e match { + case m @ Mux(cond, tval, fval, _) => + m.copy(cond = expandExpr(cond, fuel), tval = expandExpr(tval, fuel), fval = expandExpr(fval, fuel)) + case p @ DoPrim(_, args, _, _) => + p.copy(args = args.map(expandExpr(_, fuel))) + case r: RefLikeExpression => + if (fuel > 0) { + connects.get(r.serialize) match { + case None => r + case Some(expr) => expandExpr(expr, fuel - 1) + } + } else { + r + } + case other => other + } + } + + private def isTrue(e: Expression): Boolean = simplifyExpr(expandExpr(e, fuel = 2)) == Utils.True() + + private def simplifyExpr(e: Expression): Expression = { + e // TODO: better simplification could improve the resulting circuit size + } +} diff --git a/src/main/scala/firrtl/ir/IR.scala b/src/main/scala/firrtl/ir/IR.scala index 9c3d6186f3..13ba3d46cb 100644 --- a/src/main/scala/firrtl/ir/IR.scala +++ b/src/main/scala/firrtl/ir/IR.scala @@ -4,6 +4,7 @@ package firrtl package ir import Utils.{dec2string, trim} +import firrtl.backends.experimental.smt.random.DefRandom import dataclass.{data, since} import firrtl.constraint.{Constraint, IsKnown, IsVar} import org.apache.commons.text.translate.{AggregateTranslator, JavaUnicodeEscaper, LookupTranslator} @@ -242,6 +243,9 @@ object Reference { /** Creates a Reference from a Register */ def apply(reg: DefRegister): Reference = Reference(reg.name, reg.tpe, RegKind, UnknownFlow) + /** Creates a Reference from a Random Source */ + def apply(rnd: DefRandom): Reference = Reference(rnd.name, rnd.tpe, RandomKind, UnknownFlow) + /** Creates a Reference from a Node */ def apply(node: DefNode): Reference = Reference(node.name, node.value.tpe, NodeKind, SourceFlow) diff --git a/src/main/scala/firrtl/ir/Serializer.scala b/src/main/scala/firrtl/ir/Serializer.scala index caea0a9cae..dca902feef 100644 --- a/src/main/scala/firrtl/ir/Serializer.scala +++ b/src/main/scala/firrtl/ir/Serializer.scala @@ -2,6 +2,8 @@ package firrtl.ir +import firrtl.Utils +import firrtl.backends.experimental.smt.random.DefRandom import firrtl.constraint.Constraint object Serializer { @@ -114,6 +116,11 @@ object Serializer { case DefRegister(info, name, tpe, clock, reset, init) => b ++= "reg "; b ++= name; b ++= " : "; s(tpe); b ++= ", "; s(clock); b ++= " with :"; newLineAndIndent(1) b ++= "reset => ("; s(reset); b ++= ", "; s(init); b += ')'; s(info) + case DefRandom(info, name, tpe, clock, en) => + b ++= "rand "; b ++= name; b ++= " : "; s(tpe); + if (clock.isDefined) { b ++= ", "; s(clock.get); } + en match { case Utils.True() => case _ => b ++= " when "; s(en) } + s(info) case DefInstance(info, name, module, _) => b ++= "inst "; b ++= name; b ++= " of "; b ++= module; s(info) case DefMemory( info, diff --git a/src/main/scala/firrtl/passes/ResolveKinds.scala b/src/main/scala/firrtl/passes/ResolveKinds.scala index e3218467c1..745be1e26c 100644 --- a/src/main/scala/firrtl/passes/ResolveKinds.scala +++ b/src/main/scala/firrtl/passes/ResolveKinds.scala @@ -5,6 +5,7 @@ package firrtl.passes import firrtl._ import firrtl.ir._ import firrtl.Mappers._ +import firrtl.backends.experimental.smt.random.DefRandom import firrtl.traversals.Foreachers._ object ResolveKinds extends Pass { @@ -31,6 +32,7 @@ object ResolveKinds extends Pass { case sx: DefRegister => kinds(sx.name) = RegKind case sx: WDefInstance => kinds(sx.name) = InstanceKind case sx: DefMemory => kinds(sx.name) = MemKind + case sx: DefRandom => kinds(sx.name) = RandomKind case _ => } s.map(resolve_stmt(kinds)) diff --git a/src/main/scala/firrtl/passes/memlib/VerilogMemDelays.scala b/src/main/scala/firrtl/passes/memlib/VerilogMemDelays.scala index 8fb2dc889f..143b925a9a 100644 --- a/src/main/scala/firrtl/passes/memlib/VerilogMemDelays.scala +++ b/src/main/scala/firrtl/passes/memlib/VerilogMemDelays.scala @@ -177,7 +177,8 @@ class MemDelayAndReadwriteTransformer(m: DefModule) { object VerilogMemDelays extends Pass { - override def prerequisites = firrtl.stage.Forms.LowForm :+ Dependency(firrtl.passes.RemoveValidIf) + override def prerequisites = firrtl.stage.Forms.LowForm + override val optionalPrerequisites = Seq(Dependency(firrtl.passes.RemoveValidIf)) override val optionalPrerequisiteOf = Seq(Dependency[VerilogEmitter], Dependency[SystemVerilogEmitter]) diff --git a/src/main/scala/firrtl/transforms/DeadCodeElimination.scala b/src/main/scala/firrtl/transforms/DeadCodeElimination.scala index 1d9bfd0e1f..f72585d112 100644 --- a/src/main/scala/firrtl/transforms/DeadCodeElimination.scala +++ b/src/main/scala/firrtl/transforms/DeadCodeElimination.scala @@ -11,6 +11,7 @@ import firrtl.analyses.InstanceKeyGraph import firrtl.Mappers._ import firrtl.Utils.{kind, throwInternalError} import firrtl.MemoizedHash._ +import firrtl.backends.experimental.smt.random.DefRandom import firrtl.options.{Dependency, RegisteredTransform, ShellOption} import collection.mutable @@ -126,6 +127,11 @@ class DeadCodeElimination extends Transform with RegisteredTransform with Depend val node = LogicNode(mod.name, name) depGraph.addVertex(node) Seq(clock, reset, init).flatMap(getDeps(_)).foreach(ref => depGraph.addPairWithEdge(node, ref)) + case DefRandom(_, name, _, clock, en) => + val node = LogicNode(mod.name, name) + depGraph.addVertex(node) + val inputs = clock ++: en +: Nil + inputs.flatMap(getDeps).foreach(ref => depGraph.addPairWithEdge(node, ref)) case DefNode(_, name, value) => val node = LogicNode(mod.name, name) depGraph.addVertex(node) @@ -225,6 +231,7 @@ class DeadCodeElimination extends Transform with RegisteredTransform with Depend val tpe = decl match { case _: DefNode => "node" case _: DefRegister => "reg" + case _: DefRandom => "rand" case _: DefWire => "wire" case _: Port => "port" case _: DefMemory => "mem" diff --git a/src/main/scala/firrtl/transforms/RemoveWires.scala b/src/main/scala/firrtl/transforms/RemoveWires.scala index f2907db27f..7500b3860f 100644 --- a/src/main/scala/firrtl/transforms/RemoveWires.scala +++ b/src/main/scala/firrtl/transforms/RemoveWires.scala @@ -11,6 +11,7 @@ import firrtl.WrappedExpression._ import firrtl.graph.{CyclicException, MutableDiGraph} import firrtl.options.Dependency import firrtl.Utils.getGroundZero +import firrtl.backends.experimental.smt.random.DefRandom import scala.collection.mutable import scala.util.{Failure, Success, Try} @@ -41,13 +42,13 @@ class RemoveWires extends Transform with DependencyAPIMigration { case _ => false } - // Extract all expressions that are references to a Node, Wire, or Reg + // Extract all expressions that are references to a Node, Wire, Reg or Rand // Since we are operating on LowForm, they can only be WRefs private def extractNodeWireRegRefs(expr: Expression): Seq[WRef] = { val refs = mutable.ArrayBuffer.empty[WRef] def rec(e: Expression): Expression = { e match { - case ref @ WRef(_, _, WireKind | NodeKind | RegKind, _) => refs += ref + case ref @ WRef(_, _, WireKind | NodeKind | RegKind | RandomKind, _) => refs += ref case nested @ (_: Mux | _: DoPrim | _: ValidIf) => nested.foreach(rec) case _ => // Do nothing } @@ -59,8 +60,9 @@ class RemoveWires extends Transform with DependencyAPIMigration { // Transform netlist into DefNodes private def getOrderedNodes( - netlist: mutable.LinkedHashMap[WrappedExpression, (Seq[Expression], Info)], - regInfo: mutable.Map[WrappedExpression, DefRegister] + netlist: mutable.LinkedHashMap[WrappedExpression, (Seq[Expression], Info)], + regInfo: mutable.Map[WrappedExpression, DefRegister], + randInfo: mutable.Map[WrappedExpression, DefRandom] ): Try[Seq[Statement]] = { val digraph = new MutableDiGraph[WrappedExpression] for ((sink, (exprs, _)) <- netlist) { @@ -80,7 +82,8 @@ class RemoveWires extends Transform with DependencyAPIMigration { ordered.map { key => val WRef(name, _, kind, _) = key.e1 kind match { - case RegKind => regInfo(key) + case RegKind => regInfo(key) + case RandomKind => randInfo(key) case WireKind | NodeKind => val (Seq(rhs), info) = netlist(key) DefNode(info, name, rhs) @@ -100,6 +103,8 @@ class RemoveWires extends Transform with DependencyAPIMigration { val wireInfo = mutable.HashMap.empty[WrappedExpression, Info] // Additional info about registers val regInfo = mutable.HashMap.empty[WrappedExpression, DefRegister] + // Additional info about rand statements + val randInfo = mutable.HashMap.empty[WrappedExpression, DefRandom] def onStmt(stmt: Statement): Statement = { stmt match { @@ -115,6 +120,9 @@ class RemoveWires extends Transform with DependencyAPIMigration { val initDep = Some(reg.init).filter(we(WRef(reg)) != we(_)) // Dependency exists IF reg doesn't init itself regInfo(we(WRef(reg))) = reg netlist(we(WRef(reg))) = (Seq(reg.clock) ++ resetDep ++ initDep, reg.info) + case rand: DefRandom => + randInfo(we(Reference(rand))) = rand + netlist(we(Reference(rand))) = (rand.clock ++: rand.en +: List(), rand.info) case decl: CanBeReferenced => // Keep all declarations except for nodes and non-Analog wires and "other" statements. // Thus this is expected to match DefInstance and DefMemory which both do not connect to @@ -148,7 +156,7 @@ class RemoveWires extends Transform with DependencyAPIMigration { m match { case mod @ Module(info, name, ports, body) => onStmt(body) - getOrderedNodes(netlist, regInfo) match { + getOrderedNodes(netlist, regInfo, randInfo) match { case Success(logic) => Module(info, name, ports, Block(List() ++ decls ++ logic ++ otherStmts)) // If we hit a CyclicException, just abort removing wires diff --git a/src/test/scala/firrtl/backends/experimental/smt/random/UndefinedMemoryBehaviorSpec.scala b/src/test/scala/firrtl/backends/experimental/smt/random/UndefinedMemoryBehaviorSpec.scala new file mode 100644 index 0000000000..c6f89b933d --- /dev/null +++ b/src/test/scala/firrtl/backends/experimental/smt/random/UndefinedMemoryBehaviorSpec.scala @@ -0,0 +1,360 @@ +package firrtl.backends.experimental.smt.random + +import firrtl.options.Dependency +import firrtl.testutils.LeanTransformSpec + +class UndefinedMemoryBehaviorSpec extends LeanTransformSpec(Seq(Dependency(UndefinedMemoryBehaviorPass))) { + behavior.of("UndefinedMemoryBehaviorPass") + + it should "model write-write conflicts between 2 ports" in { + + val circuit = compile(UBMSources.writeWriteConflict, List()).circuit + // println(circuit.serialize) + val result = circuit.serialize.split('\n').map(_.trim) + + // a random value should be declared for the data written on a write-write conflict + assert(result.contains("rand m_a_wwc_data : UInt<32>, m.a.clk when m_a_b_wwc")) + + // a write-write conflict occurs when both ports are enabled and the addresses match + assert(result.contains("m_a_b_wwc <= and(and(m_a_en, m_b_en), eq(m.a.addr, m.b.addr))")) + + // the data of read port a depends on whether there is a write-write conflict + assert(result.contains("m.a.data <= mux(m_a_b_wwc, m_a_wwc_data, m_a_data)")) + + // the enable of read port b depends on whether there is a write-write conflict + assert(result.contains("m.b.en <= and(m_b_en, not(m_a_b_wwc))")) + } + + it should "model write-write conflicts between 3 ports" in { + + val circuit = compile(UBMSources.writeWriteConflict3, List()).circuit + //println(circuit.serialize) + val result = circuit.serialize.split('\n').map(_.trim) + + // when there is more than one next write port, a "active" node is created + assert(result.contains("node m_a_wwc_active = or(m_a_b_wwc, m_a_c_wwc)")) + + // a random value should be declared for the data written on a write-write conflict + assert(result.contains("rand m_a_wwc_data : UInt<32>, m.a.clk when m_a_wwc_active")) + assert(result.contains("rand m_b_wwc_data : UInt<32>, m.b.clk when m_b_c_wwc")) + + // a write-write conflict occurs when both ports are enabled and the addresses match + Seq(("a", "b"), ("a", "c"), ("b", "c")).foreach { + case (w1, w2) => + assert( + result.contains(s"m_${w1}_${w2}_wwc <= and(and(m_${w1}_en, m_${w2}_en), eq(m.${w1}.addr, m.${w2}.addr))") + ) + } + + // the data of read port a depends on whether there is a write-write conflict + assert(result.contains("m.a.data <= mux(m_a_wwc_active, m_a_wwc_data, m_a_data)")) + + // the data of read port b depends on whether there is a write-write conflict + assert(result.contains("m.b.data <= mux(m_b_c_wwc, m_b_wwc_data, m_b_data)")) + + // the enable of read port b depends on whether there is a write-write conflict + assert(result.contains("m.b.en <= and(m_b_en, not(m_a_b_wwc))")) + + // the enable of read port c depends on whether there is a write-write conflict + // note that in this case we do not add an extra node since the disjunction is only used once + assert(result.contains("m.c.en <= and(m_c_en, not(or(m_a_c_wwc, m_b_c_wwc)))")) + } + + it should "model write-write conflicts more efficiently when ports are mutually exclusive" in { + + val circuit = compile(UBMSources.writeWriteConflict3Exclusive, List()).circuit + // println(circuit.serialize) + val result = circuit.serialize.split('\n').map(_.trim) + + // we should not compute the conflict between a and c since it is impossible + assert(!result.contains("node m_a_c_wwc = and(and(m_a_en, m_c_en), eq(m.a.addr, m.c.addr))")) + + // the enable of port b depends on whether there is a conflict with a + assert(result.contains("m.b.en <= and(m_b_en, not(m_a_b_wwc))")) + + // the data of port b depends on whether these is a conflict with c + assert(result.contains("m.b.data <= mux(m_b_c_wwc, m_b_wwc_data, m_b_data)")) + + // the enable of port c only depend on whether there is a conflict with b since c and a cannot conflict + assert(result.contains("m.c.en <= and(m_c_en, not(m_b_c_wwc))")) + + // the data of port a only depends on whether there is a conflict with b since a and c cannot conflict + assert(result.contains("m.a.data <= mux(m_a_b_wwc, m_a_wwc_data, m_a_data)")) + } + + it should "assert out-of-bounds writes when told to" in { + val anno = List(UndefinedMemoryBehaviorOptions(assertNoOutOfBoundsWrites = true)) + + val circuit = compile(UBMSources.readWrite(30, 0), anno).circuit + // println(circuit.serialize) + val result = circuit.serialize.split('\n').map(_.trim) + + assert( + result.contains( + """assert(m.a.clk, or(not(and(m.a.en, m.a.mask)), geq(UInt<5>("h1e"), m.a.addr)), UInt<1>("h1"), "out of bounds read")""" + ) + ) + } + + it should "model out-of-bounds reads" in { + val circuit = compile(UBMSources.readWrite(30, 0), List()).circuit + //println(circuit.serialize) + val result = circuit.serialize.split('\n').map(_.trim) + + // an out of bounds read happens if the depth is not greater or equal to the address + assert(result.contains("node m_r_oob = not(geq(UInt<5>(\"h1e\"), m.r.addr))")) + + // the source of randomness needs to be triggered when there is an out of bounds read + assert(result.contains("rand m_r_rand_data : UInt<32>, m.r.clk when m_r_oob")) + + // the data is random when there is an oob + assert(result.contains("m_r_data <= mux(m_r_oob, m_r_rand_data, m.r.data)")) + } + + it should "model un-enabled reads w/o out-of-bounds" in { + // without possible out-of-bounds + val circuit = compile(UBMSources.readEnable(32), List()).circuit + //println(circuit.serialize) + val result = circuit.serialize.split('\n').map(_.trim) + + // the memory is disabled when it is not enabled + assert(result.contains("node m_r_disabled = not(m.r.en)")) + + // the source of randomness needs to be triggered when there is an read while the port is disabled + assert(result.contains("rand m_r_rand_data : UInt<32>, m.r.clk when m_r_disabled")) + + // the data is random when there is an un-enabled read + assert(result.contains("m_r_data <= mux(m_r_disabled, m_r_rand_data, m.r.data)")) + } + + it should "model un-enabled reads with out-of-bounds" in { + // with possible out-of-bounds + val circuit = compile(UBMSources.readEnable(30), List()).circuit + //println(circuit.serialize) + val result = circuit.serialize.split('\n').map(_.trim) + + // the memory is disabled when it is not enabled + assert(result.contains("node m_r_disabled = not(m.r.en)")) + + // an out of bounds read happens if the depth is not greater or equal to the address and the memory is enabled + assert(result.contains("node m_r_oob = and(m.r.en, not(geq(UInt<5>(\"h1e\"), m.r.addr)))")) + + // the two possible issues are combined into a single signal + assert(result.contains("node m_r_do_rand = or(m_r_disabled, m_r_oob)")) + + // the source of randomness needs to be triggered when either issue occurs + assert(result.contains("rand m_r_rand_data : UInt<32>, m.r.clk when m_r_do_rand")) + + // the data is random when either issue occurs + assert(result.contains("m_r_data <= mux(m_r_do_rand, m_r_rand_data, m.r.data)")) + } + + it should "model un-enabled reads with out-of-bounds with read pipelining" in { + // with read latency one, we need to pipeline the `do_rand` signal + val circuit = compile(UBMSources.readEnable(30, 1), List()).circuit + //println(circuit.serialize) + val result = circuit.serialize.split('\n').map(_.trim) + + // pipeline register + assert(result.contains("m_r_do_rand_r1 <= m_r_do_rand")) + + // the source of randomness needs to be triggered by the pipeline register + assert(result.contains("rand m_r_rand_data : UInt<32>, m.r.clk when m_r_do_rand_r1")) + + // the data is random when the pipeline register is 1 + assert(result.contains("m_r_data <= mux(m_r_do_rand_r1, m_r_rand_data, m.r.data)")) + } + + it should "model read/write conflicts when they are undefined" in { + val circuit = compile(UBMSources.readWrite(32, 1), List()).circuit + //println(circuit.serialize) + val result = circuit.serialize.split('\n').map(_.trim) + + // detect read/write conflicts + assert(result.contains("m_r_a_rwc <= and(and(m.r.en, and(m.a.en, m.a.mask)), eq(m.r.addr, m.a.addr))")) + + // delay the signal + assert(result.contains("m_r_do_rand_r1 <= m_r_rwc")) + + // randomize the data + assert(result.contains("rand m_r_rand_data : UInt<32>, m.r.clk when m_r_do_rand_r1")) + assert(result.contains("m_r_data <= mux(m_r_do_rand_r1, m_r_rand_data, m.r.data)")) + } +} + +private object UBMSources { + + val writeWriteConflict = + s""" + |circuit Test: + | module Test: + | input c : Clock + | input preset: AsyncReset + | input addr : UInt<8> + | input data : UInt<32> + | input aEn : UInt<1> + | input bEn : UInt<1> + | + | mem m: + | data-type => UInt<32> + | depth => 32 + | reader => r + | writer => a, b + | read-latency => 0 + | write-latency => 1 + | read-under-write => undefined + | + | m.r.clk <= c + | m.r.en <= UInt(1) + | m.r.addr <= addr + | + | ; both read ports write to the same address and the same data + | m.a.clk <= c + | m.a.en <= aEn + | m.a.addr <= addr + | m.a.data <= data + | m.a.mask <= UInt(1) + | m.b.clk <= c + | m.b.en <= bEn + | m.b.addr <= addr + | m.b.data <= data + | m.b.mask <= UInt(1) + """.stripMargin + + val writeWriteConflict3 = + s""" + |circuit Test: + | module Test: + | input c : Clock + | input preset: AsyncReset + | input addr : UInt<8> + | input data : UInt<32> + | input aEn : UInt<1> + | input bEn : UInt<1> + | input cEn : UInt<1> + | + | mem m: + | data-type => UInt<32> + | depth => 32 + | reader => r + | writer => a, b, c + | read-latency => 0 + | write-latency => 1 + | read-under-write => undefined + | + | m.r.clk <= c + | m.r.en <= UInt(1) + | m.r.addr <= addr + | + | ; both read ports write to the same address and the same data + | m.a.clk <= c + | m.a.en <= aEn + | m.a.addr <= addr + | m.a.data <= data + | m.a.mask <= UInt(1) + | m.b.clk <= c + | m.b.en <= bEn + | m.b.addr <= addr + | m.b.data <= data + | m.b.mask <= UInt(1) + | m.c.clk <= c + | m.c.en <= cEn + | m.c.addr <= addr + | m.c.data <= data + | m.c.mask <= UInt(1) + """.stripMargin + + val writeWriteConflict3Exclusive = + s""" + |circuit Test: + | module Test: + | input c : Clock + | input preset: AsyncReset + | input addr : UInt<8> + | input data : UInt<32> + | input aEn : UInt<1> + | input bEn : UInt<1> + | + | mem m: + | data-type => UInt<32> + | depth => 32 + | reader => r + | writer => a, b, c + | read-latency => 0 + | write-latency => 1 + | read-under-write => undefined + | + | m.r.clk <= c + | m.r.en <= UInt(1) + | m.r.addr <= addr + | + | ; both read ports write to the same address and the same data + | m.a.clk <= c + | m.a.en <= aEn + | m.a.addr <= addr + | m.a.data <= data + | m.a.mask <= UInt(1) + | m.b.clk <= c + | m.b.en <= bEn + | m.b.addr <= addr + | m.b.data <= data + | m.b.mask <= UInt(1) + | m.c.clk <= c + | m.c.en <= not(aEn) + | m.c.addr <= addr + | m.c.data <= data + | m.c.mask <= UInt(1) + """.stripMargin + + def readWrite(depth: Int, readLatency: Int) = + s"""circuit CollisionTest: + | module CollisionTest: + | input c : Clock + | input preset: AsyncReset + | input addr : UInt<8> + | input data : UInt<32> + | output dataOut : UInt<32> + | + | mem m: + | data-type => UInt<32> + | depth => $depth + | reader => r + | writer => a + | read-latency => $readLatency + | write-latency => 1 + | read-under-write => undefined + | + | m.r.clk <= c + | m.r.en <= UInt(1) + | m.r.addr <= addr + | dataOut <= m.r.data + | + | m.a.clk <= c + | m.a.mask <= UInt(1) + | m.a.en <= UInt(1) + | m.a.addr <= addr + | m.a.data <= data + |""".stripMargin + + def readEnable(depth: Int, latency: Int = 0) = + s"""circuit Test: + | module Test: + | input c : Clock + | input addr : UInt<8> + | input en : UInt<1> + | output data : UInt<32> + | + | mem m: + | data-type => UInt<32> + | depth => $depth + | reader => r + | read-latency => $latency + | write-latency => 1 + | read-under-write => old + | + | m.r.clk <= c + | m.r.en <= en + | m.r.addr <= addr + | data <= m.r.data + |""".stripMargin +} From 29d57a612df69ae4a6db4b3755fc292e5a539e11 Mon Sep 17 00:00:00 2001 From: Kevin Laeufer Date: Mon, 8 Mar 2021 15:39:15 -0800 Subject: [PATCH 35/88] SMT: memory port inout fields cannot be used as RHS expressions (#2105) * SMT: memory port inout fields cannot be used as RHS expressions * smt: add end2end check for read enable modelling --- .../random/UndefinedMemoryBehaviorPass.scala | 146 +++++++++--------- .../experimental/smt/end2end/MemorySpec.scala | 42 +++++ .../random/UndefinedMemoryBehaviorSpec.scala | 34 ++-- 3 files changed, 134 insertions(+), 88 deletions(-) diff --git a/src/main/scala/firrtl/backends/experimental/smt/random/UndefinedMemoryBehaviorPass.scala b/src/main/scala/firrtl/backends/experimental/smt/random/UndefinedMemoryBehaviorPass.scala index 91e77433cd..5fd0e68091 100644 --- a/src/main/scala/firrtl/backends/experimental/smt/random/UndefinedMemoryBehaviorPass.scala +++ b/src/main/scala/firrtl/backends/experimental/smt/random/UndefinedMemoryBehaviorPass.scala @@ -2,8 +2,7 @@ package firrtl.backends.experimental.smt.random -import firrtl.Utils.{isLiteral, kind, BoolType} -import firrtl.WrappedExpression.{we, weq} +import firrtl.Utils.{isLiteral, BoolType} import firrtl._ import firrtl.annotations.NoTargetAnnotation import firrtl.backends.experimental.smt._ @@ -14,7 +13,7 @@ import firrtl.passes.memlib.AnalysisUtils.Connects import firrtl.passes.memlib.InferReadWritePass.checkComplement import firrtl.passes.memlib.{AnalysisUtils, InferReadWritePass, VerilogMemDelays} import firrtl.stage.Forms -import firrtl.transforms.{ConstantPropagation, RemoveWires} +import firrtl.transforms.RemoveWires import scala.collection.mutable @@ -111,7 +110,10 @@ private class InstrumentMems( private def onMem(m: DefMemory): Statement = { // collect wire and random statement defines - val declarations = mutable.ListBuffer[Statement]() + implicit val declarations: mutable.ListBuffer[Statement] = mutable.ListBuffer[Statement]() + + // cache for the expressions of memory inputs + implicit val cache: mutable.HashMap[String, Expression] = mutable.HashMap[String, Expression]() // only for non power of 2 memories do we have to worry about reading or writing out of bounds val canBeOutOfBounds = !isPow2(m.depth) @@ -132,42 +134,23 @@ private class InstrumentMems( val maskRef = memPortField(m, write, "mask") val prods = getProductTerms(enRef) ++ getProductTerms(maskRef) + val expr = Utils.and(readInput(m.info, enRef), readInput(m.info, maskRef)) - // if we can have write/write conflicts, we are going to change the mask and enable pins - val expr = if (canHaveWriteWriteConflicts) { - val maskIsOne = isTrue(connects(maskRef.serialize)) - // if the mask is connected to a constant true, we do not need to consider it, this is a common case - if (maskIsOne) { - val enWire = disconnectInput(m.info, enRef) - declarations += enWire - Reference(enWire) - } else { - val maskWire = disconnectInput(m.info, maskRef) - val enWire = disconnectInput(m.info, enRef) - // create a node for the conjunction - val nodeName = namespace.newName(s"${m.name}_${write}_mask_and_en") - val node = DefNode(m.info, nodeName, Utils.and(Reference(maskWire), Reference(enWire))) - declarations ++= List(maskWire, enWire, node) - Reference(node) - } - } else { - Utils.and(enRef, maskRef) - } (expr, prods) } // implement the three undefined read behaviors m.readers.foreach { read => // many memories have their read enable hard wired to true - val canBeDisabled = !isTrue(memPortField(m, read, "en")) - val readEn = if (canBeDisabled) memPortField(m, read, "en") else Utils.True() - val addr = memPortField(m, read, "addr") + val canBeDisabled = !isTrue(readInput(m, read, "en")) + val readEn = if (canBeDisabled) readInput(m, read, "en") else Utils.True() // collect signals that would lead to a randomization var doRand = List[Expression]() // randomize the read value when the address is out of bounds if (canBeOutOfBounds && opt.randomizeOutOfBoundsRead) { + val addr = readInput(m, read, "addr") val cond = Utils.and(readEn, Utils.not(isInBounds(m.depth, addr))) val node = DefNode(m.info, namespace.newName(s"${m.name}_${read}_oob"), cond) declarations += node @@ -175,8 +158,7 @@ private class InstrumentMems( } if (readWriteUndefined && opt.randomizeReadWriteConflicts) { - val (cond, d) = readWriteConflict(m, read, writeEn) - declarations ++= d + val cond = readWriteConflict(m, read, writeEn) val node = DefNode(m.info, namespace.newName(s"${m.name}_${read}_rwc"), cond) declarations += node doRand = Reference(node) +: doRand @@ -203,7 +185,7 @@ private class InstrumentMems( } val doRandSignal = if (m.readLatency == 0) { doRandNode } else { - val clock = memPortField(m, read, "clk") + val clock = readInput(m, read, "clk") val (signal, regDecls) = pipeline(m.info, clock, doRandName, doRandNode, m.readLatency) declarations ++= regDecls signal @@ -217,7 +199,7 @@ private class InstrumentMems( // create a source of randomness and connect the new wire either to the actual data port or to the random value val randName = namespace.newName(s"${m.name}_${read}_rand_data") - val random = DefRandom(m.info, randName, m.dataType, Some(memPortField(m, read, "clk")), doRandSignal) + val random = DefRandom(m.info, randName, m.dataType, Some(readInput(m, read, "clk")), doRandSignal) declarations += random val data = Utils.mux(doRandSignal, Reference(random), dataRef) newStmts.append(Connect(m.info, Reference(dataWire), data)) @@ -226,16 +208,16 @@ private class InstrumentMems( // write if (opt.randomizeWriteWriteConflicts) { - declarations ++= writeWriteConflicts(m, writeEn) + writeWriteConflicts(m, writeEn) } // add an assertion that if the write is taking place, then the address must be in range if (canBeOutOfBounds && opt.assertNoOutOfBoundsWrites) { m.writers.zip(writeEn).foreach { case (write, (combinedEn, _)) => - val addr = memPortField(m, write, "addr") + val addr = readInput(m, write, "addr") val cond = Utils.implies(combinedEn, isInBounds(m.depth, addr)) - val clk = memPortField(m, write, "clk") + val clk = readInput(m, write, "clk") val a = Verification(Formal.Assert, m.info, clk, cond, Utils.True(), StringLit("out of bounds read")) newStmts.append(a) } @@ -268,11 +250,13 @@ private class InstrumentMems( m: DefMemory, read: String, writeEn: Seq[(Expression, ProdTerms)] - ): (Expression, Seq[Statement]) = { - if (m.writers.isEmpty) return (Utils.False(), List()) - val declarations = mutable.ListBuffer[Statement]() + )( + implicit cache: mutable.HashMap[String, Expression], + declarations: mutable.ListBuffer[Statement] + ): Expression = { + if (m.writers.isEmpty) return Utils.False() - val readEn = memPortField(m, read, "en") + val readEn = readInput(m, read, "en") val readProd = getProductTerms(readEn) // create all conflict signals @@ -283,7 +267,7 @@ private class InstrumentMems( } else { val name = namespace.newName(s"${m.name}_${read}_${write}_rwc") val bothEn = Utils.and(readEn, writeEn) - val sameAddr = Utils.eq(memPortField(m, read, "addr"), memPortField(m, write, "addr")) + val sameAddr = Utils.eq(readInput(m, read, "addr"), readInput(m, write, "addr")) // we need a wire because this condition might be used in a random statement val wire = DefWire(m.info, name, BoolType) declarations += wire @@ -292,13 +276,18 @@ private class InstrumentMems( } } - (conflicts.reduce(Utils.or), declarations.toList) + conflicts.reduce(Utils.or) } private type ProdTerms = Seq[Expression] - private def writeWriteConflicts(m: DefMemory, writeEn: Seq[(Expression, ProdTerms)]): Seq[Statement] = { - if (m.writers.size < 2) return List() - val declarations = mutable.ListBuffer[Statement]() + private def writeWriteConflicts( + m: DefMemory, + writeEn: Seq[(Expression, ProdTerms)] + )( + implicit cache: mutable.HashMap[String, Expression], + declarations: mutable.ListBuffer[Statement] + ): Unit = { + if (m.writers.size < 2) return // we first create all conflict signals: val conflict = @@ -314,7 +303,7 @@ private class InstrumentMems( } else { val name = namespace.newName(s"${m.name}_${w1}_${w2}_wwc") val bothEn = Utils.and(en1, en2) - val sameAddr = Utils.eq(memPortField(m, w1, "addr"), memPortField(m, w2, "addr")) + val sameAddr = Utils.eq(readInput(m, w1, "addr"), readInput(m, w2, "addr")) // we need a wire because this condition might be used in a random statement val wire = DefWire(m.info, name, BoolType) declarations += wire @@ -355,25 +344,24 @@ private class InstrumentMems( // create the source of randomness val name = namespace.newName(s"${m.name}_${w1}_wwc_data") - val random = DefRandom(m.info, name, m.dataType, Some(memPortField(m, w1, "clk")), hasConflict) + val random = DefRandom(m.info, name, m.dataType, Some(readInput(m, w1, "clk")), hasConflict) declarations.append(random) - // replace the old data input - val dataWire = disconnectInput(m.info, memPortField(m, w1, "data")) - declarations += dataWire + // generate new data input - val data = Utils.mux(hasConflict, Reference(random), Reference(dataWire)) + val data = Utils.mux(hasConflict, Reference(random), readInput(m, w1, "data")) newStmts.append(Connect(m.info, memPortField(m, w1, "data"), data)) + doDisconnect.add(memPortField(m, w1, "data").serialize) } // connect data enable signals - val maskIsOne = isTrue(connects(memPortField(m, w1, "mask").serialize)) + val maskIsOne = isTrue(readInput(m, w1, "mask")) if (!maskIsOne) { newStmts.append(Connect(m.info, memPortField(m, w1, "mask"), Utils.True())) + doDisconnect.add(memPortField(m, w1, "mask").serialize) } newStmts.append(Connect(m.info, memPortField(m, w1, "en"), en)) + doDisconnect.add(memPortField(m, w1, "en").serialize) } - - declarations.toList } /** check whether two signals can be proven to be mutually exclusive */ @@ -383,27 +371,43 @@ private class InstrumentMems( proofOfMutualExclusion.nonEmpty } - /** replace a memory port with a wire */ - private def disconnectInput(info: Info, signal: RefLikeExpression): DefWire = { - // disconnect the old value - doDisconnect.add(signal.serialize) - - // if the old value is a literal, we just replace all references to it with this literal - val oldValue = connects(signal.serialize) - if (isLiteral(oldValue)) { - println("TODO: better code for literal") - } + /** memory inputs my not be read, only assigned to, thus we might need to add a wire to make them accessible */ + private def readInput( + info: Info, + signal: RefLikeExpression + )( + implicit cache: mutable.HashMap[String, Expression], + declarations: mutable.ListBuffer[Statement] + ): Expression = + cache.getOrElseUpdate( + signal.serialize, { + // if it is a literal, we just return it + val value = connects(signal.serialize) + if (isLiteral(value)) { + value + } else { + // otherwise we make a wire that refelect the value + val wire = DefWire(info, copyName(signal), signal.tpe) + declarations += wire - // create a new wire and replace all references to the original port with this wire - val wire = DefWire(info, copyName(signal), signal.tpe) - exprReplacements(signal.serialize) = Reference(wire) - // connect the old expression to the new wire - val con = Connect(info, Reference(wire), connects(signal.serialize)) - newStmts.append(con) + // connect the old expression to the new wire + val con = Connect(info, Reference(wire), value) + newStmts.append(con) - // the wire definition should end up right after the memory definition - wire - } + // use a reference to this new wire + Reference(wire) + } + } + ) + private def readInput( + m: DefMemory, + port: String, + field: String + )( + implicit cache: mutable.HashMap[String, Expression], + declarations: mutable.ListBuffer[Statement] + ): Expression = + readInput(m.info, memPortField(m, port, field)) private def copyName(ref: RefLikeExpression): String = namespace.newName(ref.serialize.replace('.', '_')) diff --git a/src/test/scala/firrtl/backends/experimental/smt/end2end/MemorySpec.scala b/src/test/scala/firrtl/backends/experimental/smt/end2end/MemorySpec.scala index e489db7d5e..2a0276e1a9 100644 --- a/src/test/scala/firrtl/backends/experimental/smt/end2end/MemorySpec.scala +++ b/src/test/scala/firrtl/backends/experimental/smt/end2end/MemorySpec.scala @@ -195,4 +195,46 @@ class MemorySpec extends EndToEndSMTBaseSpec { "memory with two write ports" should "can have collisions when enables are unconstrained" taggedAs (RequiresZ3) in { test(collisionTest("UInt(1)"), MCFail(1), kmax = 1) } + + private def readEnableSrc(pred: String, num: Int) = + s""" + |circuit ReadEnableTest$num: + | module ReadEnableTest$num: + | input c : Clock + | input preset: AsyncReset + | + | reg first: UInt<1>, c with: (reset => (preset, UInt(1))) + | first <= UInt(0) + | + | reg even: UInt<1>, c with: (reset => (preset, UInt(0))) + | node odd = not(even) + | even <= not(even) + | + | mem m: + | data-type => UInt<8> + | depth => 4 + | reader => r + | read-latency => 1 + | write-latency => 1 + | read-under-write => undefined + | + | m.r.clk <= c + | m.r.addr <= UInt(0) + | ; the read port is enabled in even cycles + | m.r.en <= even + | + | assert(c, $pred, not(first), "") + |""".stripMargin + + "a memory with read enable" should "supply valid data one cycle after en=1" in { + val init = Seq(MemoryScalarInitAnnotation(CircuitTarget(s"ReadEnableTest1").module(s"ReadEnableTest1").ref("m"), 0)) + // the read port is enabled on even cycles, so on odd cycles we should reliably get zeros + test(readEnableSrc("or(not(odd), eq(m.r.data, UInt(0)))", 1), MCSuccess, kmax = 3, annos = init) + } + + "a memory with read enable" should "supply invalid data one cycle after en=0" in { + val init = Seq(MemoryScalarInitAnnotation(CircuitTarget(s"ReadEnableTest2").module(s"ReadEnableTest2").ref("m"), 0)) + // the read port is disabled on odd cycles, so on even cycles we should *NOT* reliably get zeros + test(readEnableSrc("or(not(even), eq(m.r.data, UInt(0)))", 2), MCFail(1), kmax = 1, annos = init) + } } diff --git a/src/test/scala/firrtl/backends/experimental/smt/random/UndefinedMemoryBehaviorSpec.scala b/src/test/scala/firrtl/backends/experimental/smt/random/UndefinedMemoryBehaviorSpec.scala index c6f89b933d..f8f889ac33 100644 --- a/src/test/scala/firrtl/backends/experimental/smt/random/UndefinedMemoryBehaviorSpec.scala +++ b/src/test/scala/firrtl/backends/experimental/smt/random/UndefinedMemoryBehaviorSpec.scala @@ -13,10 +13,10 @@ class UndefinedMemoryBehaviorSpec extends LeanTransformSpec(Seq(Dependency(Undef val result = circuit.serialize.split('\n').map(_.trim) // a random value should be declared for the data written on a write-write conflict - assert(result.contains("rand m_a_wwc_data : UInt<32>, m.a.clk when m_a_b_wwc")) + assert(result.contains("rand m_a_wwc_data : UInt<32>, m_a_clk when m_a_b_wwc")) // a write-write conflict occurs when both ports are enabled and the addresses match - assert(result.contains("m_a_b_wwc <= and(and(m_a_en, m_b_en), eq(m.a.addr, m.b.addr))")) + assert(result.contains("m_a_b_wwc <= and(and(m_a_en, m_b_en), eq(m_a_addr, m_b_addr))")) // the data of read port a depends on whether there is a write-write conflict assert(result.contains("m.a.data <= mux(m_a_b_wwc, m_a_wwc_data, m_a_data)")) @@ -35,14 +35,14 @@ class UndefinedMemoryBehaviorSpec extends LeanTransformSpec(Seq(Dependency(Undef assert(result.contains("node m_a_wwc_active = or(m_a_b_wwc, m_a_c_wwc)")) // a random value should be declared for the data written on a write-write conflict - assert(result.contains("rand m_a_wwc_data : UInt<32>, m.a.clk when m_a_wwc_active")) - assert(result.contains("rand m_b_wwc_data : UInt<32>, m.b.clk when m_b_c_wwc")) + assert(result.contains("rand m_a_wwc_data : UInt<32>, m_a_clk when m_a_wwc_active")) + assert(result.contains("rand m_b_wwc_data : UInt<32>, m_b_clk when m_b_c_wwc")) // a write-write conflict occurs when both ports are enabled and the addresses match Seq(("a", "b"), ("a", "c"), ("b", "c")).foreach { case (w1, w2) => assert( - result.contains(s"m_${w1}_${w2}_wwc <= and(and(m_${w1}_en, m_${w2}_en), eq(m.${w1}.addr, m.${w2}.addr))") + result.contains(s"m_${w1}_${w2}_wwc <= and(and(m_${w1}_en, m_${w2}_en), eq(m_${w1}_addr, m_${w2}_addr))") ) } @@ -67,7 +67,7 @@ class UndefinedMemoryBehaviorSpec extends LeanTransformSpec(Seq(Dependency(Undef val result = circuit.serialize.split('\n').map(_.trim) // we should not compute the conflict between a and c since it is impossible - assert(!result.contains("node m_a_c_wwc = and(and(m_a_en, m_c_en), eq(m.a.addr, m.c.addr))")) + assert(!result.contains("node m_a_c_wwc = and(and(m_a_en, m_c_en), eq(m_a_addr, m_c_addr))")) // the enable of port b depends on whether there is a conflict with a assert(result.contains("m.b.en <= and(m_b_en, not(m_a_b_wwc))")) @@ -91,7 +91,7 @@ class UndefinedMemoryBehaviorSpec extends LeanTransformSpec(Seq(Dependency(Undef assert( result.contains( - """assert(m.a.clk, or(not(and(m.a.en, m.a.mask)), geq(UInt<5>("h1e"), m.a.addr)), UInt<1>("h1"), "out of bounds read")""" + """assert(m_a_clk, geq(UInt<5>("h1e"), m_a_addr), UInt<1>("h1"), "out of bounds read")""" ) ) } @@ -102,10 +102,10 @@ class UndefinedMemoryBehaviorSpec extends LeanTransformSpec(Seq(Dependency(Undef val result = circuit.serialize.split('\n').map(_.trim) // an out of bounds read happens if the depth is not greater or equal to the address - assert(result.contains("node m_r_oob = not(geq(UInt<5>(\"h1e\"), m.r.addr))")) + assert(result.contains("node m_r_oob = not(geq(UInt<5>(\"h1e\"), m_r_addr))")) // the source of randomness needs to be triggered when there is an out of bounds read - assert(result.contains("rand m_r_rand_data : UInt<32>, m.r.clk when m_r_oob")) + assert(result.contains("rand m_r_rand_data : UInt<32>, m_r_clk when m_r_oob")) // the data is random when there is an oob assert(result.contains("m_r_data <= mux(m_r_oob, m_r_rand_data, m.r.data)")) @@ -118,10 +118,10 @@ class UndefinedMemoryBehaviorSpec extends LeanTransformSpec(Seq(Dependency(Undef val result = circuit.serialize.split('\n').map(_.trim) // the memory is disabled when it is not enabled - assert(result.contains("node m_r_disabled = not(m.r.en)")) + assert(result.contains("node m_r_disabled = not(m_r_en)")) // the source of randomness needs to be triggered when there is an read while the port is disabled - assert(result.contains("rand m_r_rand_data : UInt<32>, m.r.clk when m_r_disabled")) + assert(result.contains("rand m_r_rand_data : UInt<32>, m_r_clk when m_r_disabled")) // the data is random when there is an un-enabled read assert(result.contains("m_r_data <= mux(m_r_disabled, m_r_rand_data, m.r.data)")) @@ -134,16 +134,16 @@ class UndefinedMemoryBehaviorSpec extends LeanTransformSpec(Seq(Dependency(Undef val result = circuit.serialize.split('\n').map(_.trim) // the memory is disabled when it is not enabled - assert(result.contains("node m_r_disabled = not(m.r.en)")) + assert(result.contains("node m_r_disabled = not(m_r_en)")) // an out of bounds read happens if the depth is not greater or equal to the address and the memory is enabled - assert(result.contains("node m_r_oob = and(m.r.en, not(geq(UInt<5>(\"h1e\"), m.r.addr)))")) + assert(result.contains("node m_r_oob = and(m_r_en, not(geq(UInt<5>(\"h1e\"), m_r_addr)))")) // the two possible issues are combined into a single signal assert(result.contains("node m_r_do_rand = or(m_r_disabled, m_r_oob)")) // the source of randomness needs to be triggered when either issue occurs - assert(result.contains("rand m_r_rand_data : UInt<32>, m.r.clk when m_r_do_rand")) + assert(result.contains("rand m_r_rand_data : UInt<32>, m_r_clk when m_r_do_rand")) // the data is random when either issue occurs assert(result.contains("m_r_data <= mux(m_r_do_rand, m_r_rand_data, m.r.data)")) @@ -159,7 +159,7 @@ class UndefinedMemoryBehaviorSpec extends LeanTransformSpec(Seq(Dependency(Undef assert(result.contains("m_r_do_rand_r1 <= m_r_do_rand")) // the source of randomness needs to be triggered by the pipeline register - assert(result.contains("rand m_r_rand_data : UInt<32>, m.r.clk when m_r_do_rand_r1")) + assert(result.contains("rand m_r_rand_data : UInt<32>, m_r_clk when m_r_do_rand_r1")) // the data is random when the pipeline register is 1 assert(result.contains("m_r_data <= mux(m_r_do_rand_r1, m_r_rand_data, m.r.data)")) @@ -171,13 +171,13 @@ class UndefinedMemoryBehaviorSpec extends LeanTransformSpec(Seq(Dependency(Undef val result = circuit.serialize.split('\n').map(_.trim) // detect read/write conflicts - assert(result.contains("m_r_a_rwc <= and(and(m.r.en, and(m.a.en, m.a.mask)), eq(m.r.addr, m.a.addr))")) + assert(result.contains("m_r_a_rwc <= eq(m_r_addr, m_a_addr)")) // delay the signal assert(result.contains("m_r_do_rand_r1 <= m_r_rwc")) // randomize the data - assert(result.contains("rand m_r_rand_data : UInt<32>, m.r.clk when m_r_do_rand_r1")) + assert(result.contains("rand m_r_rand_data : UInt<32>, m_r_clk when m_r_do_rand_r1")) assert(result.contains("m_r_data <= mux(m_r_do_rand_r1, m_r_rand_data, m.r.data)")) } } From 8a4c156f401c8bfab5f2d595c32c20534f0722d7 Mon Sep 17 00:00:00 2001 From: Kevin Laeufer Date: Mon, 8 Mar 2021 17:37:35 -0800 Subject: [PATCH 36/88] SMT Backend: model Invalid and Division by Zero with DefRandom nodes (#2104) This finally removes all randomization code from the transition system conversion and into a separate pass using DefRandom nodes. --- .../smt/FirrtlExpressionSemantics.scala | 17 +-- .../smt/FirrtlToTransitionSystem.scala | 49 +++---- .../smt/random/InvalidToRandomPass.scala | 125 ++++++++++++++++++ .../smt/random/InvalidToRandomSpec.scala | 56 ++++++++ 4 files changed, 201 insertions(+), 46 deletions(-) create mode 100644 src/main/scala/firrtl/backends/experimental/smt/random/InvalidToRandomPass.scala create mode 100644 src/test/scala/firrtl/backends/experimental/smt/random/InvalidToRandomSpec.scala diff --git a/src/main/scala/firrtl/backends/experimental/smt/FirrtlExpressionSemantics.scala b/src/main/scala/firrtl/backends/experimental/smt/FirrtlExpressionSemantics.scala index d85fbfe5fb..13e0c31295 100644 --- a/src/main/scala/firrtl/backends/experimental/smt/FirrtlExpressionSemantics.scala +++ b/src/main/scala/firrtl/backends/experimental/smt/FirrtlExpressionSemantics.scala @@ -9,8 +9,6 @@ import firrtl.passes.CheckWidths.WidthTooBig private trait TranslationContext { def getReference(name: String, tpe: ir.Type): BVExpr = BVSymbol(name, FirrtlExpressionSemantics.getWidth(tpe)) - def getRandom(tpe: ir.Type): BVExpr = getRandom(FirrtlExpressionSemantics.getWidth(tpe)) - def getRandom(width: Int): BVExpr } private object FirrtlExpressionSemantics { @@ -34,9 +32,8 @@ private object FirrtlExpressionSemantics { case ir.Mux(cond, tval, fval, _) => val width = List(tval, fval).map(getWidth).max BVIte(toSMT(cond), toSMT(tval, width), toSMT(fval, width)) - case ir.ValidIf(cond, value, tpe) => - val tru = toSMT(value) - BVIte(toSMT(cond), tru, ctx.getRandom(tpe)) + case v: ir.ValidIf => + throw new RuntimeException(s"Unsupported expression: ValidIf ${v.serialize}") } assert( eSMT.width == getWidth(e), @@ -81,15 +78,7 @@ private object FirrtlExpressionSemantics { val (width, op) = if (isSigned(num)) { (getWidth(num) + 1, Op.SignedDiv) } else { (getWidth(num), Op.UnsignedDiv) } - // "The result of a division where den is zero is undefined." - val undef = ctx.getRandom(width) - val denSMT = toSMT(den) - val denIsZero = BVEqual(denSMT, BVLiteral(0, denSMT.width)) - val numByDen = BVOp(op, toSMT(num, width), forceWidth(denSMT, isSigned(den), width)) - BVIte(denIsZero, undef, numByDen) - case (PrimOps.Div, Seq(num, den), _) if isSigned(num) => - val width = getWidth(num) + 1 - BVOp(Op.SignedDiv, toSMT(num, width), toSMT(den, width)) + BVOp(op, toSMT(num, width), forceWidth(toSMT(den), isSigned(den), width)) case (PrimOps.Rem, Seq(num, den), _) => val op = if (isSigned(num)) Op.SignedRem else Op.UnsignedRem val width = args.map(getWidth).max diff --git a/src/main/scala/firrtl/backends/experimental/smt/FirrtlToTransitionSystem.scala b/src/main/scala/firrtl/backends/experimental/smt/FirrtlToTransitionSystem.scala index d3a1ed6840..fea92c7587 100644 --- a/src/main/scala/firrtl/backends/experimental/smt/FirrtlToTransitionSystem.scala +++ b/src/main/scala/firrtl/backends/experimental/smt/FirrtlToTransitionSystem.scala @@ -62,7 +62,7 @@ object FirrtlToTransitionSystem extends Transform with DependencyAPIMigration { // TODO: We also would like to run some optimization passes, but RemoveValidIf won't allow us to model DontCare // precisely and PadWidths emits ill-typed firrtl. override def prerequisites: Seq[Dependency[Transform]] = Forms.LowForm ++ - Seq(Dependency(UndefinedMemoryBehaviorPass), Dependency(VerilogMemDelays)) + Seq(Dependency(InvalidToRandomPass), Dependency(UndefinedMemoryBehaviorPass), Dependency(VerilogMemDelays)) override def invalidates(a: Transform): Boolean = false // since this pass only runs on the main module, inlining needs to happen before override def optionalPrerequisites: Seq[TransformDependency] = Seq(Dependency[firrtl.passes.InlineInstances]) @@ -147,7 +147,7 @@ private class ModuleToTransitionSystem extends LazyLogging { uninterpreted: Map[String, UninterpretedModuleAnnotation] = Map() ): TransitionSystem = { // first pass over the module to convert expressions; discover state and I/O - val scan = new ModuleScanner(makeRandom, uninterpreted) + val scan = new ModuleScanner(uninterpreted) m.foreachPort(scan.onPort) m.foreachStmt(scan.onStatement) @@ -200,8 +200,8 @@ private class ModuleToTransitionSystem extends LazyLogging { } } - // inputs are original module inputs and any "random" signal we need for modelling - val inputs = scan.inputs ++ randoms.values + // inputs are original module inputs and any DefRandom signal + val inputs = scan.inputs // module info to the comment header val header = serializeInfo(m.info).map(InfoPrefix + _).toArray @@ -350,21 +350,10 @@ private class ModuleToTransitionSystem extends LazyLogging { if (infos.isEmpty) { None } else { Some(infos.map(_.escaped).mkString(InfoSeparator)) } } - - private[firrtl] val randoms = mutable.LinkedHashMap[String, BVSymbol]() - private def makeRandom(baseName: String, width: Int): BVExpr = { - // TODO: actually ensure that there cannot be any name clashes with other identifiers - val suffixes = Iterator(baseName) ++ (0 until 200).map(ii => baseName + "_" + ii) - val name = suffixes.map(s => "RANDOM." + s).find(!randoms.contains(_)).get - val sym = BVSymbol(name, width) - randoms(name) = sym - sym - } } // performas a first pass over the module collecting all connections, wires, registers, input and outputs private class ModuleScanner( - makeRandom: (String, Int) => BVExpr, uninterpreted: Map[String, UninterpretedModuleAnnotation]) extends LazyLogging { import FirrtlExpressionSemantics.getWidth @@ -429,7 +418,7 @@ private class ModuleScanner( if (!isClock(expr.tpe)) { insertDummyAssignsForUnusedOutputs(expr) infos.append(name -> info) - val e = onExpression(expr, name) + val e = onExpression(expr) nodes.append(name) connects.append((name, e)) } @@ -439,8 +428,8 @@ private class ModuleScanner( insertDummyAssignsForUnusedOutputs(init) infos.append(name -> info) val width = getWidth(tpe) - val resetExpr = onExpression(reset, 1, name + "_reset") - val initExpr = onExpression(init, width, name + "_init") + val resetExpr = onExpression(reset, 1) + val initExpr = onExpression(init, width) registers.append((name, width, resetExpr, initExpr)) case m: ir.DefMemory => namespace.newName(m.name) @@ -456,13 +445,11 @@ private class ModuleScanner( val name = loc.serialize insertDummyAssignsForUnusedOutputs(expr) infos.append(name -> info) - connects.append((name, onExpression(expr, getWidth(loc.tpe), name))) + connects.append((name, onExpression(expr, getWidth(loc.tpe)))) } - case ir.IsInvalid(info, loc) => + case i @ ir.IsInvalid(info, loc) => if (!isGroundType(loc.tpe)) error("All connects should have been lowered to ground type!") - val name = loc.serialize - infos.append(name -> info) - connects.append((name, makeRandom(name + "_INVALID", getWidth(loc.tpe)))) + throw new UnsupportedFeatureException(s"IsInvalid statements are not supported: ${i.serialize}") case ir.DefInstance(info, name, module, tpe) => onInstance(info, name, module, tpe) case s @ ir.Verification(op, info, _, pred, en, msg) => if (op == ir.Formal.Cover) { @@ -471,8 +458,8 @@ private class ModuleScanner( insertDummyAssignsForUnusedOutputs(pred) insertDummyAssignsForUnusedOutputs(en) val name = namespace.newName(msgToName(op.toString, msg.string)) - val predicate = onExpression(pred, name + "_predicate") - val enabled = onExpression(en, name + "_enabled") + val predicate = onExpression(pred) + val enabled = onExpression(en) val e = BVImplies(enabled, predicate) infos.append(name -> info) connects.append(name -> e) @@ -596,16 +583,14 @@ private class ModuleScanner( case other => other.foreachExpr(findUnusedOutputUse) } - private case class Context(baseName: String) extends TranslationContext { - override def getRandom(width: Int): BVExpr = makeRandom(baseName, width) - } + private case class Context() extends TranslationContext {} - private def onExpression(e: ir.Expression, width: Int, randomPrefix: String): BVExpr = { - implicit val ctx: TranslationContext = Context(randomPrefix) + private def onExpression(e: ir.Expression, width: Int): BVExpr = { + implicit val ctx: TranslationContext = Context() FirrtlExpressionSemantics.toSMT(e, width, allowNarrow = false) } - private def onExpression(e: ir.Expression, randomPrefix: String): BVExpr = { - implicit val ctx: TranslationContext = Context(randomPrefix) + private def onExpression(e: ir.Expression): BVExpr = { + implicit val ctx: TranslationContext = Context() FirrtlExpressionSemantics.toSMT(e) } diff --git a/src/main/scala/firrtl/backends/experimental/smt/random/InvalidToRandomPass.scala b/src/main/scala/firrtl/backends/experimental/smt/random/InvalidToRandomPass.scala new file mode 100644 index 0000000000..c7eaad74b0 --- /dev/null +++ b/src/main/scala/firrtl/backends/experimental/smt/random/InvalidToRandomPass.scala @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: Apache-2.0 + +package firrtl.backends.experimental.smt.random + +import firrtl._ +import firrtl.annotations.NoTargetAnnotation +import firrtl.ir._ +import firrtl.passes._ +import firrtl.options.Dependency +import firrtl.stage.Forms +import firrtl.transforms.RemoveWires + +import scala.collection.mutable + +/** Chooses how to model explicit and implicit invalid values in the circuit */ +case class InvalidToRandomOptions( + randomizeInvalidSignals: Boolean = true, + randomizeDivisionByZero: Boolean = true) + extends NoTargetAnnotation + +/** Replaces all explicit and implicit "invalid" values with random values. + * Explicit invalids are: + * - signal is invalid + * - signal <= valid(..., expr) + * Implicit invalids are: + * - a / b when eq(b, 0) + */ +object InvalidToRandomPass extends Transform with DependencyAPIMigration { + override def prerequisites = Forms.LowForm + // once ValidIf has been removed, we can no longer detect and randomize them + override def optionalPrerequisiteOf = Seq(Dependency(RemoveValidIf)) + override def invalidates(a: Transform) = a match { + // this pass might destroy SSA form, as we add a wire for the data field of every read port + case _: RemoveWires => true + // TODO: should we add some optimization passes here? we could be generating some dead code. + case _ => false + } + + override protected def execute(state: CircuitState): CircuitState = { + val opts = state.annotations.collect { case o: InvalidToRandomOptions => o } + require(opts.size < 2, s"Multiple options: $opts") + val opt = opts.headOption.getOrElse(InvalidToRandomOptions()) + + // quick exit if we just want to skip this pass + if (!opt.randomizeDivisionByZero && !opt.randomizeInvalidSignals) { + state + } else { + val c = state.circuit.mapModule(onModule(_, opt)) + state.copy(circuit = c) + } + } + + private def onModule(m: DefModule, opt: InvalidToRandomOptions): DefModule = m match { + case d: DescribedMod => + throw new RuntimeException(s"CompilerError: Unexpected internal node: ${d.serialize}") + case e: ExtModule => e + case mod: Module => + val namespace = Namespace(mod) + mod.mapStmt(onStmt(namespace, opt, _)) + } + + private def onStmt(namespace: Namespace, opt: InvalidToRandomOptions, s: Statement): Statement = s match { + case IsInvalid(info, loc: RefLikeExpression) if opt.randomizeInvalidSignals => + val name = namespace.newName(loc.serialize.replace('.', '_') + "_invalid") + val rand = DefRandom(info, name, loc.tpe, None) + Block(List(rand, Connect(info, loc, Reference(rand)))) + case other => + val info = other match { + case h: HasInfo => h.info + case _ => NoInfo + } + val prefix = other match { + case c: Connect => c.loc.serialize.replace('.', '_') + case h: HasName => h.name + case _ => "" + } + val ctx = ExprCtx(namespace, opt, prefix, info, mutable.ListBuffer[Statement]()) + val stmt = other.mapExpr(onExpr(ctx, _)).mapStmt(onStmt(namespace, opt, _)) + if (ctx.rands.isEmpty) { stmt } + else { Block(Block(ctx.rands.toList), stmt) } + } + + private case class ExprCtx( + namespace: Namespace, + opt: InvalidToRandomOptions, + prefix: String, + info: Info, + rands: mutable.ListBuffer[Statement]) + + private def onExpr(ctx: ExprCtx, e: Expression): Expression = + e.mapExpr(onExpr(ctx, _)) match { + case ValidIf(_, value, tpe) if tpe == ClockType => + // we currently assume that clocks are always valid + // TODO: is that a good assumption? + value + case ValidIf(cond, value, tpe) if ctx.opt.randomizeInvalidSignals => + makeRand(ctx, cond, tpe, value, invert = true) + case d @ DoPrim(PrimOps.Div, Seq(_, den), _, tpe) if ctx.opt.randomizeDivisionByZero => + val denIsZero = Utils.eq(den, Utils.getGroundZero(den.tpe.asInstanceOf[GroundType])) + makeRand(ctx, denIsZero, tpe, d, invert = false) + case other => other + } + + private def makeRand( + ctx: ExprCtx, + cond: Expression, + tpe: Type, + value: Expression, + invert: Boolean + ): Expression = { + val name = ctx.namespace.newName(if (ctx.prefix.isEmpty) "invalid" else ctx.prefix + "_invalid") + // create a condition node if the condition isn't a reference already + val condRef = cond match { + case r: RefLikeExpression => if (invert) Utils.not(r) else r + case other => + val cond = if (invert) Utils.not(other) else other + val condNode = DefNode(ctx.info, ctx.namespace.newName(name + "_cond"), cond) + ctx.rands.append(condNode) + Reference(condNode) + } + val rand = DefRandom(ctx.info, name, tpe, None, condRef) + ctx.rands.append(rand) + Utils.mux(condRef, Reference(rand), value) + } +} diff --git a/src/test/scala/firrtl/backends/experimental/smt/random/InvalidToRandomSpec.scala b/src/test/scala/firrtl/backends/experimental/smt/random/InvalidToRandomSpec.scala new file mode 100644 index 0000000000..8f17a847ed --- /dev/null +++ b/src/test/scala/firrtl/backends/experimental/smt/random/InvalidToRandomSpec.scala @@ -0,0 +1,56 @@ +package firrtl.backends.experimental.smt.random + +import firrtl.options.Dependency +import firrtl.testutils.LeanTransformSpec + +class InvalidToRandomSpec extends LeanTransformSpec(Seq(Dependency(InvalidToRandomPass))) { + behavior.of("InvalidToRandomPass") + + val src1 = + s""" + |circuit Test: + | module Test: + | input a : UInt<2> + | output o : UInt<8> + | output o2 : UInt<8> + | output o3 : UInt<8> + | + | o is invalid + | + | when eq(a, UInt(3)): + | o <= UInt(5) + | + | o2 is invalid + | node o2_valid = eq(a, UInt(2)) + | when o2_valid: + | o2 <= UInt(7) + | + | o3 is invalid + | o3 <= UInt(3) + |""".stripMargin + + it should "model invalid signals as random" in { + + val circuit = compile(src1, List()).circuit + //println(circuit.serialize) + val result = circuit.serialize.split('\n').map(_.trim) + + // the condition should end up as a new node if it wasn't a reference already + assert(result.contains("node _GEN_0_invalid_cond = not(eq(a, UInt<2>(\"h3\")))")) + assert(result.contains("node o2_valid = eq(a, UInt<2>(\"h2\"))")) + + // every invalid results in a random statement + assert(result.contains("rand _GEN_0_invalid : UInt<3> when _GEN_0_invalid_cond")) + assert(result.contains("rand _GEN_1_invalid : UInt<3> when not(o2_valid)")) + + // the random value is conditionally assigned + assert(result.contains("node _GEN_0 = mux(_GEN_0_invalid_cond, _GEN_0_invalid, UInt<3>(\"h5\"))")) + assert(result.contains("node _GEN_1 = mux(not(o2_valid), _GEN_1_invalid, UInt<3>(\"h7\"))")) + + // expressions that are trivially valid do not get randomized + assert(result.contains("o3 <= UInt<2>(\"h3\")")) + val defRandCount = result.count(_.contains("rand ")) + assert(defRandCount == 2) + } + +} From efdefde2a5fa13de8faa8c141f852391909225df Mon Sep 17 00:00:00 2001 From: Carlos Eduardo Date: Tue, 9 Mar 2021 15:49:37 -0300 Subject: [PATCH 37/88] Create annotation to allow inline readmem in Verilog (#2107) This PR adds a new annotation allowing inline loading for memory files in Verilog code. --- .gitignore | 1 + src/main/scala/firrtl/EmissionOption.scala | 3 +++ .../annotations/MemoryInitAnnotation.scala | 21 ++++++++++++++++++- .../backends/verilog/VerilogEmitter.scala | 12 ++++++++++- .../scala/firrtlTests/MemoryInitSpec.scala | 17 +++++++++++++++ 5 files changed, 52 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 8b3d51a642..dd2f998ba5 100644 --- a/.gitignore +++ b/.gitignore @@ -66,3 +66,4 @@ project/metals.sbt *~ *#*# +.vscode diff --git a/src/main/scala/firrtl/EmissionOption.scala b/src/main/scala/firrtl/EmissionOption.scala index 074d9c2886..90eb585186 100644 --- a/src/main/scala/firrtl/EmissionOption.scala +++ b/src/main/scala/firrtl/EmissionOption.scala @@ -2,6 +2,8 @@ package firrtl +import firrtl.annotations.MemoryLoadFileType + /** * Base type for emission customization options * NOTE: all the following traits must be mixed with SingleTargetAnnotation[T <: Named] @@ -18,6 +20,7 @@ sealed trait MemoryInitValue case object MemoryRandomInit extends MemoryInitValue case class MemoryScalarInit(value: BigInt) extends MemoryInitValue case class MemoryArrayInit(values: Seq[BigInt]) extends MemoryInitValue +case class MemoryFileInlineInit(filename: String, hexOrBinary: MemoryLoadFileType.FileType) extends MemoryInitValue /** default Emitter behavior for memories */ case object MemoryEmissionOptionDefault extends MemoryEmissionOption diff --git a/src/main/scala/firrtl/annotations/MemoryInitAnnotation.scala b/src/main/scala/firrtl/annotations/MemoryInitAnnotation.scala index 44656e0d08..62dc96f485 100644 --- a/src/main/scala/firrtl/annotations/MemoryInitAnnotation.scala +++ b/src/main/scala/firrtl/annotations/MemoryInitAnnotation.scala @@ -2,7 +2,14 @@ package firrtl.annotations -import firrtl.{MemoryArrayInit, MemoryEmissionOption, MemoryInitValue, MemoryRandomInit, MemoryScalarInit} +import firrtl.{ + MemoryArrayInit, + MemoryEmissionOption, + MemoryFileInlineInit, + MemoryInitValue, + MemoryRandomInit, + MemoryScalarInit +} /** * Represents the initial value of the annotated memory. @@ -33,3 +40,15 @@ case class MemoryArrayInitAnnotation(target: ReferenceTarget, values: Seq[BigInt override def initValue: MemoryInitValue = MemoryArrayInit(values) override def isRandomInit: Boolean = false } + +/** Initialize the `target` memory with inline readmem[hb] statement. */ +case class MemoryFileInlineAnnotation( + target: ReferenceTarget, + filename: String, + hexOrBinary: MemoryLoadFileType.FileType = MemoryLoadFileType.Hex) + extends MemoryInitAnnotation { + require(filename.trim.nonEmpty, "empty filename not allowed in MemoryFileInlineAnnotation") + override def duplicate(n: ReferenceTarget): Annotation = copy(n) + override def initValue: MemoryInitValue = MemoryFileInlineInit(filename, hexOrBinary) + override def isRandomInit: Boolean = false +} diff --git a/src/main/scala/firrtl/backends/verilog/VerilogEmitter.scala b/src/main/scala/firrtl/backends/verilog/VerilogEmitter.scala index f1650ad741..bc4996df42 100644 --- a/src/main/scala/firrtl/backends/verilog/VerilogEmitter.scala +++ b/src/main/scala/firrtl/backends/verilog/VerilogEmitter.scala @@ -7,7 +7,7 @@ import firrtl.PrimOps._ import firrtl.Utils._ import firrtl.WrappedExpression._ import firrtl.traversals.Foreachers._ -import firrtl.annotations.{CircuitTarget, ReferenceTarget, SingleTargetAnnotation} +import firrtl.annotations.{CircuitTarget, MemoryLoadFileType, ReferenceTarget, SingleTargetAnnotation} import firrtl.passes.LowerTypes import firrtl.passes.MemPortUtils._ import firrtl.stage.TransformManager @@ -849,6 +849,16 @@ class VerilogEmitter extends SeqTransform with Emitter { rstring, ";" ) + case MemoryFileInlineInit(filename, hexOrBinary) => + val readmem = hexOrBinary match { + case MemoryLoadFileType.Binary => "$readmemb" + case MemoryLoadFileType.Hex => "$readmemh" + } + val inlineLoad = s""" + |initial begin + | $readmem("$filename", ${s.name}); + |end""".stripMargin + memoryInitials += Seq(inlineLoad) } } diff --git a/src/test/scala/firrtlTests/MemoryInitSpec.scala b/src/test/scala/firrtlTests/MemoryInitSpec.scala index 18f2b7ffda..a7c9966a3e 100644 --- a/src/test/scala/firrtlTests/MemoryInitSpec.scala +++ b/src/test/scala/firrtlTests/MemoryInitSpec.scala @@ -165,6 +165,23 @@ class MemInitSpec extends FirrtlFlatSpec { assert(annos == Seq(MemoryArrayInitAnnotation(mRef, largeSeq))) } + "MemoryFileInlineAnnotation" should "emit $readmemh for text.hex" in { + val annos = Seq(MemoryFileInlineAnnotation(mRef, filename = "text.hex")) + val result = compile(annos) + result should containLine("""$readmemh("text.hex", """ + mRef.name + """);""") + } + + "MemoryFileInlineAnnotation" should "emit $readmemb for text.bin" in { + val annos = Seq(MemoryFileInlineAnnotation(mRef, filename = "text.bin", hexOrBinary = MemoryLoadFileType.Binary)) + val result = compile(annos) + result should containLine("""$readmemb("text.bin", """ + mRef.name + """);""") + } + + "MemoryFileInlineAnnotation" should "fail with blank filename" in { + assertThrows[Exception] { + compile(Seq(MemoryFileInlineAnnotation(mRef, filename = ""))) + } + } } abstract class MemInitExecutionSpec(values: Seq[Int], init: ReferenceTarget => Annotation) From aa24fe3ece6edcd1c121d6aa6860b6de825bb381 Mon Sep 17 00:00:00 2001 From: Carlos Eduardo Date: Tue, 9 Mar 2021 20:23:27 -0300 Subject: [PATCH 38/88] Fix the readmem statements in nested block (#2109) --- src/main/scala/firrtl/backends/verilog/VerilogEmitter.scala | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/main/scala/firrtl/backends/verilog/VerilogEmitter.scala b/src/main/scala/firrtl/backends/verilog/VerilogEmitter.scala index bc4996df42..c7143f5f0b 100644 --- a/src/main/scala/firrtl/backends/verilog/VerilogEmitter.scala +++ b/src/main/scala/firrtl/backends/verilog/VerilogEmitter.scala @@ -854,11 +854,7 @@ class VerilogEmitter extends SeqTransform with Emitter { case MemoryLoadFileType.Binary => "$readmemb" case MemoryLoadFileType.Hex => "$readmemh" } - val inlineLoad = s""" - |initial begin - | $readmem("$filename", ${s.name}); - |end""".stripMargin - memoryInitials += Seq(inlineLoad) + memoryInitials += Seq(s"""$readmem("$filename", ${s.name});""") } } From ed1eb88d6ccdccd4b5802676cd8b69f5cc357e4f Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Wed, 10 Mar 2021 20:14:03 -0800 Subject: [PATCH 39/88] Fix CSESubAccesses for SubAccesses with flips (#2112) The flow of a LHS SubAccess node may still be SourceFlow if the type of the Vec element has a flip. Tweak the logic of CSESubAccesses to check every Expression flow while recursing instead of just the flow of the final SubAccess. Co-authored-by: Schuyler Eldridge Co-authored-by: Schuyler Eldridge --- .../firrtl/transforms/CSESubAccesses.scala | 6 +++- .../transforms/CSESubAccessesSpec.scala | 36 +++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/main/scala/firrtl/transforms/CSESubAccesses.scala b/src/main/scala/firrtl/transforms/CSESubAccesses.scala index 6ed3a5b5cf..6ca27a830a 100644 --- a/src/main/scala/firrtl/transforms/CSESubAccesses.scala +++ b/src/main/scala/firrtl/transforms/CSESubAccesses.scala @@ -21,7 +21,9 @@ object CSESubAccesses { val acc = new mutable.ListBuffer[(SubAccess, Info)] def onExpr(outer: Statement)(expr: Expression): Unit = { // Need postorder because we want to visit inner SubAccesses first - expr.foreach(onExpr(outer)) + // Stop recursing on any non-Source because flips can make the SubAccess a Source despite the + // overall Expression being a Sink + if (flow(expr) == SourceFlow) expr.foreach(onExpr(outer)) expr match { case e: SubAccess if flow(e) == SourceFlow => acc += e -> get_info(outer) case _ => // Do nothing @@ -42,6 +44,8 @@ object CSESubAccesses { // Replaces all right-hand side SubAccesses with References private def replaceOnSourceExpr(replace: SubAccess => Reference)(expr: Expression): Expression = expr match { + // Stop is we ever see a non-SourceFlow + case e if flow(e) != SourceFlow => e // Don't traverse children of SubAccess, just replace it // Nested SubAccesses are handled during creation of the nodes that the references refer to case acc: SubAccess if flow(acc) == SourceFlow => replace(acc) diff --git a/src/test/scala/firrtlTests/transforms/CSESubAccessesSpec.scala b/src/test/scala/firrtlTests/transforms/CSESubAccessesSpec.scala index 55ce07dfc5..f7d67026fd 100644 --- a/src/test/scala/firrtlTests/transforms/CSESubAccessesSpec.scala +++ b/src/test/scala/firrtlTests/transforms/CSESubAccessesSpec.scala @@ -184,4 +184,40 @@ class CSESubAccessesSpec extends FirrtlFlatSpec { compile(input) should be(parse(expected).serialize) } + it should "ignore flipped LHS SubAccesses" in { + val input = circuit( + s"""|input in : { foo : UInt<8> } + |input idx : UInt<1> + |input out : { flip foo : UInt<8> }[2] + |out[0].foo <= UInt(0) + |out[1].foo <= UInt(0) + |out[idx].foo <= in.foo""" + ) + val expected = circuit( + s"""|input in : { foo : UInt<8> } + |input idx : UInt<1> + |input out : { flip foo : UInt<8> }[2] + |out[0].foo <= UInt(0) + |out[1].foo <= UInt(0) + |out[idx].foo <= in.foo""" + ) + compile(input) should be(parse(expected).serialize) + } + + it should "ignore SubAccesses of bidirectional aggregates" in { + val input = circuit( + s"""|input in : { flip foo : UInt<8>, bar : UInt<8> } + |input idx : UInt<2> + |output out : { flip foo : UInt<8>, bar : UInt<8> }[4] + |out[idx] <= in""" + ) + val expected = circuit( + s"""|input in : { flip foo : UInt<8>, bar : UInt<8> } + |input idx : UInt<2> + |output out : { flip foo : UInt<8>, bar : UInt<8> }[4] + |out[idx] <= in""" + ) + compile(input) should be(parse(expected).serialize) + } + } From fd55c51bcef01c2b2919817aa33c67e5a0849d05 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Sat, 13 Mar 2021 18:38:20 -0800 Subject: [PATCH 40/88] Fix cat of zero-width SInt (#2116) Previously, concatenating two SInts where one is of zero-width would return the non-zero-width SInt. This is incorrect because the output of Cat should be of type UInt. Now the ZeroWidth transform will introduce a cast when removing a Cat when the argument type is non-UInt. --- src/main/scala/firrtl/passes/ZeroWidth.scala | 5 +++-- src/test/scala/firrtlTests/ZeroWidthTests.scala | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/main/scala/firrtl/passes/ZeroWidth.scala b/src/main/scala/firrtl/passes/ZeroWidth.scala index 60439ec14c..e918ff6341 100644 --- a/src/main/scala/firrtl/passes/ZeroWidth.scala +++ b/src/main/scala/firrtl/passes/ZeroWidth.scala @@ -135,8 +135,9 @@ object ZeroWidth extends Transform with DependencyAPIMigration { } } nonZeros match { - case Nil => UIntLiteral(ZERO, IntWidth(BigInt(1))) - case Seq(x) => x + case Nil => UIntLiteral(ZERO, IntWidth(BigInt(1))) + // We may have an SInt, Cat has type UInt so cast + case Seq(x) => castRhs(tpe, x) case seq => DoPrim(Cat, seq, consts, tpe).map(onExp) } case DoPrim(Andr, Seq(x), _, _) if (bitWidth(x.tpe) == 0) => UIntLiteral(1) // nothing false diff --git a/src/test/scala/firrtlTests/ZeroWidthTests.scala b/src/test/scala/firrtlTests/ZeroWidthTests.scala index 99ebbdd350..df63006517 100644 --- a/src/test/scala/firrtlTests/ZeroWidthTests.scala +++ b/src/test/scala/firrtlTests/ZeroWidthTests.scala @@ -220,6 +220,23 @@ class ZeroWidthTests extends FirrtlFlatSpec { | x <= UInt<1>(1)""".stripMargin (parse(exec(input))) should be(parse(check)) } + + "Cat of SInt with zero-width" should "keep type correctly" in { + val input = + """circuit Top : + | module Top : + | input x : SInt<0> + | input y : SInt<1> + | output z : UInt<1> + | z <= cat(y, x)""".stripMargin + val check = + """circuit Top : + | module Top : + | input y : SInt<1> + | output z : UInt<1> + | z <= asUInt(y)""".stripMargin + (parse(exec(input))) should be(parse(check)) + } } class ZeroWidthVerilog extends FirrtlFlatSpec { From 0eb7afd09d488507d776017c5df8f6ec56924927 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Sun, 14 Mar 2021 00:03:25 -0800 Subject: [PATCH 41/88] Fix width of constant propagation of SInt with zero (#2120) --- .../firrtl/transforms/ConstantPropagation.scala | 2 +- .../firrtlTests/ConstantPropagationTests.scala | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/main/scala/firrtl/transforms/ConstantPropagation.scala b/src/main/scala/firrtl/transforms/ConstantPropagation.scala index 0523082dd2..7106c699e7 100644 --- a/src/main/scala/firrtl/transforms/ConstantPropagation.scala +++ b/src/main/scala/firrtl/transforms/ConstantPropagation.scala @@ -230,7 +230,7 @@ class ConstantPropagation extends Transform with DependencyAPIMigration { } def simplify(e: Expression, lhs: Literal, rhs: Expression) = lhs match { case UIntLiteral(v, _) if v == BigInt(0) => rhs - case SIntLiteral(v, _) if v == BigInt(0) => asUInt(rhs, e.tpe) + case SIntLiteral(v, _) if v == BigInt(0) => asUInt(pad(rhs, e.tpe), e.tpe) case UIntLiteral(v, IntWidth(w)) if v == (BigInt(1) << bitWidth(rhs.tpe).toInt) - 1 => lhs case _ => e } diff --git a/src/test/scala/firrtlTests/ConstantPropagationTests.scala b/src/test/scala/firrtlTests/ConstantPropagationTests.scala index 28c1d823cf..60cf1b8be8 100644 --- a/src/test/scala/firrtlTests/ConstantPropagationTests.scala +++ b/src/test/scala/firrtlTests/ConstantPropagationTests.scala @@ -1638,6 +1638,20 @@ class ConstantPropagationEquivalenceSpec extends FirrtlFlatSpec { firrtlEquivalenceTest(input, transforms) } + // https://github.com/chipsalliance/firrtl/issues/2034 + "SInt OR with constant zero" should "have the correct widths" in { + val input = + s"""circuit WidthsOrSInt : + | module WidthsOrSInt : + | input in : SInt<1> + | input in2 : SInt<4> + | output out : UInt<8> + | output out2 : UInt<8> + | out <= or(in, SInt<8>(0)) + | out2 <= or(in2, SInt<8>(0))""".stripMargin + firrtlEquivalenceTest(input, transforms) + } + "addition by zero width wires" should "have the correct widths" in { val input = s"""circuit ZeroWidthAdd: From 94d1bee4c23bd3d8f99dae3ca431ffaa5dc1410d Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Tue, 16 Mar 2021 10:26:38 -0700 Subject: [PATCH 42/88] Fix issue where inlined cvt could cause crash (#2124) Due to inlining of Boolean expressions, the following circuit is handled directly by the VerilogEmitter: input a: UInt<4> input b: SInt<1> output o: UInt<5> o <= dshl(a, asUInt(cvt(b))) Priot to this change, this could crash due to mishandling of cvt in the logic to inject parentheses based on Verilog precedence rules. This is a corner case, but similar bugs would drop up if we open up the VerilogEmitter to more expression inlining. --- .../firrtl/backends/verilog/VerilogEmitter.scala | 13 ++++++++++--- .../InlineBooleanExpressionsSpec.scala | 16 ++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/main/scala/firrtl/backends/verilog/VerilogEmitter.scala b/src/main/scala/firrtl/backends/verilog/VerilogEmitter.scala index c7143f5f0b..33c6b9a8af 100644 --- a/src/main/scala/firrtl/backends/verilog/VerilogEmitter.scala +++ b/src/main/scala/firrtl/backends/verilog/VerilogEmitter.scala @@ -57,6 +57,13 @@ object VerilogEmitter { private def precedenceGt(op1: PrimOp, op2: PrimOp): Boolean = { precedenceMap(op1) < precedenceMap(op2) } + + /** Identifies PrimOps that never need parentheses + * + * These PrimOps emit either {..., a0, ...} or a0 so they never need parentheses + */ + private val neverParens: PrimOp => Boolean = + Set(Shl, Cat, Cvt, AsUInt, AsSInt, AsClock, AsAsyncReset, Pad) } class VerilogEmitter extends SeqTransform with Emitter { @@ -229,8 +236,7 @@ class VerilogEmitter extends SeqTransform with Emitter { // to ensure Verilog operations are signed. def op_stream(doprim: DoPrim): Seq[Any] = { def parenthesize(e: Expression, isFirst: Boolean): Any = doprim.op match { - // these PrimOps emit either {..., a0, ...} or a0 so they never need parentheses - case Shl | Cat | Cvt | AsUInt | AsSInt | AsClock | AsAsyncReset => e + case op if neverParens(op) => e case _ => e match { case e: DoPrim => @@ -247,7 +253,8 @@ class VerilogEmitter extends SeqTransform with Emitter { */ case other => val noParens = - precedenceGt(e.op, doprim.op) || + neverParens(e.op) || + precedenceGt(e.op, doprim.op) || (isFirst && precedenceEq(e.op, doprim.op) && !isUnaryOp(e.op)) if (noParens) other else Seq("(", other, ")") } diff --git a/src/test/scala/firrtlTests/InlineBooleanExpressionsSpec.scala b/src/test/scala/firrtlTests/InlineBooleanExpressionsSpec.scala index 02ac3cd0d1..e11c428161 100644 --- a/src/test/scala/firrtlTests/InlineBooleanExpressionsSpec.scala +++ b/src/test/scala/firrtlTests/InlineBooleanExpressionsSpec.scala @@ -392,6 +392,22 @@ class InlineBooleanExpressionsSpec extends FirrtlFlatSpec { firrtlEquivalenceTest(input, Seq(new InlineBooleanExpressions)) } + // https://github.com/chipsalliance/firrtl/issues/2035 + // This is interesting because other ways of trying to express this get split out by + // SplitExpressions and don't get inlined again + // If we were to inline more expressions (ie. not just boolean ones) the issue this represents + // would come up more often + it should "handle cvt nested inside of a dshl" in { + val input = + """circuit DshlCvt: + | module DshlCvt: + | input a: UInt<4> + | input b: SInt<1> + | output o: UInt + | o <= dshl(a, asUInt(cvt(b)))""".stripMargin + firrtlEquivalenceTest(input, Seq(new InlineBooleanExpressions)) + } + it should s"respect --${PrettyNoExprInlining.longOption}" in { val input = """circuit Top : From b274b319d4a4014c154f06bfc174beba461d6fce Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Thu, 18 Mar 2021 23:31:51 -0700 Subject: [PATCH 43/88] Ensure InlineCasts does not inline complex Expressions (#2130) Previously, InlineCasts could inline complex (ie. non-cast) Expressions into other complex Expressions. Now it will only inline so long as there no more than 1 complex Expression in the current nested Expression. Co-authored-by: Albert Magyar --- .../scala/firrtl/transforms/InlineCasts.scala | 41 ++++++----- .../scala/firrtl/testutils/FirrtlSpec.scala | 8 ++- .../scala/firrtlTests/InlineCastsSpec.scala | 68 ++++++++++++++++--- 3 files changed, 88 insertions(+), 29 deletions(-) diff --git a/src/main/scala/firrtl/transforms/InlineCasts.scala b/src/main/scala/firrtl/transforms/InlineCasts.scala index 71318eee1a..761252c192 100644 --- a/src/main/scala/firrtl/transforms/InlineCasts.scala +++ b/src/main/scala/firrtl/transforms/InlineCasts.scala @@ -28,23 +28,30 @@ object InlineCastsTransform { * @param expr the Expression being transformed * @return Returns expr with [[WRef]]s replaced by values found in replace */ - def onExpr(replace: NodeMap)(expr: Expression): Expression = expr match { - // Anything that may generate a part-select should not be inlined! - case DoPrim(op, _, _, _) if (isBitExtract(op) || op == Pad) => expr - case e => - e.map(onExpr(replace)) match { - case e @ WRef(name, _, _, _) => - replace - .get(name) - .filter(isSimpleCast(castSeen = false)) - .getOrElse(e) - case e @ DoPrim(op, Seq(WRef(name, _, _, _)), _, _) if isCast(op) => - replace - .get(name) - .map(value => e.copy(args = Seq(value))) - .getOrElse(e) - case other => other // Not a candidate - } + def onExpr(replace: NodeMap)(expr: Expression): Expression = { + // Keep track if we've seen any non-cast expressions while recursing + def rec(hasNonCastParent: Boolean)(expr: Expression): Expression = expr match { + // Skip pads to avoid inlining literals into pads which results in invalid Verilog + case DoPrim(op, _, _, _) if (isBitExtract(op) || op == Pad) => expr + case e => + e.map(rec(hasNonCastParent || !isCast(e))) match { + case e @ WRef(name, _, _, _) => + replace + .get(name) + .filter(isSimpleCast(castSeen = false)) + .getOrElse(e) + case e @ DoPrim(op, Seq(WRef(name, _, _, _)), _, _) if isCast(op) => + replace + .get(name) + // Only inline the Expression if there is no non-cast parent in the expression tree OR + // if the subtree contains only casts and references. + .filter(x => !hasNonCastParent || isSimpleCast(castSeen = true)(x)) + .map(value => e.copy(args = Seq(value))) + .getOrElse(e) + case other => other // Not a candidate + } + } + rec(false)(expr) } /** Inline casts in a Statement diff --git a/src/test/scala/firrtl/testutils/FirrtlSpec.scala b/src/test/scala/firrtl/testutils/FirrtlSpec.scala index 24793437d4..63def26a78 100644 --- a/src/test/scala/firrtl/testutils/FirrtlSpec.scala +++ b/src/test/scala/firrtl/testutils/FirrtlSpec.scala @@ -165,10 +165,14 @@ trait FirrtlRunners extends BackendCompilationUtilities { /** Compiles input Firrtl to Verilog */ def compileToVerilog(input: String, annotations: AnnotationSeq = Seq.empty): String = { + compileToVerilogCircuitState(input, annotations).getEmittedCircuit.value + } + + /** Compiles input Firrtl to Verilog */ + def compileToVerilogCircuitState(input: String, annotations: AnnotationSeq = Seq.empty): CircuitState = { val circuit = Parser.parse(input.split("\n").toIterator) val compiler = new VerilogCompiler - val res = compiler.compileAndEmit(CircuitState(circuit, HighForm, annotations), extraCheckTransforms) - res.getEmittedCircuit.value + compiler.compileAndEmit(CircuitState(circuit, HighForm, annotations), extraCheckTransforms) } /** Compile a Firrtl file diff --git a/src/test/scala/firrtlTests/InlineCastsSpec.scala b/src/test/scala/firrtlTests/InlineCastsSpec.scala index e27020e5ed..7a248def03 100644 --- a/src/test/scala/firrtlTests/InlineCastsSpec.scala +++ b/src/test/scala/firrtlTests/InlineCastsSpec.scala @@ -4,18 +4,19 @@ package firrtlTests import firrtl.transforms.InlineCastsTransform import firrtl.testutils.FirrtlFlatSpec +import firrtl.testutils.FirrtlCheckers._ -/* - * Note: InlineCasts is still part of mverilog, so this test must both: - * - Test that the InlineCasts fix is effective given the current mverilog - * - Provide a test that will be robust if and when InlineCasts is no longer run in mverilog - * - * This is why the test passes InlineCasts as a custom transform: to future-proof it so that - * it can do real LEC against no-InlineCasts. It currently is just a sanity check that the - * emitted Verilog is legal, but it will automatically become a more meaningful test when - * InlineCasts is not run in mverilog. - */ class InlineCastsEquivalenceSpec extends FirrtlFlatSpec { + /* + * Note: InlineCasts is still part of mverilog, so this test must both: + * - Test that the InlineCasts fix is effective given the current mverilog + * - Provide a test that will be robust if and when InlineCasts is no longer run in mverilog + * + * This is why the test passes InlineCasts as a custom transform: to future-proof it so that + * it can do real LEC against no-InlineCasts. It currently is just a sanity check that the + * emitted Verilog is legal, but it will automatically become a more meaningful test when + * InlineCasts is not run in mverilog. + */ "InlineCastsTransform" should "not produce broken Verilog" in { val input = s"""circuit literalsel_fir: @@ -26,4 +27,51 @@ class InlineCastsEquivalenceSpec extends FirrtlFlatSpec { |""".stripMargin firrtlEquivalenceTest(input, Seq(new InlineCastsTransform)) } + + it should "not inline complex expressions into other complex expressions" in { + val input = + """circuit NeverInlineComplexIntoComplex : + | module NeverInlineComplexIntoComplex : + | input a : SInt<3> + | input b : UInt<2> + | input c : UInt<2> + | input sel : UInt<1> + | output out : SInt<3> + | node diff = sub(b, c) + | out <= mux(sel, a, asSInt(diff)) + |""".stripMargin + val expected = + """module NeverInlineComplexIntoComplexRef( + | input [2:0] a, + | input [1:0] b, + | input [1:0] c, + | input sel, + | output [2:0] out + |); + | wire [2:0] diff = b - c; + | assign out = sel ? $signed(a) : $signed(diff); + |endmodule + |""".stripMargin + firrtlEquivalenceWithVerilog(input, expected) + } + + it should "inline casts on both sides of a more complex expression" in { + val input = + """circuit test : + | module test : + | input clock : Clock + | input in : UInt<8> + | output out : UInt<8> + | + | node _T_1 = asUInt(clock) + | node _T_2 = not(_T_1) + | node clock_n = asClock(_T_2) + | reg r : UInt<8>, clock_n + | r <= in + | out <= r + |""".stripMargin + val verilog = compileToVerilogCircuitState(input) + verilog should containLine("always @(posedge clock_n) begin") + + } } From 49b823244732e8d3a4b0fe91d0f10625fea34eec Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Fri, 19 Mar 2021 13:59:52 -0700 Subject: [PATCH 44/88] Legalize neg: -x becomes 0 - x (#2128) This fixes an error with negating a negative SInt literal and a [debatable] lint warning in Verilator when negating any value. This behavior matches that of Chisel (which directly emits the 0 - x already). --- src/main/scala/firrtl/passes/Legalize.scala | 18 +++++++- .../resources/passes/Legalize/Legalize.fir | 22 +++++++++ .../scala/firrtl/testutils/FirrtlSpec.scala | 25 ++++++++++ src/test/scala/firrtlTests/LegalizeSpec.scala | 4 +- src/test/scala/firrtlTests/NegSpec.scala | 46 +++++++++++++++++++ 5 files changed, 112 insertions(+), 3 deletions(-) create mode 100644 src/test/scala/firrtlTests/NegSpec.scala diff --git a/src/main/scala/firrtl/passes/Legalize.scala b/src/main/scala/firrtl/passes/Legalize.scala index ef0e17060c..e1a39fbe15 100644 --- a/src/main/scala/firrtl/passes/Legalize.scala +++ b/src/main/scala/firrtl/passes/Legalize.scala @@ -3,11 +3,11 @@ package firrtl.passes import firrtl.PrimOps._ -import firrtl.Utils.{error, zero, BoolType} +import firrtl.Utils.{error, getGroundZero, zero, BoolType} import firrtl.ir._ import firrtl.options.Dependency import firrtl.transforms.ConstantPropagation -import firrtl.{bitWidth, Transform} +import firrtl.{bitWidth, getWidth, Transform} import firrtl.Mappers._ // Replace shr by amount >= arg width with 0 for UInts and MSB for SInts @@ -56,6 +56,19 @@ object Legalize extends Pass { SIntLiteral(value, IntWidth(expr.consts.head)) case _ => expr } + // Convert `-x` to `0 - x` + private def legalizeNeg(expr: DoPrim): Expression = { + val arg = expr.args.head + arg.tpe match { + case tpe: SIntType => + val zero = getGroundZero(tpe) + DoPrim(Sub, Seq(zero, arg), Nil, expr.tpe) + case tpe: UIntType => + val zero = getGroundZero(tpe) + val sub = DoPrim(Sub, Seq(zero, arg), Nil, UIntType(tpe.width + IntWidth(1))) + DoPrim(AsSInt, Seq(sub), Nil, expr.tpe) + } + } private def legalizeConnect(c: Connect): Statement = { val t = c.loc.tpe val w = bitWidth(t) @@ -78,6 +91,7 @@ object Legalize extends Pass { case Shr => legalizeShiftRight(prim) case Pad => legalizePad(prim) case Bits | Head | Tail => legalizeBitExtract(prim) + case Neg => legalizeNeg(prim) case _ => prim } case e => e // respect pre-order traversal diff --git a/src/test/resources/passes/Legalize/Legalize.fir b/src/test/resources/passes/Legalize/Legalize.fir index 7e5386951a..a0a3984561 100644 --- a/src/test/resources/passes/Legalize/Legalize.fir +++ b/src/test/resources/passes/Legalize/Legalize.fir @@ -39,3 +39,25 @@ circuit Legalize : when neq(bar_15, UInt(1)) : printf(clock, UInt(1), "Assertion failed!\n bar_15 != 0\n") stop(clock, UInt(1), 1) + + ; Check neg of literals + node negUInt0 = neg(UInt(123)) + when neq(negUInt0, SInt(-123)) : + printf(clock, UInt(1), "Assertion failed!\n negUInt0 != -123\n") + stop(clock, UInt(1), 1) + node negUInt1 = neg(UInt<8>(0)) + when neq(negUInt1, SInt<8>(0)) : + printf(clock, UInt(1), "Assertion failed!\n negUInt1 != 0\n") + stop(clock, UInt(1), 1) + node negSInt0 = neg(SInt(123)) + when neq(negSInt0, SInt(-123)) : + printf(clock, UInt(1), "Assertion failed!\n negSInt0 != -123\n") + stop(clock, UInt(1), 1) + node negSInt1 = neg(SInt(-123)) + when neq(negSInt1, SInt(123)) : + printf(clock, UInt(1), "Assertion failed!\n negSInt1 != 123\n") + stop(clock, UInt(1), 1) + node negSInt2 = neg(SInt(0)) + when neq(negSInt2, SInt(0)) : + printf(clock, UInt(1), "Assertion failed!\n negSInt2 != 0\n") + stop(clock, UInt(1), 1) diff --git a/src/test/scala/firrtl/testutils/FirrtlSpec.scala b/src/test/scala/firrtl/testutils/FirrtlSpec.scala index 63def26a78..4dc2d642da 100644 --- a/src/test/scala/firrtl/testutils/FirrtlSpec.scala +++ b/src/test/scala/firrtl/testutils/FirrtlSpec.scala @@ -4,6 +4,7 @@ package firrtl.testutils import java.io._ import java.security.Permission +import scala.sys.process._ import logger.{LazyLogging, LogLevel, LogLevelAnnotation} @@ -175,6 +176,22 @@ trait FirrtlRunners extends BackendCompilationUtilities { compiler.compileAndEmit(CircuitState(circuit, HighForm, annotations), extraCheckTransforms) } + /** Run Verilator lint on some Verilog text + * + * @param inputVerilog Verilog to pass to `verilator --lint-only` + * @return Verilator return 0 + */ + def lintVerilog(inputVerilog: String): Unit = { + val testDir = createTestDirectory(s"${this.getClass.getSimpleName}_lint") + val filename = new File(testDir, "test.v") + val w = new FileWriter(filename) + w.write(inputVerilog) + w.close() + + val cmd = Seq("verilator", "--lint-only", filename.toString) + assert(cmd.!(loggingProcessLogger) == 0, "Lint must pass") + } + /** Compile a Firrtl file * * @param prefix is the name of the Firrtl file without path or file extension @@ -413,6 +430,14 @@ abstract class ExecutionTest( } } +/** Super class for execution driven Firrtl tests compiled without optimizations */ +abstract class ExecutionTestNoOpt( + name: String, + dir: String, + vFiles: Seq[String] = Seq.empty, + annotations: AnnotationSeq = Seq.empty) + extends ExecutionTest(name, dir, vFiles, RunFirrtlTransformAnnotation(new MinimumVerilogEmitter) +: annotations) + /** Super class for compilation driven Firrtl tests */ abstract class CompilationTest(name: String, dir: String) extends FirrtlPropSpec { property(s"$name should compile correctly") { diff --git a/src/test/scala/firrtlTests/LegalizeSpec.scala b/src/test/scala/firrtlTests/LegalizeSpec.scala index 905d578e4b..ad85668ef8 100644 --- a/src/test/scala/firrtlTests/LegalizeSpec.scala +++ b/src/test/scala/firrtlTests/LegalizeSpec.scala @@ -2,6 +2,8 @@ package firrtlTests -import firrtl.testutils.ExecutionTest +import firrtl.testutils.{ExecutionTest, ExecutionTestNoOpt} class LegalizeExecutionTest extends ExecutionTest("Legalize", "/passes/Legalize") +// Legalize also needs to work when optimizations are turned off +class LegalizeExecutionTestNoOpt extends ExecutionTestNoOpt("Legalize", "/passes/Legalize") diff --git a/src/test/scala/firrtlTests/NegSpec.scala b/src/test/scala/firrtlTests/NegSpec.scala new file mode 100644 index 0000000000..c60294e3bc --- /dev/null +++ b/src/test/scala/firrtlTests/NegSpec.scala @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: Apache-2.0 + +package firrtlTests + +import firrtl.testutils._ + +class NegSpec extends FirrtlFlatSpec { + "unsigned neg" should "be correct and lint-clean" in { + val input = + """|circuit UnsignedNeg : + | module UnsignedNeg : + | input in : UInt<8> + | output out : SInt + | out <= neg(in) + |""".stripMargin + val expected = + """|module UnsignedNegRef( + | input [7:0] in, + | output [8:0] out + |); + | assign out = 8'd0 - in; + |endmodule""".stripMargin + firrtlEquivalenceWithVerilog(input, expected) + lintVerilog(compileToVerilog(input)) + } + + "signed neg" should "be correct and lint-clean" in { + val input = + """|circuit SignedNeg : + | module SignedNeg : + | input in : SInt<8> + | output out : SInt + | out <= neg(in) + |""".stripMargin + // -$signed(in) is a lint warning in Verilator but is functionally correct + val expected = + """|module SignedNegRef( + | input [7:0] in, + | output [8:0] out + |); + | assign out = -$signed(in); + |endmodule""".stripMargin + firrtlEquivalenceWithVerilog(input, expected) + lintVerilog(compileToVerilog(input)) + } +} From 09c4baf90ca61c06fd87467110f62db5b6a999c2 Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Mon, 22 Mar 2021 19:35:04 +0000 Subject: [PATCH 45/88] Fix mill compile and add to CI (#2147) * fix for #2071 * add mill compile to CI --- .github/workflows/test.yml | 18 ++++++++++++++++++ build.sc | 9 ++++++--- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f6af7cbd98..4e0c81259e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -46,6 +46,24 @@ jobs: - name: Binary compatibility run: sbt ++${{ matrix.scala }} mimaReportBinaryIssues + mill: + name: Mill Sanity Check + runs-on: ubuntu-latest + strategy: + matrix: + scala: [2.13.4, 2.12.13] + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Setup Scala + uses: olafurpg/setup-scala@v10 + - name: Cache + uses: coursier/cache-action@v5 + - name: Setup Mill + uses: jodersky/setup-mill@v0.2.3 + - name: Mill sanity check + run: mill _[${{ matrix.scala }}].compile + # TODO find better way to express Ops and AddNot as single test equiv: name: formal equivalence diff --git a/build.sc b/build.sc index 89796964c9..232552b779 100644 --- a/build.sc +++ b/build.sc @@ -7,7 +7,7 @@ import mill.modules.Util import $ivy.`com.lihaoyi::mill-contrib-buildinfo:$MILL_VERSION` import mill.contrib.buildinfo.BuildInfo -object firrtl extends mill.Cross[firrtlCrossModule]("2.12.12", "2.13.2") +object firrtl extends mill.Cross[firrtlCrossModule]("2.12.13", "2.13.4") class firrtlCrossModule(val crossScalaVersion: String) extends CrossSbtModule with PublishModule with BuildInfo { override def millSourcePath = super.millSourcePath / os.up @@ -28,7 +28,7 @@ class firrtlCrossModule(val crossScalaVersion: String) extends CrossSbtModule wi "-deprecation", "-unchecked", "-Yrangepos" // required by SemanticDB compiler plugin - ) + ) ++ (if (majorVersion == 13) Seq("-Ymacro-annotations") else Nil) } override def javacOptions = T { @@ -42,16 +42,19 @@ class firrtlCrossModule(val crossScalaVersion: String) extends CrossSbtModule wi ivy"net.jcazevedo::moultingyaml:0.4.2", ivy"org.json4s::json4s-native:3.6.9", ivy"org.apache.commons:commons-text:1.8", + ivy"io.github.alexarchambault::data-class:0.2.5", ivy"org.antlr:antlr4-runtime:$antlr4Version", ivy"com.google.protobuf:protobuf-java:$protocVersion" ) ++ { - if (majorVersion > 12) + if (majorVersion == 13) Agg(ivy"org.scala-lang.modules::scala-parallel-collections:0.2.0") else Agg() } } + override def scalacPluginIvyDeps = if (majorVersion == 12) Agg(ivy"org.scalamacros:::paradise:2.1.1") else super.scalacPluginIvyDeps + object test extends Tests { override def ivyDeps = T { Agg( From 5614a5b534ef5901d2862a07fa79e6eb65893123 Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Thu, 25 Mar 2021 17:30:52 +0000 Subject: [PATCH 46/88] add scalafmt to mill (#2151) --- build.sc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build.sc b/build.sc index 232552b779..8ae16e3955 100644 --- a/build.sc +++ b/build.sc @@ -3,13 +3,14 @@ import mill._ import mill.scalalib._ import mill.scalalib.publish._ +import mill.scalalib.scalafmt._ import mill.modules.Util import $ivy.`com.lihaoyi::mill-contrib-buildinfo:$MILL_VERSION` import mill.contrib.buildinfo.BuildInfo object firrtl extends mill.Cross[firrtlCrossModule]("2.12.13", "2.13.4") -class firrtlCrossModule(val crossScalaVersion: String) extends CrossSbtModule with PublishModule with BuildInfo { +class firrtlCrossModule(val crossScalaVersion: String) extends CrossSbtModule with ScalafmtModule with PublishModule with BuildInfo { override def millSourcePath = super.millSourcePath / os.up // 2.12.12 -> Array("2", "12", "12") -> "12" -> 12 From 67ce97a10564cfa07829af8cfce562009d60bafb Mon Sep 17 00:00:00 2001 From: Schuyler Eldridge Date: Fri, 26 Mar 2021 13:50:21 -0400 Subject: [PATCH 47/88] Fix bug in zero-width memory removal (#2153) * Fix bug in zero-width memory removal Correctly remove all extraneous connections to all types of memory ports (read, write, readwrite) for zero-width memories. Previously, only read ports were correctly handled. Signed-off-by: Schuyler Eldridge * fixup! Fix bug in zero-width memory removal --- src/main/scala/firrtl/passes/ZeroWidth.scala | 27 +++----- .../scala/firrtlTests/ZeroWidthTests.scala | 63 +++++++++++++++++++ 2 files changed, 72 insertions(+), 18 deletions(-) diff --git a/src/main/scala/firrtl/passes/ZeroWidth.scala b/src/main/scala/firrtl/passes/ZeroWidth.scala index e918ff6341..003ab3c9a0 100644 --- a/src/main/scala/firrtl/passes/ZeroWidth.scala +++ b/src/main/scala/firrtl/passes/ZeroWidth.scala @@ -25,19 +25,12 @@ object ZeroWidth extends Transform with DependencyAPIMigration { case _ => false } - private def makeEmptyMemBundle(name: String): Field = - Field( - name, - Flip, - BundleType( - Seq( - Field("addr", Default, UIntType(IntWidth(0))), - Field("en", Default, UIntType(IntWidth(0))), - Field("clk", Default, UIntType(IntWidth(0))), - Field("data", Flip, UIntType(IntWidth(0))) - ) - ) - ) + private def makeZero(tpe: ir.Type): ir.Type = tpe match { + case ClockType => UIntType(IntWidth(0)) + case a: UIntType => a.copy(IntWidth(0)) + case a: SIntType => a.copy(IntWidth(0)) + case a: AggregateType => a.map(makeZero) + } private def onEmptyMemStmt(s: Statement): Statement = s match { case d @ DefMemory(info, name, tpe, _, _, _, rs, ws, rws, _) => @@ -46,11 +39,9 @@ object ZeroWidth extends Transform with DependencyAPIMigration { DefWire( info, name, - BundleType( - rs.map(r => makeEmptyMemBundle(r)) ++ - ws.map(w => makeEmptyMemBundle(w)) ++ - rws.map(rw => makeEmptyMemBundle(rw)) - ) + MemPortUtils + .memType(d) + .map(makeZero) ) case Some(_) => d } diff --git a/src/test/scala/firrtlTests/ZeroWidthTests.scala b/src/test/scala/firrtlTests/ZeroWidthTests.scala index df63006517..654c6f4294 100644 --- a/src/test/scala/firrtlTests/ZeroWidthTests.scala +++ b/src/test/scala/firrtlTests/ZeroWidthTests.scala @@ -237,6 +237,69 @@ class ZeroWidthTests extends FirrtlFlatSpec { | z <= asUInt(y)""".stripMargin (parse(exec(input))) should be(parse(check)) } + + "Memories with zero-width data-type" should "be fully removed" in { + val input = + """circuit Foo: + | module Foo: + | input clock: Clock + | input rAddr: UInt<4> + | input rEn: UInt<1> + | output rData: UInt<0> + | input wAddr: UInt<4> + | input wEn: UInt<1> + | input wMask: UInt<1> + | input wData: UInt<0> + | input rwEn: UInt<1> + | input rwMode: UInt<1> + | input rwAddr: UInt<1> + | input rwMask: UInt<1> + | input rwDataIn: UInt<0> + | output rwDataOut: UInt<0> + | + | mem memory: + | data-type => UInt<0> + | depth => 16 + | reader => r + | writer => w + | readwriter => rw + | read-latency => 0 + | write-latency => 1 + | read-under-write => undefined + | + | memory.r.clk <= clock + | memory.r.en <= rEn + | memory.r.addr <= rAddr + | rData <= memory.r.data + | memory.w.clk <= clock + | memory.w.en <= wEn + | memory.w.addr <= wAddr + | memory.w.mask <= wMask + | memory.w.data <= wData + | memory.rw.clk <= clock + | memory.rw.en <= rwEn + | memory.rw.addr <= rwAddr + | memory.rw.wmode <= rwMode + | memory.rw.wmask <= rwMask + | memory.rw.wdata <= rwDataIn + | rwDataOut <= memory.rw.rdata""".stripMargin + val check = + s"""circuit Foo: + | module Foo: + | input clock: Clock + | input rAddr: UInt<4> + | input rEn: UInt<1> + | input wAddr: UInt<4> + | input wEn: UInt<1> + | input wMask: UInt<1> + | input rwEn: UInt<1> + | input rwMode: UInt<1> + | input rwAddr: UInt<1> + | input rwMask: UInt<1> + | + |${Seq.tabulate(17)(_ => " skip").mkString("\n")}""".stripMargin + parse(exec(input)) should be(parse(check)) + } } class ZeroWidthVerilog extends FirrtlFlatSpec { From abeff01f0714d5474b9d18d78fc13011e5ad6b99 Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Sat, 27 Mar 2021 00:53:25 +0000 Subject: [PATCH 48/88] Add NoConstantPropagationAnnotation to disable constatnt propagation (#2150) * add --no-constant-propagation to disable constant propagation * add test * deprecate DisableFold. Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- .../firrtl.options.RegisteredTransform | 1 + .../firrtl/stage/FirrtlAnnotations.scala | 1 + .../transforms/ConstantPropagation.scala | 19 ++++++++++++++++--- .../transforms/OptimizationAnnotations.scala | 3 +++ .../ConstantPropagationTests.scala | 11 +++++++++++ 5 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/main/resources/META-INF/services/firrtl.options.RegisteredTransform b/src/main/resources/META-INF/services/firrtl.options.RegisteredTransform index bb72d45ccb..9304c39aaf 100644 --- a/src/main/resources/META-INF/services/firrtl.options.RegisteredTransform +++ b/src/main/resources/META-INF/services/firrtl.options.RegisteredTransform @@ -3,3 +3,4 @@ firrtl.transforms.CheckCombLoops firrtl.passes.InlineInstances firrtl.passes.clocklist.ClockListTransform firrtl.transforms.formal.AssertSubmoduleAssumptions +firrtl.transforms.ConstantPropagation diff --git a/src/main/scala/firrtl/stage/FirrtlAnnotations.scala b/src/main/scala/firrtl/stage/FirrtlAnnotations.scala index 26655efd1c..44c884188c 100644 --- a/src/main/scala/firrtl/stage/FirrtlAnnotations.scala +++ b/src/main/scala/firrtl/stage/FirrtlAnnotations.scala @@ -291,6 +291,7 @@ case object PrettyNoExprInlining extends NoTargetAnnotation with FirrtlOption wi */ case class DisableFold(op: ir.PrimOp) extends NoTargetAnnotation with FirrtlOption +@deprecated("will be removed and merged into ConstantPropagation in 1.5", "1.4") object DisableFold extends HasShellOptions { private val mapping: Map[String, ir.PrimOp] = PrimOps.builtinPrimOps.map { case op => op.toString -> op }.toMap diff --git a/src/main/scala/firrtl/transforms/ConstantPropagation.scala b/src/main/scala/firrtl/transforms/ConstantPropagation.scala index 7106c699e7..5610c7e762 100644 --- a/src/main/scala/firrtl/transforms/ConstantPropagation.scala +++ b/src/main/scala/firrtl/transforms/ConstantPropagation.scala @@ -13,7 +13,7 @@ import firrtl.PrimOps._ import firrtl.graph.DiGraph import firrtl.analyses.InstanceKeyGraph import firrtl.annotations.TargetToken.Ref -import firrtl.options.Dependency +import firrtl.options.{Dependency, RegisteredTransform, ShellOption} import firrtl.stage.DisableFold import annotation.tailrec @@ -101,7 +101,7 @@ object ConstantPropagation { } -class ConstantPropagation extends Transform with DependencyAPIMigration { +class ConstantPropagation extends Transform with RegisteredTransform with DependencyAPIMigration { import ConstantPropagation._ override def prerequisites = @@ -124,6 +124,14 @@ class ConstantPropagation extends Transform with DependencyAPIMigration { case _ => false } + val options = Seq( + new ShellOption[Unit]( + longOption = "no-constant-propagation", + toAnnotationSeq = _ => Seq(NoConstantPropagationAnnotation), + helpText = "Disable constant propagation elimination" + ) + ) + sealed trait SimplifyBinaryOp { def matchingArgsValue(e: DoPrim, arg: Expression): Expression def apply(e: DoPrim): Expression = { @@ -869,6 +877,11 @@ class ConstantPropagation extends Transform with DependencyAPIMigration { val disabledOps = state.annotations.collect { case DisableFold(op) => op }.toSet - state.copy(circuit = run(state.circuit, dontTouchMap, disabledOps)) + if (state.annotations.contains(NoConstantPropagationAnnotation)) { + logger.info("Skipping Constant Propagation") + state + } else { + state.copy(circuit = run(state.circuit, dontTouchMap, disabledOps)) + } } } diff --git a/src/main/scala/firrtl/transforms/OptimizationAnnotations.scala b/src/main/scala/firrtl/transforms/OptimizationAnnotations.scala index 10163b7259..e817571aab 100644 --- a/src/main/scala/firrtl/transforms/OptimizationAnnotations.scala +++ b/src/main/scala/firrtl/transforms/OptimizationAnnotations.scala @@ -9,6 +9,9 @@ import firrtl.passes.PassException /** Indicate that DCE should not be run */ case object NoDCEAnnotation extends NoTargetAnnotation +/** Indicate that ConstantPropagation should not be run */ +case object NoConstantPropagationAnnotation extends NoTargetAnnotation + /** Lets an annotation mark its ReferenceTarget members as DontTouch * * This permits a transform to run and remove its associated annotations, diff --git a/src/test/scala/firrtlTests/ConstantPropagationTests.scala b/src/test/scala/firrtlTests/ConstantPropagationTests.scala index 60cf1b8be8..bc7f92e638 100644 --- a/src/test/scala/firrtlTests/ConstantPropagationTests.scala +++ b/src/test/scala/firrtlTests/ConstantPropagationTests.scala @@ -908,6 +908,17 @@ class ConstantPropagationIntegrationSpec extends LowTransformSpec { execute(input, check, Seq(dontTouch("Child.in1"))) } + it should "NOT optimize if no-constant-propagation is enabled" in { + val input = + """circuit Foo: + | module Foo: + | input a: UInt<1> + | output b: UInt<1> + | b <= and(UInt<1>(0), a)""".stripMargin + val check = parse(input).serialize + execute(input, check, Seq(NoConstantPropagationAnnotation)) + } + it should "still propagate constants even when there is name swapping" in { val input = """circuit Top : From a41af6f0a34f9e13866002f19040a40ef55ee9e5 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Mon, 29 Mar 2021 10:21:26 -0700 Subject: [PATCH 49/88] Fix RemoveAccesses, delete CSESubAccesses (#2157) CSESubAccesses was intended to be a simple workaround for a quadratic performance bug in RemoveAccesses but ended up having tricky corner cases and was hard to get right. The solution to the RemoveAccesses bug--quadratic expansion of dynamic indexes of vecs of aggreate type--turned out to be quite simple and makes CSESubAccesses much less useful and not worth fixing. Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- .../scala/firrtl/passes/RemoveAccesses.scala | 63 +++-- src/main/scala/firrtl/stage/Forms.scala | 1 - .../firrtl/transforms/CSESubAccesses.scala | 168 ------------ .../scala/firrtlTests/LowerTypesSpec.scala | 3 +- .../firrtlTests/LoweringCompilersSpec.scala | 1 - src/test/scala/firrtlTests/UnitTests.scala | 10 +- .../passes/RemoveAccessesSpec.scala | 256 ++++++++++++++++++ .../transforms/CSESubAccessesSpec.scala | 223 --------------- 8 files changed, 297 insertions(+), 428 deletions(-) delete mode 100644 src/main/scala/firrtl/transforms/CSESubAccesses.scala create mode 100644 src/test/scala/firrtlTests/passes/RemoveAccessesSpec.scala delete mode 100644 src/test/scala/firrtlTests/transforms/CSESubAccessesSpec.scala diff --git a/src/main/scala/firrtl/passes/RemoveAccesses.scala b/src/main/scala/firrtl/passes/RemoveAccesses.scala index 7449db5167..073bf49d93 100644 --- a/src/main/scala/firrtl/passes/RemoveAccesses.scala +++ b/src/main/scala/firrtl/passes/RemoveAccesses.scala @@ -9,7 +9,6 @@ import firrtl.Mappers._ import firrtl.Utils._ import firrtl.WrappedExpression._ import firrtl.options.Dependency -import firrtl.transforms.CSESubAccesses import scala.collection.mutable @@ -22,8 +21,7 @@ object RemoveAccesses extends Pass { Dependency(PullMuxes), Dependency(ZeroLengthVecs), Dependency(ReplaceAccesses), - Dependency(ExpandConnects), - Dependency[CSESubAccesses] + Dependency(ExpandConnects) ) ++ firrtl.stage.Forms.Deduped override def invalidates(a: Transform): Boolean = a match { @@ -122,26 +120,26 @@ object RemoveAccesses extends Pass { /** Replaces a subaccess in a given source expression */ val stmts = mutable.ArrayBuffer[Statement]() - def removeSource(e: Expression): Expression = e match { - case (_: WSubAccess | _: WSubField | _: WSubIndex | _: WRef) if hasAccess(e) => - val rs = getLocations(e) - rs.find(x => x.guard != one) match { - case None => throwInternalError(s"removeSource: shouldn't be here - $e") - case Some(_) => - val (wire, temp) = create_temp(e) - val temps = create_exps(temp) - def getTemp(i: Int) = temps(i % temps.size) - stmts += wire - rs.zipWithIndex.foreach { - case (x, i) if i < temps.size => - stmts += IsInvalid(get_info(s), getTemp(i)) - stmts += Conditionally(get_info(s), x.guard, Connect(get_info(s), getTemp(i), x.base), EmptyStmt) - case (x, i) => - stmts += Conditionally(get_info(s), x.guard, Connect(get_info(s), getTemp(i), x.base), EmptyStmt) - } - temp - } - case _ => e + // Only called on RefLikes that definitely have a SubAccess + // Must accept Expression because that's the output type of fixIndices + def removeSource(e: Expression): Expression = { + val rs = getLocations(e) + rs.find(x => x.guard != one) match { + case None => throwInternalError(s"removeSource: shouldn't be here - $e") + case Some(_) => + val (wire, temp) = create_temp(e) + val temps = create_exps(temp) + def getTemp(i: Int) = temps(i % temps.size) + stmts += wire + rs.zipWithIndex.foreach { + case (x, i) if i < temps.size => + stmts += IsInvalid(get_info(s), getTemp(i)) + stmts += Conditionally(get_info(s), x.guard, Connect(get_info(s), getTemp(i), x.base), EmptyStmt) + case (x, i) => + stmts += Conditionally(get_info(s), x.guard, Connect(get_info(s), getTemp(i), x.base), EmptyStmt) + } + temp + } } /** Replaces a subaccess in a given sink expression @@ -162,14 +160,23 @@ object RemoveAccesses extends Pass { case _ => loc } + /** Recurse until find SubAccess and call fixSource on its index + * @note this only accepts [[RefLikeExpression]]s but we can't enforce it because map + * requires Expression => Expression + */ + def fixIndices(e: Expression): Expression = e match { + case e: SubAccess => e.copy(index = fixSource(e.index)) + case other => other.map(fixIndices) + } + /** Recursively walks a source expression and fixes all subaccesses - * If we see a sub-access, replace it. - * Otherwise, map to children. + * + * If we see a RefLikeExpression that contains a SubAccess, we recursively remove + * subaccesses from the indices of any SubAccesses, then process modified RefLikeExpression */ def fixSource(e: Expression): Expression = e match { - case w: WSubAccess => removeSource(WSubAccess(w.expr, fixSource(w.index), w.tpe, w.flow)) - //case w: WSubIndex => removeSource(w) - //case w: WSubField => removeSource(w) + case ref: RefLikeExpression => + if (hasAccess(ref)) removeSource(fixIndices(ref)) else ref case x => x.map(fixSource) } diff --git a/src/main/scala/firrtl/stage/Forms.scala b/src/main/scala/firrtl/stage/Forms.scala index ba27b55238..ab08215102 100644 --- a/src/main/scala/firrtl/stage/Forms.scala +++ b/src/main/scala/firrtl/stage/Forms.scala @@ -57,7 +57,6 @@ object Forms { val MidForm: Seq[TransformDependency] = HighForm ++ Seq( Dependency(passes.PullMuxes), - Dependency[firrtl.transforms.CSESubAccesses], Dependency(passes.ReplaceAccesses), Dependency(passes.ExpandConnects), Dependency(passes.RemoveAccesses), diff --git a/src/main/scala/firrtl/transforms/CSESubAccesses.scala b/src/main/scala/firrtl/transforms/CSESubAccesses.scala deleted file mode 100644 index 6ca27a830a..0000000000 --- a/src/main/scala/firrtl/transforms/CSESubAccesses.scala +++ /dev/null @@ -1,168 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package firrtl -package transforms - -import firrtl.ir._ -import firrtl.traversals.Foreachers._ -import firrtl.Mappers._ -import firrtl.PrimOps._ -import firrtl.WrappedExpression._ -import firrtl.options.Dependency -import firrtl.passes._ -import firrtl.Utils.{distinctBy, flow, getAllRefs, get_info, niceName} - -import scala.collection.mutable - -object CSESubAccesses { - - // Get all SubAccesses used on the right-hand side along with the info from the outer Statement - private def collectRValueSubAccesses(mod: Module): Seq[(SubAccess, Info)] = { - val acc = new mutable.ListBuffer[(SubAccess, Info)] - def onExpr(outer: Statement)(expr: Expression): Unit = { - // Need postorder because we want to visit inner SubAccesses first - // Stop recursing on any non-Source because flips can make the SubAccess a Source despite the - // overall Expression being a Sink - if (flow(expr) == SourceFlow) expr.foreach(onExpr(outer)) - expr match { - case e: SubAccess if flow(e) == SourceFlow => acc += e -> get_info(outer) - case _ => // Do nothing - } - } - def onStmt(stmt: Statement): Unit = { - stmt.foreach(onStmt) - stmt match { - // Don't record SubAccesses that are already assigned to a Node, but *do* record any nested - // inside of the SubAccess. This makes the transform idempotent and avoids unnecessary work. - case DefNode(_, _, acc: SubAccess) => acc.foreach(onExpr(stmt)) - case other => other.foreach(onExpr(stmt)) - } - } - onStmt(mod.body) - distinctBy(acc.toList)(_._1) - } - - // Replaces all right-hand side SubAccesses with References - private def replaceOnSourceExpr(replace: SubAccess => Reference)(expr: Expression): Expression = expr match { - // Stop is we ever see a non-SourceFlow - case e if flow(e) != SourceFlow => e - // Don't traverse children of SubAccess, just replace it - // Nested SubAccesses are handled during creation of the nodes that the references refer to - case acc: SubAccess if flow(acc) == SourceFlow => replace(acc) - case other => other.map(replaceOnSourceExpr(replace)) - } - - private def hoistSubAccesses( - hoist: String => List[DefNode], - replace: SubAccess => Reference - )(stmt: Statement - ): Statement = { - val onExpr = replaceOnSourceExpr(replace) _ - def onStmt(s: Statement): Statement = s.map(onExpr).map(onStmt) match { - case decl: IsDeclaration => - val nodes = hoist(decl.name) - if (nodes.isEmpty) decl else Block(decl :: nodes) - case other => other - } - onStmt(stmt) - } - - // Given some nodes, determine after which String declaration each node should be inserted - // This function is *mutable*, it keeps track of which declarations each node is sensitive to and - // returns nodes in groups once the last declaration they depend on is seen - private def getSensitivityLookup(nodes: Iterable[DefNode]): String => List[DefNode] = { - case class ReferenceCount(var n: Int, node: DefNode) - // Gather names of declarations each node depends on - val nodeDeps = nodes.map(node => getAllRefs(node.value).view.map(_.name).toSet -> node) - // Map from declaration names to the indices of nodeDeps that depend on it - val lookup = new mutable.HashMap[String, mutable.ArrayBuffer[Int]] - for (((decls, _), idx) <- nodeDeps.zipWithIndex) { - for (d <- decls) { - val indices = lookup.getOrElseUpdate(d, new mutable.ArrayBuffer[Int]) - indices += idx - } - } - // Now we can just associate each List of nodes with how many declarations they need to see - // We use an Array because we're mutating anyway and might as well be quick about it - val nodeLists: Array[ReferenceCount] = - nodeDeps.view.map { case (deps, node) => ReferenceCount(deps.size, node) }.toArray - - // Must be a def because it's recursive - def func(decl: String): List[DefNode] = { - if (lookup.contains(decl)) { - val indices = lookup(decl) - val result = new mutable.ListBuffer[DefNode] - lookup -= decl - for (i <- indices) { - val refCount = nodeLists(i) - refCount.n -= 1 - assert(refCount.n >= 0, "Internal Error!") - if (refCount.n == 0) result += refCount.node - } - // DefNodes can depend on each other, recurse - result.toList.flatMap { node => node :: func(node.name) } - } else { - Nil - } - } - func _ - } - - /** Performs [[CSESubAccesses]] on a single [[ir.Module Module]] */ - def onMod(mod: Module): Module = { - // ***** Pre-Analyze (do we even need to do anything) ***** - val accesses = collectRValueSubAccesses(mod) - if (accesses.isEmpty) mod - else { - // ***** Analyze ***** - val namespace = Namespace(mod) - val replace = new mutable.HashMap[SubAccess, Reference] - val nodes = new mutable.ArrayBuffer[DefNode] - for ((acc, info) <- accesses) { - val name = namespace.newName(niceName(acc)) - // SubAccesses can be nested, so replace any nested ones with prior references - // This is why post-order traversal in collectRValueSubAccesses is important - val accx = acc.map(replaceOnSourceExpr(replace)) - val node = DefNode(info, name, accx) - val ref = Reference(node) - // Record in replace - replace(acc) = ref - // Record node - nodes += node - } - val hoist = getSensitivityLookup(nodes) - - // ***** Transform ***** - val portStmts = mod.ports.flatMap(x => hoist(x.name)) - val bodyx = hoistSubAccesses(hoist, replace)(mod.body) - mod.copy(body = if (portStmts.isEmpty) bodyx else Block(Block(portStmts), bodyx)) - } - } -} - -/** Performs Common Subexpression Elimination (CSE) on right-hand side [[ir.SubAccess SubAccess]]es - * - * This avoids quadratic node creation behavior in [[passes.RemoveAccesses RemoveAccesses]]. For - * simplicity of implementation, all SubAccesses on the right-hand side are also split into - * individual nodes. - */ -class CSESubAccesses extends Transform with DependencyAPIMigration { - - override def prerequisites = Dependency(ResolveFlows) :: Dependency(CheckHighForm) :: Nil - - // Faster to run after these - override def optionalPrerequisites = Dependency(ReplaceAccesses) :: Dependency[DedupModules] :: Nil - - // Running before ExpandConnects is an optimization - override def optionalPrerequisiteOf = Dependency(ExpandConnects) :: Nil - - override def invalidates(a: Transform) = false - - def execute(state: CircuitState): CircuitState = { - val modulesx = state.circuit.modules.map { - case ext: ExtModule => ext - case mod: Module => CSESubAccesses.onMod(mod) - } - state.copy(circuit = state.circuit.copy(modules = modulesx)) - } -} diff --git a/src/test/scala/firrtlTests/LowerTypesSpec.scala b/src/test/scala/firrtlTests/LowerTypesSpec.scala index 9425a58222..78d03e68ac 100644 --- a/src/test/scala/firrtlTests/LowerTypesSpec.scala +++ b/src/test/scala/firrtlTests/LowerTypesSpec.scala @@ -486,8 +486,7 @@ class LowerTypesUniquifySpec extends FirrtlFlatSpec { | out <= in0[in1[in2[0]]][in1[in2[1]]] |""".stripMargin val expected = Seq( - "node _in0_in1_in1 = _in0_in1_in1_in2_1", - "out <= _in0_in1_in1" + "out <= _in0_in1_in1_in2_1" ) executeTest(input, expected) diff --git a/src/test/scala/firrtlTests/LoweringCompilersSpec.scala b/src/test/scala/firrtlTests/LoweringCompilersSpec.scala index ee6077d322..bdc72e7b80 100644 --- a/src/test/scala/firrtlTests/LoweringCompilersSpec.scala +++ b/src/test/scala/firrtlTests/LoweringCompilersSpec.scala @@ -180,7 +180,6 @@ class LoweringCompilersSpec extends AnyFlatSpec with Matchers { it should "replicate the old order" in { val tm = new TransformManager(Forms.MidForm, Forms.Deduped) val patches = Seq( - Add(2, Seq(Dependency[firrtl.transforms.CSESubAccesses])), Add(4, Seq(Dependency(firrtl.passes.ResolveFlows))), Add(5, Seq(Dependency(firrtl.passes.ResolveKinds))), // Uniquify is now part of [[firrtl.passes.LowerTypes]] diff --git a/src/test/scala/firrtlTests/UnitTests.scala b/src/test/scala/firrtlTests/UnitTests.scala index 0a0df355ba..061837d796 100644 --- a/src/test/scala/firrtlTests/UnitTests.scala +++ b/src/test/scala/firrtlTests/UnitTests.scala @@ -189,14 +189,14 @@ class UnitTests extends FirrtlFlatSpec { //TODO(azidar): I realize this is brittle, but unfortunately there // isn't a better way to test this pass val check = Seq( - """wire _table_1 : { a : UInt<8>}""", - """_table_1.a is invalid""", + """wire _table_1_a : UInt<8>""", + """_table_1_a is invalid""", """when UInt<1>("h1") :""", - """_table_1.a <= table[1].a""", + """_table_1_a <= table[1].a""", """wire _otherTable_table_1_a_a : UInt<8>""", - """when eq(UInt<1>("h0"), _table_1.a) :""", + """when eq(UInt<1>("h0"), _table_1_a) :""", """otherTable[0].a <= _otherTable_table_1_a_a""", - """when eq(UInt<1>("h1"), _table_1.a) :""", + """when eq(UInt<1>("h1"), _table_1_a) :""", """otherTable[1].a <= _otherTable_table_1_a_a""", """_otherTable_table_1_a_a <= UInt<1>("h0")""" ) diff --git a/src/test/scala/firrtlTests/passes/RemoveAccessesSpec.scala b/src/test/scala/firrtlTests/passes/RemoveAccessesSpec.scala new file mode 100644 index 0000000000..1f1f19680f --- /dev/null +++ b/src/test/scala/firrtlTests/passes/RemoveAccessesSpec.scala @@ -0,0 +1,256 @@ +// SPDX-License-Identifier: Apache-2.0 + +package firrtlTests +package passes + +import firrtl._ +import firrtl.testutils._ +import firrtl.stage.TransformManager +import firrtl.options.Dependency +import firrtl.passes._ + +class RemoveAccessesSpec extends FirrtlFlatSpec { + def compile(input: String): String = { + val manager = new TransformManager(Dependency(RemoveAccesses) :: Nil) + val result = manager.execute(CircuitState(parse(input), Nil)) + val checks = List( + CheckHighForm, + CheckTypes, + CheckFlows + ) + for (check <- checks) { check.run(result.circuit) } + result.circuit.serialize + } + def circuit(body: String): String = { + """|circuit Test : + | module Test : + |""".stripMargin + body.stripMargin.split("\n").mkString(" ", "\n ", "\n") + } + + behavior.of("RemoveAccesses") + + it should "handle a simple RHS subaccess" in { + val input = circuit( + s"""|input in : UInt<8>[4] + |input idx : UInt<2> + |output out : UInt<8> + |out <= in[idx]""" + ) + val expected = circuit( + s"""|input in : UInt<8>[4] + |input idx : UInt<2> + |output out : UInt<8> + |wire _in_idx : UInt<8> + |_in_idx is invalid + |when eq(UInt<1>("h0"), idx) : + | _in_idx <= in[0] + |when eq(UInt<1>("h1"), idx) : + | _in_idx <= in[1] + |when eq(UInt<2>("h2"), idx) : + | _in_idx <= in[2] + |when eq(UInt<2>("h3"), idx) : + | _in_idx <= in[3] + |out <= _in_idx""" + ) + compile(input) should be(parse(expected).serialize) + } + + it should "support complex expressions" in { + val input = circuit( + s"""|input clock : Clock + |input in : UInt<8>[4] + |input idx : UInt<2> + |input sel : UInt<1> + |output out : UInt<8> + |reg r : UInt<2>, clock + |out <= in[mux(sel, r, idx)] + |r <= not(idx)""" + ) + val expected = circuit( + s"""|input clock : Clock + |input in : UInt<8>[4] + |input idx : UInt<2> + |input sel : UInt<1> + |output out : UInt<8> + |reg r : UInt<2>, clock + |wire _in_mux : UInt<8> + |_in_mux is invalid + |when eq(UInt<1>("h0"), mux(sel, r, idx)) : + | _in_mux <= in[0] + |when eq(UInt<1>("h1"), mux(sel, r, idx)) : + | _in_mux <= in[1] + |when eq(UInt<2>("h2"), mux(sel, r, idx)) : + | _in_mux <= in[2] + |when eq(UInt<2>("h3"), mux(sel, r, idx)) : + | _in_mux <= in[3] + |out <= _in_mux + |r <= not(idx)""" + ) + compile(input) should be(parse(expected).serialize) + } + + it should "support nested subaccesses" in { + val input = circuit( + s"""|input in : UInt<8>[4] + |input idx : UInt<2>[4] + |input jdx : UInt<2> + |output out : UInt<8> + |out <= in[idx[jdx]]""" + ) + val expected = circuit( + s"""|input in : UInt<8>[4] + |input idx : UInt<2>[4] + |input jdx : UInt<2> + |output out : UInt<8> + |wire _idx_jdx : UInt<2> + |_idx_jdx is invalid + |when eq(UInt<1>("h0"), jdx) : + | _idx_jdx <= idx[0] + |when eq(UInt<1>("h1"), jdx) : + | _idx_jdx <= idx[1] + |when eq(UInt<2>("h2"), jdx) : + | _idx_jdx <= idx[2] + |when eq(UInt<2>("h3"), jdx) : + | _idx_jdx <= idx[3] + |wire _in_idx_jdx : UInt<8> + |_in_idx_jdx is invalid + |when eq(UInt<1>("h0"), _idx_jdx) : + | _in_idx_jdx <= in[0] + |when eq(UInt<1>("h1"), _idx_jdx) : + | _in_idx_jdx <= in[1] + |when eq(UInt<2>("h2"), _idx_jdx) : + | _in_idx_jdx <= in[2] + |when eq(UInt<2>("h3"), _idx_jdx) : + | _in_idx_jdx <= in[3] + |out <= _in_idx_jdx""" + ) + compile(input) should be(parse(expected).serialize) + } + + it should "avoid name collisions" in { + val input = circuit( + s"""|input in : UInt<8>[4] + |input idx : UInt<2> + |output out : UInt<8> + |out <= in[idx] + |node _in_idx = not(idx)""" + ) + val expected = circuit( + s"""|input in : UInt<8>[4] + |input idx : UInt<2> + |output out : UInt<8> + |wire _in_idx_0 : UInt<8> + |_in_idx_0 is invalid + |when eq(UInt<1>("h0"), idx) : + | _in_idx_0 <= in[0] + |when eq(UInt<1>("h1"), idx) : + | _in_idx_0 <= in[1] + |when eq(UInt<2>("h2"), idx) : + | _in_idx_0 <= in[2] + |when eq(UInt<2>("h3"), idx) : + | _in_idx_0 <= in[3] + |out <= _in_idx_0 + |node _in_idx = not(idx)""" + ) + compile(input) should be(parse(expected).serialize) + } + + it should "handle a simple LHS subaccess" in { + val input = circuit( + s"""|input in : UInt<8> + |input idx : UInt<2> + |output out : UInt<8>[4] + |out[idx] <= in""" + ) + val expected = circuit( + s"""|input in : UInt<8> + |input idx : UInt<2> + |output out : UInt<8>[4] + |wire _out_idx : UInt<8> + |when eq(UInt<1>("h0"), idx) : + | out[0] <= _out_idx + |when eq(UInt<1>("h1"), idx) : + | out[1] <= _out_idx + |when eq(UInt<2>("h2"), idx) : + | out[2] <= _out_idx + |when eq(UInt<2>("h3"), idx) : + | out[3] <= _out_idx + |_out_idx <= in""" + ) + compile(input) should be(parse(expected).serialize) + } + + it should "linearly expand RHS subaccesses of aggregate-typed vecs" in { + val input = circuit( + s"""|input in : { foo : UInt<8>, bar : UInt<8> }[4] + |input idx : UInt<2> + |output out : { foo : UInt<8>, bar : UInt<8> } + |out.foo <= in[idx].foo + |out.bar <= in[idx].bar""" + ) + val expected = circuit( + s"""|input in : { foo : UInt<8>, bar : UInt<8>}[4] + |input idx : UInt<2> + |output out : { foo : UInt<8>, bar : UInt<8>} + |wire _in_idx_foo : UInt<8> + |_in_idx_foo is invalid + |when eq(UInt<1>("h0"), idx) : + | _in_idx_foo <= in[0].foo + |when eq(UInt<1>("h1"), idx) : + | _in_idx_foo <= in[1].foo + |when eq(UInt<2>("h2"), idx) : + | _in_idx_foo <= in[2].foo + |when eq(UInt<2>("h3"), idx) : + | _in_idx_foo <= in[3].foo + |out.foo <= _in_idx_foo + |wire _in_idx_bar : UInt<8> + |_in_idx_bar is invalid + |when eq(UInt<1>("h0"), idx) : + | _in_idx_bar <= in[0].bar + |when eq(UInt<1>("h1"), idx) : + | _in_idx_bar <= in[1].bar + |when eq(UInt<2>("h2"), idx) : + | _in_idx_bar <= in[2].bar + |when eq(UInt<2>("h3"), idx) : + | _in_idx_bar <= in[3].bar + |out.bar <= _in_idx_bar""" + ) + compile(input) should be(parse(expected).serialize) + } + + it should "linearly expand LHS subaccesses of aggregate-typed vecs" in { + val input = circuit( + s"""|input in : { foo : UInt<8>, bar : UInt<8> } + |input idx : UInt<2> + |output out : { foo : UInt<8>, bar : UInt<8> }[4] + |out[idx].foo <= in.foo + |out[idx].bar <= in.bar""" + ) + val expected = circuit( + s"""|input in : { foo : UInt<8>, bar : UInt<8> } + |input idx : UInt<2> + |output out : { foo : UInt<8>, bar : UInt<8> }[4] + |wire _out_idx_foo : UInt<8> + |when eq(UInt<1>("h0"), idx) : + | out[0].foo <= _out_idx_foo + |when eq(UInt<1>("h1"), idx) : + | out[1].foo <= _out_idx_foo + |when eq(UInt<2>("h2"), idx) : + | out[2].foo <= _out_idx_foo + |when eq(UInt<2>("h3"), idx) : + | out[3].foo <= _out_idx_foo + |_out_idx_foo <= in.foo + |wire _out_idx_bar : UInt<8> + |when eq(UInt<1>("h0"), idx) : + | out[0].bar <= _out_idx_bar + |when eq(UInt<1>("h1"), idx) : + | out[1].bar <= _out_idx_bar + |when eq(UInt<2>("h2"), idx) : + | out[2].bar <= _out_idx_bar + |when eq(UInt<2>("h3"), idx) : + | out[3].bar <= _out_idx_bar + |_out_idx_bar <= in.bar""" + ) + compile(input) should be(parse(expected).serialize) + } +} diff --git a/src/test/scala/firrtlTests/transforms/CSESubAccessesSpec.scala b/src/test/scala/firrtlTests/transforms/CSESubAccessesSpec.scala deleted file mode 100644 index f7d67026fd..0000000000 --- a/src/test/scala/firrtlTests/transforms/CSESubAccessesSpec.scala +++ /dev/null @@ -1,223 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package firrtlTests -package transforms - -import firrtl._ -import firrtl.testutils._ -import firrtl.stage.TransformManager -import firrtl.options.Dependency -import firrtl.transforms.CSESubAccesses - -class CSESubAccessesSpec extends FirrtlFlatSpec { - def compile(input: String): String = { - val manager = new TransformManager(Dependency[CSESubAccesses] :: Nil) - val result = manager.execute(CircuitState(parse(input), Nil)) - result.circuit.serialize - } - def circuit(body: String): String = { - """|circuit Test : - | module Test : - |""".stripMargin + body.stripMargin.split("\n").mkString(" ", "\n ", "\n") - } - - behavior.of("CSESubAccesses") - - it should "hoist a single RHS subaccess" in { - val input = circuit( - s"""|input in : UInt<8>[4] - |input idx : UInt<2> - |output out : UInt<8> - |out <= in[idx]""" - ) - val expected = circuit( - s"""|input in : UInt<8>[4] - |input idx : UInt<2> - |output out : UInt<8> - |node _in_idx = in[idx] - |out <= _in_idx""" - ) - compile(input) should be(parse(expected).serialize) - } - - it should "be idempotent" in { - val input = circuit( - s"""|input in : UInt<8>[4] - |input idx : UInt<2> - |output out : UInt<8> - |out <= in[idx]""" - ) - val expected = circuit( - s"""|input in : UInt<8>[4] - |input idx : UInt<2> - |output out : UInt<8> - |node _in_idx = in[idx] - |out <= _in_idx""" - ) - val first = compile(input) - val second = compile(first) - first should be(second) - first should be(parse(expected).serialize) - } - - it should "hoist a redundant RHS subaccess" in { - val input = circuit( - s"""|input in : { foo : UInt<8>, bar : UInt<8> }[4] - |input idx : UInt<2> - |output out : { foo : UInt<8>, bar : UInt<8> } - |out.foo <= in[idx].foo - |out.bar <= in[idx].bar""" - ) - val expected = circuit( - s"""|input in : { foo : UInt<8>, bar : UInt<8> }[4] - |input idx : UInt<2> - |output out : { foo : UInt<8>, bar : UInt<8> } - |node _in_idx = in[idx] - |out.foo <= _in_idx.foo - |out.bar <= _in_idx.bar""" - ) - compile(input) should be(parse(expected).serialize) - } - - it should "correctly place hosited subaccess after last declaration it depends on" in { - val input = circuit( - s"""|input in : UInt<8>[4] - |input idx : UInt<2> - |output out : UInt<8> - |out is invalid - |when UInt(1) : - | node nidx = not(idx) - | out <= in[nidx] - |""" - ) - val expected = circuit( - s"""|input in : UInt<8>[4] - |input idx : UInt<2> - |output out : UInt<8> - |out is invalid - |when UInt(1) : - | node nidx = not(idx) - | node _in_nidx = in[nidx] - | out <= _in_nidx - |""" - ) - compile(input) should be(parse(expected).serialize) - } - - it should "support complex expressions" in { - val input = circuit( - s"""|input clock : Clock - |input in : UInt<8>[4] - |input idx : UInt<2> - |input sel : UInt<1> - |output out : UInt<8> - |reg r : UInt<2>, clock - |out <= in[mux(sel, r, idx)] - |r <= not(idx)""" - ) - val expected = circuit( - s"""|input clock : Clock - |input in : UInt<8>[4] - |input idx : UInt<2> - |input sel : UInt<1> - |output out : UInt<8> - |reg r : UInt<2>, clock - |node _in_mux = in[mux(sel, r, idx)] - |out <= _in_mux - |r <= not(idx)""" - ) - compile(input) should be(parse(expected).serialize) - } - - it should "support nested subaccesses" in { - val input = circuit( - s"""|input in : UInt<8>[4] - |input idx : UInt<2>[4] - |input jdx : UInt<2> - |output out : UInt<8> - |out <= in[idx[jdx]]""" - ) - val expected = circuit( - s"""|input in : UInt<8>[4] - |input idx : UInt<2>[4] - |input jdx : UInt<2> - |output out : UInt<8> - |node _idx_jdx = idx[jdx] - |node _in_idx = in[_idx_jdx] - |out <= _in_idx""" - ) - compile(input) should be(parse(expected).serialize) - } - - it should "avoid name collisions" in { - val input = circuit( - s"""|input in : UInt<8>[4] - |input idx : UInt<2> - |output out : UInt<8> - |out <= in[idx] - |node _in_idx = not(idx)""" - ) - val expected = circuit( - s"""|input in : UInt<8>[4] - |input idx : UInt<2> - |output out : UInt<8> - |node _in_idx_0 = in[idx] - |out <= _in_idx_0 - |node _in_idx = not(idx)""" - ) - compile(input) should be(parse(expected).serialize) - } - - it should "have no effect on LHS SubAccesses" in { - val input = circuit( - s"""|input in : UInt<8> - |input idx : UInt<2> - |output out : UInt<8>[4] - |out[idx] <= in""" - ) - val expected = circuit( - s"""|input in : UInt<8> - |input idx : UInt<2> - |output out : UInt<8>[4] - |out[idx] <= in""" - ) - compile(input) should be(parse(expected).serialize) - } - - it should "ignore flipped LHS SubAccesses" in { - val input = circuit( - s"""|input in : { foo : UInt<8> } - |input idx : UInt<1> - |input out : { flip foo : UInt<8> }[2] - |out[0].foo <= UInt(0) - |out[1].foo <= UInt(0) - |out[idx].foo <= in.foo""" - ) - val expected = circuit( - s"""|input in : { foo : UInt<8> } - |input idx : UInt<1> - |input out : { flip foo : UInt<8> }[2] - |out[0].foo <= UInt(0) - |out[1].foo <= UInt(0) - |out[idx].foo <= in.foo""" - ) - compile(input) should be(parse(expected).serialize) - } - - it should "ignore SubAccesses of bidirectional aggregates" in { - val input = circuit( - s"""|input in : { flip foo : UInt<8>, bar : UInt<8> } - |input idx : UInt<2> - |output out : { flip foo : UInt<8>, bar : UInt<8> }[4] - |out[idx] <= in""" - ) - val expected = circuit( - s"""|input in : { flip foo : UInt<8>, bar : UInt<8> } - |input idx : UInt<2> - |output out : { flip foo : UInt<8>, bar : UInt<8> }[4] - |out[idx] <= in""" - ) - compile(input) should be(parse(expected).serialize) - } - -} From e5ef6d786445e5974cbffd5c59ef52ce699e32fb Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Mon, 29 Mar 2021 20:31:40 +0200 Subject: [PATCH 50/88] Update protobuf-java to 3.15.6 (#2136) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- build.sc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sc b/build.sc index 8ae16e3955..416774d047 100644 --- a/build.sc +++ b/build.sc @@ -113,7 +113,7 @@ class firrtlCrossModule(val crossScalaVersion: String) extends CrossSbtModule wi } /* protoc */ - def protocVersion = "3.5.1" + def protocVersion = "3.15.6" def protobufSource = T.source { millSourcePath / "src" / "main" / "proto" / "firrtl.proto" From 9f6ed17060259425b49a7704b9597e353799cd57 Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Wed, 31 Mar 2021 01:53:03 +0800 Subject: [PATCH 51/88] don't use protoc-jar anymore, mill can handle it better. (#2162) --- build.sc | 52 ++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/build.sc b/build.sc index 416774d047..5f3d9730ea 100644 --- a/build.sc +++ b/build.sc @@ -119,13 +119,57 @@ class firrtlCrossModule(val crossScalaVersion: String) extends CrossSbtModule wi millSourcePath / "src" / "main" / "proto" / "firrtl.proto" } - def downloadProtocJar = T.persistent { - Util.download(s"https://repo.maven.apache.org/maven2/com/github/os72/protoc-jar/$protocVersion/protoc-jar-$protocVersion.jar") + def architecture = T { + System.getProperty("os.arch") + } + def operationSystem = T { + System.getProperty("os.name") + } + + def downloadProtoc = T.persistent { + val isMac = operationSystem().toLowerCase.startsWith("mac") + val isLinux = operationSystem().toLowerCase.startsWith("linux") + val isWindows = operationSystem().toLowerCase.startsWith("win") + + val aarch_64 = architecture().equals("aarch64") | architecture().startsWith("armv8") + val ppcle_64 = architecture().equals("ppc64le") + val s390x = architecture().equals("s390x") + val x86_32 = architecture().matches("^(x8632|x86|i[3-6]86|ia32|x32)$") + val x86_64 = architecture().matches("^(x8664|amd64|ia32e|em64t|x64)$") + + val protocBinary = + if (isMac) + if (aarch_64) "osx-x86_64" + else throw new Exception("mill cannot detect your architecture of your Mac") + else if (isLinux) + if (aarch_64) "linux-aarch_64" + else if (ppcle_64) "linux-ppcle_64" + else if (s390x) "linux-s390x" + else if (x86_32) "linux-x86_32" + else if (x86_64) "linux-x86_64" + else throw new Exception("mill cannot detect your architecture of your Linux") + else if (isWindows) + if (x86_32) "win32" + else if (x86_64) "win64" + else throw new Exception("mill cannot detect your architecture of your Windows") + else throw new Exception("mill cannot detect your operation system.") + + val zip = Util.downloadUnpackZip( + s"https://github.com/protocolbuffers/protobuf/releases/download/v$protocVersion/protoc-$protocVersion-$protocBinary.zip" + ) + val bin = if(isWindows) + zip.path / "bin" / "protoc.exe" + else + zip.path / "bin" / "protoc" + + // Download Linux/Mac binary doesn't have x. + if (!isWindows) os.perms.set(bin, "--x------") + PathRef(bin) } def generatedProtoSources = T.sources { - os.proc("java", - "-jar", downloadProtocJar().path.toString, + os.proc( + downloadProtoc().path.toString, "-I", protobufSource().path / os.up, s"--java_out=${T.ctx.dest.toString}", protobufSource().path.toString() From 78ece783cafa5bf7b5c367e21d7949250c52b947 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Tue, 30 Mar 2021 12:36:07 -0700 Subject: [PATCH 52/88] Update README.md (#2164) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d53dd0250b..5efe975e1f 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ --- [![Join the chat at https://gitter.im/freechipsproject/firrtl](https://badges.gitter.im/freechipsproject/firrtl.svg)](https://gitter.im/freechipsproject/firrtl?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -[![Build Status](https://travis-ci.org/freechipsproject/firrtl.svg?branch=master)](https://travis-ci.org/freechipsproject/firrtl) +![Build Status](https://github.com/chipsalliance/firrtl/workflows/Continuous%20Integration/badge.svg) [![Mergify Status][mergify-status]][mergify] [mergify]: https://mergify.io From ffe83fa43b9269f1e899122ba7825025df173b5a Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Tue, 30 Mar 2021 16:47:35 -0700 Subject: [PATCH 53/88] Fix Mill support for non-M1 Macs (#2165) * Fix Mill support for non-M1 Macs * Update build.sc Co-authored-by: edwardcwang Co-authored-by: edwardcwang --- build.sc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/build.sc b/build.sc index 5f3d9730ea..4ac60a51bb 100644 --- a/build.sc +++ b/build.sc @@ -135,11 +135,12 @@ class firrtlCrossModule(val crossScalaVersion: String) extends CrossSbtModule wi val ppcle_64 = architecture().equals("ppc64le") val s390x = architecture().equals("s390x") val x86_32 = architecture().matches("^(x8632|x86|i[3-6]86|ia32|x32)$") - val x86_64 = architecture().matches("^(x8664|amd64|ia32e|em64t|x64)$") + val x86_64 = architecture().matches("^(x8664|amd64|ia32e|em64t|x64|x86_64)$") val protocBinary = if (isMac) - if (aarch_64) "osx-x86_64" + // MacOS ARM 64-bit still supports x86_64 binaries via Rosetta 2 + if (aarch_64 || x86_64) "osx-x86_64" else throw new Exception("mill cannot detect your architecture of your Mac") else if (isLinux) if (aarch_64) "linux-aarch_64" From d0d3cd4ec4348eea381fe463ac9c96956fdd5eba Mon Sep 17 00:00:00 2001 From: Carlos Eduardo Date: Thu, 1 Apr 2021 18:55:03 -0300 Subject: [PATCH 54/88] Add memory initialization options for synthesis (#2166) This PR adds options for memory initialization inside or outside the `ifndef SYNTHESIS` block. --- .../annotations/MemoryInitAnnotation.scala | 6 +++ .../backends/verilog/VerilogEmitter.scala | 41 +++++++++++++++++-- .../scala/firrtlTests/MemoryInitSpec.scala | 40 +++++++++++++++++- 3 files changed, 83 insertions(+), 4 deletions(-) diff --git a/src/main/scala/firrtl/annotations/MemoryInitAnnotation.scala b/src/main/scala/firrtl/annotations/MemoryInitAnnotation.scala index 62dc96f485..1e81301d75 100644 --- a/src/main/scala/firrtl/annotations/MemoryInitAnnotation.scala +++ b/src/main/scala/firrtl/annotations/MemoryInitAnnotation.scala @@ -52,3 +52,9 @@ case class MemoryFileInlineAnnotation( override def initValue: MemoryInitValue = MemoryFileInlineInit(filename, hexOrBinary) override def isRandomInit: Boolean = false } + +/** Initializes the memory inside the `ifndef SYNTHESIS` block (default) */ +case object MemoryNoSynthInit extends NoTargetAnnotation + +/** Initializes the memory outside the `ifndef SYNTHESIS` block */ +case object MemorySynthInit extends NoTargetAnnotation diff --git a/src/main/scala/firrtl/backends/verilog/VerilogEmitter.scala b/src/main/scala/firrtl/backends/verilog/VerilogEmitter.scala index 33c6b9a8af..f854b33a35 100644 --- a/src/main/scala/firrtl/backends/verilog/VerilogEmitter.scala +++ b/src/main/scala/firrtl/backends/verilog/VerilogEmitter.scala @@ -7,7 +7,14 @@ import firrtl.PrimOps._ import firrtl.Utils._ import firrtl.WrappedExpression._ import firrtl.traversals.Foreachers._ -import firrtl.annotations.{CircuitTarget, MemoryLoadFileType, ReferenceTarget, SingleTargetAnnotation} +import firrtl.annotations.{ + CircuitTarget, + MemoryLoadFileType, + MemoryNoSynthInit, + MemorySynthInit, + ReferenceTarget, + SingleTargetAnnotation +} import firrtl.passes.LowerTypes import firrtl.passes.MemPortUtils._ import firrtl.stage.TransformManager @@ -482,6 +489,21 @@ class VerilogEmitter extends SeqTransform with Emitter { def getConnectEmissionOption(target: ReferenceTarget): ConnectEmissionOption = connectEmissionOption(target) + // Defines the memory initialization based on the annotation + // Defaults to having the memories inside the `ifndef SYNTHESIS` block + def emitMemoryInitAsNoSynth: Boolean = { + val annos = annotations.collect { case a @ (MemoryNoSynthInit | MemorySynthInit) => a } + annos match { + case Seq() => true + case Seq(MemoryNoSynthInit) => true + case Seq(MemorySynthInit) => false + case other => + throw new FirrtlUserException( + "There should only be at most one memory initialization option annotation, got $other" + ) + } + } + private val emissionAnnos = annotations.collect { case m: SingleTargetAnnotation[ReferenceTarget] @unchecked with EmissionOption => m } @@ -861,7 +883,14 @@ class VerilogEmitter extends SeqTransform with Emitter { case MemoryLoadFileType.Binary => "$readmemb" case MemoryLoadFileType.Hex => "$readmemh" } - memoryInitials += Seq(s"""$readmem("$filename", ${s.name});""") + if (emissionOptions.emitMemoryInitAsNoSynth) { + memoryInitials += Seq(s"""$readmem("$filename", ${s.name});""") + } else { + val inlineLoad = s"""initial begin + | $readmem("$filename", ${s.name}); + | end""".stripMargin + memoryInitials += Seq(inlineLoad) + } } } @@ -1200,13 +1229,19 @@ class VerilogEmitter extends SeqTransform with Emitter { for (x <- initials) emit(Seq(tab, x)) for (x <- asyncInitials) emit(Seq(tab, x)) emit(Seq(" `endif // RANDOMIZE")) - for (x <- memoryInitials) emit(Seq(tab, x)) + + if (emissionOptions.emitMemoryInitAsNoSynth) { + for (x <- memoryInitials) emit(Seq(tab, x)) + } emit(Seq("end // initial")) // User-defined macro of code to run after an initial block emit(Seq("`ifdef FIRRTL_AFTER_INITIAL")) emit(Seq("`FIRRTL_AFTER_INITIAL")) emit(Seq("`endif")) emit(Seq("`endif // SYNTHESIS")) + if (!emissionOptions.emitMemoryInitAsNoSynth) { + for (x <- memoryInitials) emit(Seq(tab, x)) + } } if (formals.keys.nonEmpty) { diff --git a/src/test/scala/firrtlTests/MemoryInitSpec.scala b/src/test/scala/firrtlTests/MemoryInitSpec.scala index a7c9966a3e..44f0162ef5 100644 --- a/src/test/scala/firrtlTests/MemoryInitSpec.scala +++ b/src/test/scala/firrtlTests/MemoryInitSpec.scala @@ -4,7 +4,7 @@ package firrtlTests import firrtl._ import firrtl.annotations._ -import firrtl.testutils.FirrtlCheckers.containLine +import firrtl.testutils.FirrtlCheckers.{containLine, containLines} import firrtl.testutils.FirrtlFlatSpec import firrtlTests.execution._ @@ -182,6 +182,44 @@ class MemInitSpec extends FirrtlFlatSpec { compile(Seq(MemoryFileInlineAnnotation(mRef, filename = ""))) } } + + "MemoryInitialization" should "emit readmem in `ifndef SYNTHESIS` block by default" in { + val annos = Seq( + MemoryFileInlineAnnotation(mRef, filename = "text.hex", hexOrBinary = MemoryLoadFileType.Hex) + ) + val result = compile(annos) + result should containLines( + """`endif // RANDOMIZE""", + """$readmemh("text.hex", """ + mRef.name + """);""", + """end // initial""" + ) + } + + "MemoryInitialization" should "emit readmem outside `ifndef SYNTHESIS` block with MemorySynthInit annotation" in { + val annos = Seq( + MemoryFileInlineAnnotation(mRef, filename = "text.hex", hexOrBinary = MemoryLoadFileType.Hex) + ) ++ Seq(MemorySynthInit) + val result = compile(annos) + result should containLines( + """`endif // SYNTHESIS""", + """initial begin""", + """$readmemh("text.hex", """ + mRef.name + """);""", + """end""" + ) + } + + "MemoryInitialization" should "emit readmem outside `ifndef SYNTHESIS` block with MemoryNoSynthInit annotation" in { + val annos = Seq( + MemoryFileInlineAnnotation(mRef, filename = "text.hex", hexOrBinary = MemoryLoadFileType.Hex) + ) ++ Seq(MemoryNoSynthInit) + + val result = compile(annos) + result should containLines( + """`endif // RANDOMIZE""", + """$readmemh("text.hex", """ + mRef.name + """);""", + """end // initial""" + ) + } } abstract class MemInitExecutionSpec(values: Seq[Int], init: ReferenceTarget => Annotation) From ca8b670eac0b0def66249738e52ef8137d30a8b5 Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Sun, 4 Apr 2021 13:32:20 +0800 Subject: [PATCH 55/88] Fix mill cache download (#2171) --- build.sc | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/build.sc b/build.sc index 4ac60a51bb..ec154b8946 100644 --- a/build.sc +++ b/build.sc @@ -97,7 +97,9 @@ class firrtlCrossModule(val crossScalaVersion: String) extends CrossSbtModule wi } def downloadAntlr4Jar = T.persistent { - Util.download(s"https://www.antlr.org/download/antlr-$antlr4Version-complete.jar") + if (!os.isFile( T.ctx.dest / "antlr4" )) + Util.download(s"https://www.antlr.org/download/antlr-$antlr4Version-complete.jar", os.rel / "antlr4") + PathRef(T.ctx.dest / "antlr4") } def generatedAntlr4Source = T.sources { @@ -155,16 +157,20 @@ class firrtlCrossModule(val crossScalaVersion: String) extends CrossSbtModule wi else throw new Exception("mill cannot detect your architecture of your Windows") else throw new Exception("mill cannot detect your operation system.") - val zip = Util.downloadUnpackZip( - s"https://github.com/protocolbuffers/protobuf/releases/download/v$protocVersion/protoc-$protocVersion-$protocBinary.zip" - ) + val unpackPath = os.rel / "unpacked" + val bin = if(isWindows) - zip.path / "bin" / "protoc.exe" + T.ctx.dest / unpackPath / "bin" / "protoc.exe" else - zip.path / "bin" / "protoc" + T.ctx.dest / unpackPath / "bin" / "protoc" + if (!os.exists(bin)) + Util.downloadUnpackZip( + s"https://github.com/protocolbuffers/protobuf/releases/download/v$protocVersion/protoc-$protocVersion-$protocBinary.zip", + unpackPath + ) // Download Linux/Mac binary doesn't have x. - if (!isWindows) os.perms.set(bin, "--x------") + if (!isWindows) os.perms.set(bin, "rwx------") PathRef(bin) } From 088c82244d58d7e5c8a6ad6e7e3bb1edaf81af3a Mon Sep 17 00:00:00 2001 From: Albert Magyar Date: Tue, 9 Mar 2021 23:15:29 -0800 Subject: [PATCH 56/88] Specify that SimplifyMems invalidates InferTypes --- src/main/scala/firrtl/transforms/SimplifyMems.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/scala/firrtl/transforms/SimplifyMems.scala b/src/main/scala/firrtl/transforms/SimplifyMems.scala index 8ecc484ae2..81c40dd45b 100644 --- a/src/main/scala/firrtl/transforms/SimplifyMems.scala +++ b/src/main/scala/firrtl/transforms/SimplifyMems.scala @@ -23,7 +23,10 @@ class SimplifyMems extends Transform with DependencyAPIMigration { override def prerequisites = Forms.MidForm override def optionalPrerequisites = Seq.empty override def optionalPrerequisiteOf = Forms.MidEmitters - override def invalidates(a: Transform) = false + override def invalidates(a: Transform) = a match { + case InferTypes => true + case _ => false + } def onModule(c: Circuit, renames: RenameMap)(m: DefModule): DefModule = { val moduleNS = Namespace(m) From a90cf1105467cab7c6708ea3faae35e1454cb0fd Mon Sep 17 00:00:00 2001 From: Albert Magyar Date: Tue, 9 Mar 2021 21:30:53 -0800 Subject: [PATCH 57/88] 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 --- .../backends/verilog/VerilogEmitter.scala | 113 +++++++++++------- src/test/scala/firrtlTests/InfoSpec.scala | 10 +- 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 f854b33a35..66df12e03a 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 43fb6ee1c7..db4828f680 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") } From 8a9dfa8b0324b3f52e676b9d27912656c43dd327 Mon Sep 17 00:00:00 2001 From: Albert Magyar Date: Tue, 9 Mar 2021 22:25:55 -0800 Subject: [PATCH 58/88] Optionally allow simple SyncReadMems to pass through VerilogMemDelays * This is enabled by adding a PassthroughSimpleSyncReadMemsAnnotation * Can be emitted directly with new changes to the Verilog emitter * Add some new deprecations to VerilogMemDelays * Run scalafmt on VerilogMemDelays --- .../passes/memlib/VerilogMemDelays.scala | 41 +++++++++++++++++-- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/src/main/scala/firrtl/passes/memlib/VerilogMemDelays.scala b/src/main/scala/firrtl/passes/memlib/VerilogMemDelays.scala index 143b925a9a..a9b42ebac0 100644 --- a/src/main/scala/firrtl/passes/memlib/VerilogMemDelays.scala +++ b/src/main/scala/firrtl/passes/memlib/VerilogMemDelays.scala @@ -10,12 +10,22 @@ import firrtl.Mappers._ import firrtl.traversals.Foreachers._ import firrtl.transforms import firrtl.options.Dependency +import firrtl.annotations.NoTargetAnnotation import MemPortUtils._ import WrappedExpression._ import collection.mutable +/** + * Adding this annotation will allow the [[VerilogMemDelays]] transform to let 'simple' synchronous-read memories to + * pass through without explicitly breaking them apart into combinational-read memories and pipeline registers. Here, + * 'simple' memories are defined as those that have one-cycle read and write latencies AND either no readwrite ports or + * read-under-write behavior that is either 'undefined' or 'old'. This second restriction avoids the particularly + * complex case of blending FIRRTL readwrite port semantics with cross-port 'bypassing' of new data on collisions. + */ +case object PassthroughSimpleSyncReadMemsAnnotation extends NoTargetAnnotation + object MemDelayAndReadwriteTransformer { // Representation of a group of signals and associated valid signals case class WithValid(valid: Expression, payload: Seq[Expression]) @@ -77,13 +87,14 @@ object MemDelayAndReadwriteTransformer { * * @note The final transformed module is found in the (sole public) field [[transformed]] */ -class MemDelayAndReadwriteTransformer(m: DefModule) { +class MemDelayAndReadwriteTransformer(m: DefModule, passthroughSimpleSyncReadMems: Boolean = false) { import MemDelayAndReadwriteTransformer._ private val ns = Namespace(m) private val netlist = new collection.mutable.HashMap[WrappedExpression, Expression] private val exprReplacements = new collection.mutable.HashMap[WrappedExpression, Expression] private val newConns = new mutable.ArrayBuffer[Connect] + private val passthroughMems = new collection.mutable.HashSet[WrappedExpression] private def findMemConns(s: Statement): Unit = s match { case Connect(_, loc, expr) if (kind(loc) == MemKind) => netlist(we(loc)) = expr @@ -95,7 +106,15 @@ class MemDelayAndReadwriteTransformer(m: DefModule) { case ex => ex } + def canPassthrough(mem: DefMemory): Boolean = { + (mem.readLatency <= 1 && mem.writeLatency == 1 && + (mem.readwriters.isEmpty || (mem.readLatency == 1 && mem.readUnderWrite != ReadUnderWrite.New))) + } + private def transform(s: Statement): Statement = s.map(transform) match { + case mem: DefMemory if passthroughSimpleSyncReadMems && canPassthrough(mem) => + passthroughMems += WRef(mem) + mem case mem: DefMemory => // Per-memory bookkeeping val portNS = Namespace(mem.readers ++ mem.writers) @@ -163,7 +182,13 @@ class MemDelayAndReadwriteTransformer(m: DefModule) { newConns ++= (readStmts ++ writeStmts).flatMap(_.conns) Block(newMem +: (readStmts ++ writeStmts).flatMap(_.decls)) - case sx: Connect if kind(sx.loc) == MemKind => EmptyStmt // Filter old mem connections + case sx: Connect if kind(sx.loc) == MemKind => + val (memRef, _) = Utils.splitRef(sx.loc) + // Filter old mem connections for *transformed* memories only + if (passthroughMems(WrappedExpression(memRef))) + sx + else + EmptyStmt case sx => sx.map(swapMemRefs) } @@ -188,6 +213,14 @@ object VerilogMemDelays extends Pass { case _ => false } - def transform(m: DefModule): DefModule = (new MemDelayAndReadwriteTransformer(m)).transformed - def run(c: Circuit): Circuit = c.copy(modules = c.modules.map(transform)) + private def transform(m: DefModule): DefModule = (new MemDelayAndReadwriteTransformer(m)).transformed + + @deprecated("VerilogMemDelays will change from a Pass to a Transform in FIRRTL 1.6.", "FIRRTL 1.5") + def run(c: Circuit): Circuit = c.copy(modules = c.modules.map(transform)) + + override def execute(state: CircuitState): CircuitState = { + val enablePassthrough = state.annotations.contains(PassthroughSimpleSyncReadMemsAnnotation) + def transform(m: DefModule) = (new MemDelayAndReadwriteTransformer(m, enablePassthrough)).transformed + state.copy(circuit = state.circuit.copy(modules = state.circuit.modules.map(transform))) + } } From bf5b51b101bd9ab0883cdfa56f8c42bf26a1f375 Mon Sep 17 00:00:00 2001 From: Albert Magyar Date: Tue, 9 Mar 2021 22:27:52 -0800 Subject: [PATCH 59/88] Add SetDefaultReadUnderWrite transform * Optionally defines read-under-write behavior for all 'undefined' memories * Use DefaultReadFirstAnnotation to choose read-first default * Use DefaultWriteFirstAnnotation to choose write-first default * Seal DefaultReadUnderWriteAnnotation based on Jack's feedback --- .../memlib/SetDefaultReadUnderWrite.scala | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 src/main/scala/firrtl/passes/memlib/SetDefaultReadUnderWrite.scala diff --git a/src/main/scala/firrtl/passes/memlib/SetDefaultReadUnderWrite.scala b/src/main/scala/firrtl/passes/memlib/SetDefaultReadUnderWrite.scala new file mode 100644 index 0000000000..d56460990a --- /dev/null +++ b/src/main/scala/firrtl/passes/memlib/SetDefaultReadUnderWrite.scala @@ -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.") + } + } +} From 59f0d9d2dbcc74e9489f453106704e403c59df06 Mon Sep 17 00:00:00 2001 From: Albert Magyar Date: Mon, 1 Mar 2021 09:21:55 -0800 Subject: [PATCH 60/88] Allow InferReadWrite to combine shared-address R/W ports when appropriate --- .../firrtl/passes/memlib/InferReadWrite.scala | 47 +++++++++++++------ 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/src/main/scala/firrtl/passes/memlib/InferReadWrite.scala b/src/main/scala/firrtl/passes/memlib/InferReadWrite.scala index 0bb9445206..39c79bc625 100644 --- a/src/main/scala/firrtl/passes/memlib/InferReadWrite.scala +++ b/src/main/scala/firrtl/passes/memlib/InferReadWrite.scala @@ -78,6 +78,16 @@ object InferReadWritePass extends Pass { case sx => sx } + /* If the ports share the same address in an undefined-collision SyncReadMem, reads issued while the write + * is enabled are *always* undefined; we may treat the read as if it were gated by the complement of w.en. + * Though not a strict requirement, this currently applies only to single-cycle read/write memories. + * N.B. for aggregate-typed memories, the spec is conservative and 'undefined' is not a function of the + * write mask, allowing optimization regardless of mask value. This must be revisited if the spec changes. + */ + private def canOptimizeCollidingRW(mem: DefMemory): Boolean = { + mem.readUnderWrite == ReadUnderWrite.Undefined && mem.readLatency == 1 && mem.writeLatency == 1 + } + def inferReadWriteStmt(connects: Connects, repl: Netlist, stmts: Statements)(s: Statement): Statement = s match { // infer readwrite ports only for non combinational memories case mem: DefMemory if mem.readLatency > 0 => @@ -94,7 +104,10 @@ object InferReadWritePass extends Pass { val proofOfMutualExclusion = wenProductTerms.find(a => renProductTerms.exists(b => checkComplement(a, b))) val wclk = getOrigin(connects)(memPortField(mem, w, "clk")) val rclk = getOrigin(connects)(memPortField(mem, r, "clk")) - if (weq(wclk, rclk) && proofOfMutualExclusion.nonEmpty) { + val waddr = getOrigin(connects)(memPortField(mem, w, "addr")) + val raddr = getOrigin(connects)(memPortField(mem, r, "addr")) + val optimizeCollision = (weq(waddr, raddr) && canOptimizeCollidingRW(mem)) + if (weq(wclk, rclk) && (proofOfMutualExclusion.nonEmpty || optimizeCollision)) { val rw = namespace.newName("rw") val rwExp = WSubField(WRef(mem.name), rw) readwriters += rw @@ -104,28 +117,32 @@ object InferReadWritePass extends Pass { repl(memPortField(mem, r, "en")) = EmptyExpression repl(memPortField(mem, r, "addr")) = EmptyExpression repl(memPortField(mem, r, "data")) = WSubField(rwExp, "rdata") - repl(memPortField(mem, w, "clk")) = EmptyExpression - repl(memPortField(mem, w, "en")) = EmptyExpression - repl(memPortField(mem, w, "addr")) = EmptyExpression + repl(memPortField(mem, w, "clk")) = WSubField(rwExp, "clk") repl(memPortField(mem, w, "data")) = WSubField(rwExp, "wdata") repl(memPortField(mem, w, "mask")) = WSubField(rwExp, "wmask") - stmts += Connect(NoInfo, WSubField(rwExp, "wmode"), proofOfMutualExclusion.get) - stmts += Connect(NoInfo, WSubField(rwExp, "clk"), wclk) stmts += Connect( NoInfo, WSubField(rwExp, "en"), DoPrim(Or, Seq(connects(memPortField(mem, r, "en")), connects(memPortField(mem, w, "en"))), Nil, BoolType) ) - stmts += Connect( - NoInfo, - WSubField(rwExp, "addr"), - Mux( - connects(memPortField(mem, w, "en")), - connects(memPortField(mem, w, "addr")), - connects(memPortField(mem, r, "addr")), - UnknownType + if (optimizeCollision) { + repl(memPortField(mem, w, "en")) = WSubField(rwExp, "wmode") + repl(memPortField(mem, w, "addr")) = WSubField(rwExp, "addr") + } else { + repl(memPortField(mem, w, "en")) = EmptyExpression + repl(memPortField(mem, w, "addr")) = EmptyExpression + stmts += Connect(NoInfo, WSubField(rwExp, "wmode"), proofOfMutualExclusion.get) + stmts += Connect( + NoInfo, + WSubField(rwExp, "addr"), + Mux( + connects(memPortField(mem, w, "en")), + connects(memPortField(mem, w, "addr")), + connects(memPortField(mem, r, "addr")), + UnknownType + ) ) - ) + } } } if (readwriters.isEmpty) mem From 47cc4a8c57a7d50c9914ceeadbcd19f71f179187 Mon Sep 17 00:00:00 2001 From: Albert Magyar Date: Mon, 1 Mar 2021 09:46:25 -0800 Subject: [PATCH 61/88] Add tests for same-address readwrite inference * Update test to include both 'old' and 'new' read-under-write values --- .../firrtlTests/InferReadWriteSpec.scala | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/src/test/scala/firrtlTests/InferReadWriteSpec.scala b/src/test/scala/firrtlTests/InferReadWriteSpec.scala index 1fb242972f..62969df5f2 100644 --- a/src/test/scala/firrtlTests/InferReadWriteSpec.scala +++ b/src/test/scala/firrtlTests/InferReadWriteSpec.scala @@ -177,4 +177,55 @@ circuit sram6t : // Check correctness of firrtl res should containLine(s"mem.rw.wmode <= wen") } + + def sameAddr(ruw: String): String = { + s""" + |circuit sram6t : + | module sram6t : + | input clock : Clock + | output io : { flip addr : UInt<11>, flip valid : UInt<1>, flip write : UInt<1>, flip dataIn : UInt<32>, dataOut : UInt<32>} + | + | mem mem: + | data-type => UInt<4> + | depth => 64 + | reader => r + | writer => w + | read-latency => 1 + | write-latency => 1 + | read-under-write => ${ruw} + | + | mem.r.clk <= clock + | mem.r.addr <= io.addr + | mem.r.en <= io.valid + | io.dataOut <= mem.r.data + | + | node wen = and(io.valid, io.write) + | mem.w.clk <= clock + | mem.w.addr <= io.addr + | mem.w.en <= wen + | mem.w.mask <= UInt(1) + | mem.w.data <= io.dataIn""".stripMargin + } + + "Infer ReadWrite Ports" should "infer readwrite ports from shared addresses with undefined readUnderWrite" in { + val input = sameAddr("undefined") + val annos = Seq(memlib.InferReadWriteAnnotation) + val res = compileAndEmit(CircuitState(parse(input), HighForm, annos)) + // Check correctness of firrtl + res should containLine(s"mem.rw.wmode <= wen") + } + + Seq("old", "new").foreach { ruw => + "Infer ReadWrite Ports" should s"not infer readwrite ports from shared addresses with '${ruw}' readUnderWrite" in { + val input = sameAddr(ruw) + val annos = Seq(memlib.InferReadWriteAnnotation) + intercept[Exception] { + compileAndEmit(CircuitState(parse(input), ChirrtlForm, annos)) + } match { + case CustomTransformException(_: InferReadWriteCheckException) => // success + case _ => fail() + } + } + } + } From 872b282ff5bd7cc08f97a42e388b4e043454fe22 Mon Sep 17 00:00:00 2001 From: Albert Magyar Date: Mon, 15 Mar 2021 11:24:09 -0700 Subject: [PATCH 62/88] Add SeparateWriteClocks to ensure one mem write per Verilog process * Address @ekiwi comments from review * Change match cases to scalafmt-mandated lined-up style --- .../passes/memlib/SeparateWriteClocks.scala | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 src/main/scala/firrtl/passes/memlib/SeparateWriteClocks.scala 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 0000000000..f526f64bb1 --- /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) + } +} From 595d37213c57b3455a1c39b1cf93e37d01f41b3f Mon Sep 17 00:00:00 2001 From: Albert Magyar Date: Tue, 9 Mar 2021 22:57:46 -0800 Subject: [PATCH 63/88] Add --target:fpga flag to prioritize FPGA-friendly compilation * Update name of FPGA flag based on Jack's comment * Add Scaladoc to describe what each constituent transform does * Add SeparateWriteClocks to --target:fpga --- src/main/scala/firrtl/stage/FirrtlCli.scala | 3 +- .../firrtl/stage/FirrtlCompilerTargets.scala | 52 +++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 src/main/scala/firrtl/stage/FirrtlCompilerTargets.scala diff --git a/src/main/scala/firrtl/stage/FirrtlCli.scala b/src/main/scala/firrtl/stage/FirrtlCli.scala index 8be5fb74d4..9cfa6be9e5 100644 --- a/src/main/scala/firrtl/stage/FirrtlCli.scala +++ b/src/main/scala/firrtl/stage/FirrtlCli.scala @@ -22,7 +22,8 @@ trait FirrtlCli { this: Shell => NoCircuitDedupAnnotation, WarnNoScalaVersionDeprecation, PrettyNoExprInlining, - DisableFold + DisableFold, + OptimizeForFPGA ) .map(_.addOptions(parser)) diff --git a/src/main/scala/firrtl/stage/FirrtlCompilerTargets.scala b/src/main/scala/firrtl/stage/FirrtlCompilerTargets.scala new file mode 100644 index 0000000000..c6975712ea --- /dev/null +++ b/src/main/scala/firrtl/stage/FirrtlCompilerTargets.scala @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: Apache-2.0 + +package firrtl.stage + +import firrtl.transforms._ +import firrtl.passes.memlib._ +import firrtl.options.{HasShellOptions, ShellOption} + +/** + * This flag enables a set of options that guide the FIRRTL compilation flow to ultimately generate Verilog that is + * more amenable to using for synthesized FPGA designs. Currently, this flag affects only memories, as the need to emit + * memories that support downstream inference of hardened RAM macros. These options are not intended to be specialized + * to any particular vendor; instead, they aim to emit simple Verilog that more closely reflects traditional + * human-written definitions of synchronous-read memories. + * + * 1) Add a [[firrtl.passes.memlib.PassthroughSimpleSyncReadMemsAnnotation]] to allow some synchronous-read memories + * and readwrite ports to pass through [[firrtl.passes.memlib.VerilogMemDelays]] without introducing explicit + * pipeline registers or splitting ports. + * + * 2) Use the [[firrtl.transforms.SimplifyMems]] transform to Lower aggregate-typed memories with always-high masks to + * packed memories without splitting them into multiple independent ground-typed memories. + * + * 3) Use the [[firrtl.passes.memlib.SeparateWriteClocks]] transform to ensure that each write port of a + * multiple-write, synchronous-read memory with 'undefined' collision behavior ultimately maps to a separate clocked + * process in the emitted Verilog. This avoids the issue of implicitly constraining cross-port collision and write + * ordering behavior and helps simplify inference of true dual-port RAM macros. + * + * 4) Use the [[firrtl.passes.memlib.SetDefaultReadUnderWrite]] to specify that memories with undefined + * read-under-write behavior should map to emitted microarchitectures characteristic of "read-first" ports by + * default. This eliminates the difficulty of inferring a RAM macro that matches the strict semantics of + * "write-first" ports. + * + * 5) Enable the [[firrtl.passes.memlib.InferReadWrite]] transform to reduce port count, where applicable. + */ +object OptimizeForFPGA extends HasShellOptions { + private val fpgaAnnos = Seq( + InferReadWriteAnnotation, + RunFirrtlTransformAnnotation(new InferReadWrite), + RunFirrtlTransformAnnotation(new SeparateWriteClocks), + DefaultReadFirstAnnotation, + RunFirrtlTransformAnnotation(new SetDefaultReadUnderWrite), + RunFirrtlTransformAnnotation(new SimplifyMems), + PassthroughSimpleSyncReadMemsAnnotation + ) + val options = Seq( + new ShellOption[Unit]( + longOption = "target:fpga", + toAnnotationSeq = a => fpgaAnnos, + helpText = "Choose compilation strategies that generally favor FPGA targets" + ) + ) +} From 78dc3d01c53f81e65ca58a166b395f26f91bc2e0 Mon Sep 17 00:00:00 2001 From: Albert Magyar Date: Wed, 24 Mar 2021 13:32:18 -0700 Subject: [PATCH 64/88] Add test for SeparateWriteClocks --- .../firrtlTests/SeparateWriteClocksSpec.scala | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 src/test/scala/firrtlTests/SeparateWriteClocksSpec.scala diff --git a/src/test/scala/firrtlTests/SeparateWriteClocksSpec.scala b/src/test/scala/firrtlTests/SeparateWriteClocksSpec.scala new file mode 100644 index 0000000000..476a3ae2de --- /dev/null +++ b/src/test/scala/firrtlTests/SeparateWriteClocksSpec.scala @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: Apache-2.0 + +package firrtlTests + +import firrtl._ +import firrtl.ir._ +import firrtl.passes.memlib.SeparateWriteClocks +import firrtl.testutils._ +import firrtl.testutils.FirrtlCheckers._ + +class SeparateWriteClocksSpec extends FirrtlFlatSpec { + def transform(input: String): CircuitState = { + val csx = (new SeparateWriteClocks).execute(CircuitState(parse(input), MidForm)) + val emittedCirc = EmittedFirrtlCircuit("top", csx.circuit.serialize, ".fir") + csx.copy(annotations = Seq(EmittedFirrtlCircuitAnnotation(emittedCirc))) + } + + behavior.of("SeparateWriteClocks") + + it should "add intermediate wires to clocks of multi-write sync-read memories" in { + val result = transform(s""" + |circuit top: + | module top: + | input clk: Clock + | input raddr: UInt<10> + | output rdata: UInt<8>[4] + | input waddr_a: UInt<10> + | input we_a: UInt<1> + | input wdata_a: UInt<8>[4] + | input waddr_a: UInt<10> + | input we_a: UInt<1> + | input wdata_a: UInt<8>[4] + | + | mem m: + | data-type => UInt<8> + | depth => 1024 + | reader => r + | writer => w_a + | writer => w_b + | read-latency => 1 + | write-latency => 1 + | read-under-write => undefined + | + | m.r.clk <= clk + | m.r.addr <= raddr + | m.r.en <= UInt(1) + | rdata <= m.r.data + | + | m.w_a.clk <= clk + | m.w_a.addr <= waddr_a + | m.w_a.en <= we_a + | m.w_a.mask <= UInt(1) + | m.w_a.data <= wdata_a + | + | m.w_b.clk <= clk + | m.w_b.addr <= waddr_b + | m.w_b.en <= we_b + | m.w_b.mask <= UInt(1) + | m.w_b.data <= wdata_b""".stripMargin) + + println(result.circuit.serialize) + result should containLine("m.r.clk <= clk") + result should containLine("m.w_a.clk <= m_w_a_clk") + result should containLine("m.w_b.clk <= m_w_b_clk") + result shouldNot containLine("m.w_a.clk <= clk") + result shouldNot containLine("m.w_b.clk <= clk") + } +} From 1afa3b40f78d781ca1f242b49ca3a56d6cbc57e4 Mon Sep 17 00:00:00 2001 From: Albert Magyar Date: Mon, 5 Apr 2021 12:18:49 -0700 Subject: [PATCH 65/88] Establish a fixed relative order for FPGA-backed passes + reflect in ScalaDoc --- src/main/scala/firrtl/stage/FirrtlCompilerTargets.scala | 8 ++++---- src/main/scala/firrtl/transforms/SimplifyMems.scala | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/scala/firrtl/stage/FirrtlCompilerTargets.scala b/src/main/scala/firrtl/stage/FirrtlCompilerTargets.scala index c6975712ea..662f3dc00e 100644 --- a/src/main/scala/firrtl/stage/FirrtlCompilerTargets.scala +++ b/src/main/scala/firrtl/stage/FirrtlCompilerTargets.scala @@ -13,9 +13,7 @@ import firrtl.options.{HasShellOptions, ShellOption} * to any particular vendor; instead, they aim to emit simple Verilog that more closely reflects traditional * human-written definitions of synchronous-read memories. * - * 1) Add a [[firrtl.passes.memlib.PassthroughSimpleSyncReadMemsAnnotation]] to allow some synchronous-read memories - * and readwrite ports to pass through [[firrtl.passes.memlib.VerilogMemDelays]] without introducing explicit - * pipeline registers or splitting ports. + * 1) Enable the [[firrtl.passes.memlib.InferReadWrite]] transform to reduce port count, where applicable. * * 2) Use the [[firrtl.transforms.SimplifyMems]] transform to Lower aggregate-typed memories with always-high masks to * packed memories without splitting them into multiple independent ground-typed memories. @@ -30,7 +28,9 @@ import firrtl.options.{HasShellOptions, ShellOption} * default. This eliminates the difficulty of inferring a RAM macro that matches the strict semantics of * "write-first" ports. * - * 5) Enable the [[firrtl.passes.memlib.InferReadWrite]] transform to reduce port count, where applicable. + * 5) Add a [[firrtl.passes.memlib.PassthroughSimpleSyncReadMemsAnnotation]] to allow some synchronous-read memories + * and readwrite ports to pass through [[firrtl.passes.memlib.VerilogMemDelays]] without introducing explicit + * pipeline registers or splitting ports. */ object OptimizeForFPGA extends HasShellOptions { private val fpgaAnnos = Seq( diff --git a/src/main/scala/firrtl/transforms/SimplifyMems.scala b/src/main/scala/firrtl/transforms/SimplifyMems.scala index 81c40dd45b..92e19f7e0a 100644 --- a/src/main/scala/firrtl/transforms/SimplifyMems.scala +++ b/src/main/scala/firrtl/transforms/SimplifyMems.scala @@ -6,6 +6,7 @@ package transforms import firrtl.ir._ import firrtl.Mappers._ import firrtl.annotations._ +import firrtl.options.Dependency import firrtl.passes._ import firrtl.passes.memlib._ import firrtl.stage.Forms @@ -21,7 +22,7 @@ import ResolveMaskGranularity._ class SimplifyMems extends Transform with DependencyAPIMigration { override def prerequisites = Forms.MidForm - override def optionalPrerequisites = Seq.empty + override def optionalPrerequisites = Seq(Dependency[InferReadWrite]) override def optionalPrerequisiteOf = Forms.MidEmitters override def invalidates(a: Transform) = a match { case InferTypes => true From 9a3dcf761e40b7ac36f9c867d0a36692d4d74c0c Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Tue, 6 Apr 2021 11:02:20 -0700 Subject: [PATCH 66/88] Deprecate InlineCasts, add InlineAcrossCasts (#2146) To maintain binary compatibility, InlineAcrossCasts is just aliases to the now deprecated InlineCasts. We can make the binary incompatible change of renaming the class and object for 1.5. Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- src/main/scala/firrtl/AddDescriptionNodes.scala | 2 +- .../scala/firrtl/passes/VerilogModulusCleanup.scala | 2 +- src/main/scala/firrtl/passes/VerilogPrep.scala | 2 +- src/main/scala/firrtl/stage/Forms.scala | 2 +- .../scala/firrtl/transforms/FlattenRegUpdate.scala | 2 +- src/main/scala/firrtl/transforms/InlineCasts.scala | 13 ++++++++++--- .../transforms/LegalizeClocksAndAsyncResets.scala | 2 +- .../firrtl/transforms/RemoveKeywordCollisions.scala | 2 +- src/main/scala/firrtl/transforms/package.scala | 3 +++ ...eCastsSpec.scala => InlineAcrossCastsSpec.scala} | 6 +++--- .../scala/firrtlTests/LoweringCompilersSpec.scala | 4 ++-- 11 files changed, 25 insertions(+), 15 deletions(-) rename src/test/scala/firrtlTests/{InlineCastsSpec.scala => InlineAcrossCastsSpec.scala} (93%) diff --git a/src/main/scala/firrtl/AddDescriptionNodes.scala b/src/main/scala/firrtl/AddDescriptionNodes.scala index 9424d4a76d..123ae6e364 100644 --- a/src/main/scala/firrtl/AddDescriptionNodes.scala +++ b/src/main/scala/firrtl/AddDescriptionNodes.scala @@ -136,7 +136,7 @@ class AddDescriptionNodes extends Transform with DependencyAPIMigration { Dependency[firrtl.transforms.ReplaceTruncatingArithmetic], Dependency[firrtl.transforms.InlineBitExtractionsTransform], Dependency[firrtl.transforms.PropagatePresetAnnotations], - Dependency[firrtl.transforms.InlineCastsTransform], + Dependency[firrtl.transforms.InlineAcrossCastsTransform], Dependency[firrtl.transforms.LegalizeClocksTransform], Dependency[firrtl.transforms.FlattenRegUpdate], Dependency(passes.VerilogModulusCleanup), diff --git a/src/main/scala/firrtl/passes/VerilogModulusCleanup.scala b/src/main/scala/firrtl/passes/VerilogModulusCleanup.scala index baad2f4f02..03dcf0a397 100644 --- a/src/main/scala/firrtl/passes/VerilogModulusCleanup.scala +++ b/src/main/scala/firrtl/passes/VerilogModulusCleanup.scala @@ -32,7 +32,7 @@ object VerilogModulusCleanup extends Pass { Dependency[firrtl.transforms.FixAddingNegativeLiterals], Dependency[firrtl.transforms.ReplaceTruncatingArithmetic], Dependency[firrtl.transforms.InlineBitExtractionsTransform], - Dependency[firrtl.transforms.InlineCastsTransform], + Dependency[firrtl.transforms.InlineAcrossCastsTransform], Dependency[firrtl.transforms.LegalizeClocksTransform], Dependency[firrtl.transforms.FlattenRegUpdate] ) diff --git a/src/main/scala/firrtl/passes/VerilogPrep.scala b/src/main/scala/firrtl/passes/VerilogPrep.scala index ed5db92efb..9499889a19 100644 --- a/src/main/scala/firrtl/passes/VerilogPrep.scala +++ b/src/main/scala/firrtl/passes/VerilogPrep.scala @@ -28,7 +28,7 @@ object VerilogPrep extends Pass { Dependency[firrtl.transforms.FixAddingNegativeLiterals], Dependency[firrtl.transforms.ReplaceTruncatingArithmetic], Dependency[firrtl.transforms.InlineBitExtractionsTransform], - Dependency[firrtl.transforms.InlineCastsTransform], + Dependency[firrtl.transforms.InlineAcrossCastsTransform], Dependency[firrtl.transforms.LegalizeClocksTransform], Dependency[firrtl.transforms.FlattenRegUpdate], Dependency(passes.VerilogModulusCleanup), diff --git a/src/main/scala/firrtl/stage/Forms.scala b/src/main/scala/firrtl/stage/Forms.scala index ab08215102..4132f75861 100644 --- a/src/main/scala/firrtl/stage/Forms.scala +++ b/src/main/scala/firrtl/stage/Forms.scala @@ -101,7 +101,7 @@ object Forms { Dependency[firrtl.transforms.FixAddingNegativeLiterals], Dependency[firrtl.transforms.ReplaceTruncatingArithmetic], Dependency[firrtl.transforms.InlineBitExtractionsTransform], - Dependency[firrtl.transforms.InlineCastsTransform], + Dependency[firrtl.transforms.InlineAcrossCastsTransform], Dependency[firrtl.transforms.LegalizeClocksTransform], Dependency[firrtl.transforms.FlattenRegUpdate], Dependency(passes.VerilogModulusCleanup), diff --git a/src/main/scala/firrtl/transforms/FlattenRegUpdate.scala b/src/main/scala/firrtl/transforms/FlattenRegUpdate.scala index 664ce1e645..3f497c91cc 100644 --- a/src/main/scala/firrtl/transforms/FlattenRegUpdate.scala +++ b/src/main/scala/firrtl/transforms/FlattenRegUpdate.scala @@ -170,7 +170,7 @@ class FlattenRegUpdate extends Transform with DependencyAPIMigration { Dependency[FixAddingNegativeLiterals], Dependency[ReplaceTruncatingArithmetic], Dependency[InlineBitExtractionsTransform], - Dependency[InlineCastsTransform], + Dependency[InlineAcrossCastsTransform], Dependency[LegalizeClocksTransform] ) diff --git a/src/main/scala/firrtl/transforms/InlineCasts.scala b/src/main/scala/firrtl/transforms/InlineCasts.scala index 761252c192..de54a326af 100644 --- a/src/main/scala/firrtl/transforms/InlineCasts.scala +++ b/src/main/scala/firrtl/transforms/InlineCasts.scala @@ -10,6 +10,7 @@ import firrtl.options.Dependency import firrtl.Utils.{isBitExtract, isCast, NodeMap} +@deprecated("Replaced by InlineAcrossCastsTransform", "FIRRTL 1.4.3") object InlineCastsTransform { // Checks if an Expression is made up of only casts terminated by a Literal or Reference @@ -54,7 +55,7 @@ object InlineCastsTransform { rec(false)(expr) } - /** Inline casts in a Statement + /** Inline across casts in a statement * * @param netlist a '''mutable''' HashMap mapping references to [[firrtl.ir.DefNode DefNode]]s to their connected * [[firrtl.ir.Expression Expression]]s. This function '''will''' mutate @@ -71,11 +72,17 @@ object InlineCastsTransform { case other => other } - /** Replaces truncating arithmetic in a Module */ + /** Inline across casts in a module */ def onMod(mod: DefModule): DefModule = mod.map(onStmt(new NodeMap)) } -/** Inline nodes that are simple casts */ +/** Inline expressions into casts and inline casts into other expressions + * + * Because casts are no-ops in the emitted Verilog, this transform eliminates statements that + * simply contain a cast. It does so by greedily building larger expression trees that contain at + * most one expression that is neither a cast nor reference-like node. + */ +@deprecated("Replaced by InlineAcrossCastsTransform", "FIRRTL 1.4.3") class InlineCastsTransform extends Transform with DependencyAPIMigration { override def prerequisites = firrtl.stage.Forms.LowFormMinimumOptimized ++ diff --git a/src/main/scala/firrtl/transforms/LegalizeClocksAndAsyncResets.scala b/src/main/scala/firrtl/transforms/LegalizeClocksAndAsyncResets.scala index 5e3d276d0d..0765a2b141 100644 --- a/src/main/scala/firrtl/transforms/LegalizeClocksAndAsyncResets.scala +++ b/src/main/scala/firrtl/transforms/LegalizeClocksAndAsyncResets.scala @@ -91,7 +91,7 @@ class LegalizeClocksAndAsyncResetsTransform extends Transform with DependencyAPI Dependency[FixAddingNegativeLiterals], Dependency[ReplaceTruncatingArithmetic], Dependency[InlineBitExtractionsTransform], - Dependency[InlineCastsTransform] + Dependency[InlineAcrossCastsTransform] ) override def optionalPrerequisites = firrtl.stage.Forms.LowFormOptimized diff --git a/src/main/scala/firrtl/transforms/RemoveKeywordCollisions.scala b/src/main/scala/firrtl/transforms/RemoveKeywordCollisions.scala index 0bf6419fb5..69d4aa8ddc 100644 --- a/src/main/scala/firrtl/transforms/RemoveKeywordCollisions.scala +++ b/src/main/scala/firrtl/transforms/RemoveKeywordCollisions.scala @@ -37,7 +37,7 @@ class VerilogRename extends RemoveKeywordCollisions(v_keywords) { Dependency[FixAddingNegativeLiterals], Dependency[ReplaceTruncatingArithmetic], Dependency[InlineBitExtractionsTransform], - Dependency[InlineCastsTransform], + Dependency[InlineAcrossCastsTransform], Dependency[LegalizeClocksTransform], Dependency[FlattenRegUpdate], Dependency(passes.VerilogModulusCleanup) diff --git a/src/main/scala/firrtl/transforms/package.scala b/src/main/scala/firrtl/transforms/package.scala index d758fa0a6b..5455690e27 100644 --- a/src/main/scala/firrtl/transforms/package.scala +++ b/src/main/scala/firrtl/transforms/package.scala @@ -3,6 +3,9 @@ package firrtl package object transforms { + type InlineAcrossCastsTransform = InlineCastsTransform + val InlineAcrossCastsTransform = InlineCastsTransform + @deprecated("Replaced by LegalizeClocksAndAsyncResetsTransform", "FIRRTL 1.4.0") type LegalizeClocksTransform = LegalizeClocksAndAsyncResetsTransform @deprecated("Replaced by LegalizeClocksAndAsyncResetsTransform", "FIRRTL 1.4.0") diff --git a/src/test/scala/firrtlTests/InlineCastsSpec.scala b/src/test/scala/firrtlTests/InlineAcrossCastsSpec.scala similarity index 93% rename from src/test/scala/firrtlTests/InlineCastsSpec.scala rename to src/test/scala/firrtlTests/InlineAcrossCastsSpec.scala index 7a248def03..669ae077ff 100644 --- a/src/test/scala/firrtlTests/InlineCastsSpec.scala +++ b/src/test/scala/firrtlTests/InlineAcrossCastsSpec.scala @@ -2,11 +2,11 @@ package firrtlTests -import firrtl.transforms.InlineCastsTransform +import firrtl.transforms.InlineAcrossCastsTransform import firrtl.testutils.FirrtlFlatSpec import firrtl.testutils.FirrtlCheckers._ -class InlineCastsEquivalenceSpec extends FirrtlFlatSpec { +class InlineAcrossCastsEquivalenceSpec extends FirrtlFlatSpec { /* * Note: InlineCasts is still part of mverilog, so this test must both: * - Test that the InlineCasts fix is effective given the current mverilog @@ -25,7 +25,7 @@ class InlineCastsEquivalenceSpec extends FirrtlFlatSpec { | output o: SInt<8> | o <= pad(asSInt(UInt<2>("h1")), 8) |""".stripMargin - firrtlEquivalenceTest(input, Seq(new InlineCastsTransform)) + firrtlEquivalenceTest(input, Seq(new InlineAcrossCastsTransform)) } it should "not inline complex expressions into other complex expressions" in { diff --git a/src/test/scala/firrtlTests/LoweringCompilersSpec.scala b/src/test/scala/firrtlTests/LoweringCompilersSpec.scala index bdc72e7b80..d56ca65743 100644 --- a/src/test/scala/firrtlTests/LoweringCompilersSpec.scala +++ b/src/test/scala/firrtlTests/LoweringCompilersSpec.scala @@ -247,7 +247,7 @@ class LoweringCompilersSpec extends AnyFlatSpec with Matchers { new firrtl.transforms.ReplaceTruncatingArithmetic, new firrtl.transforms.InlineBitExtractionsTransform, new firrtl.transforms.PropagatePresetAnnotations, - new firrtl.transforms.InlineCastsTransform, + new firrtl.transforms.InlineAcrossCastsTransform, new firrtl.transforms.LegalizeClocksTransform, new firrtl.transforms.FlattenRegUpdate, firrtl.passes.VerilogModulusCleanup, @@ -271,7 +271,7 @@ class LoweringCompilersSpec extends AnyFlatSpec with Matchers { new firrtl.transforms.ReplaceTruncatingArithmetic, new firrtl.transforms.InlineBitExtractionsTransform, new firrtl.transforms.PropagatePresetAnnotations, - new firrtl.transforms.InlineCastsTransform, + new firrtl.transforms.InlineAcrossCastsTransform, new firrtl.transforms.LegalizeClocksTransform, new firrtl.transforms.FlattenRegUpdate, new firrtl.transforms.DeadCodeElimination, From 344083ba3bdc30a25d8f2ecdf490749db9c36e4a Mon Sep 17 00:00:00 2001 From: edwardcwang Date: Sun, 11 Apr 2021 12:13:16 -0400 Subject: [PATCH 67/88] smt: use existing bitWidth API (#2175) * bitWidth: add scaladoc * smt: use existing bitWidth API --- src/main/scala/firrtl/Utils.scala | 11 +++++++++++ .../smt/FirrtlExpressionSemantics.scala | 15 ++++----------- .../smt/FirrtlToTransitionSystem.scala | 19 ++++++++++--------- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/src/main/scala/firrtl/Utils.scala b/src/main/scala/firrtl/Utils.scala index 3d0f19b80b..a58b69975d 100644 --- a/src/main/scala/firrtl/Utils.scala +++ b/src/main/scala/firrtl/Utils.scala @@ -51,7 +51,18 @@ object getWidth { def apply(e: Expression): Width = apply(e.tpe) } +/** + * Helper object for computing the width of a firrtl type. + */ object bitWidth { + + /** + * Compute the width of a firrtl type. + * For example, a Vec of 4 UInts of width 8 should have a width of 32. + * + * @param dt firrtl type + * @return Width of the given type + */ def apply(dt: Type): BigInt = widthOf(dt) private def widthOf(dt: Type): BigInt = dt match { case t: VectorType => t.size * bitWidth(t.tpe) diff --git a/src/main/scala/firrtl/backends/experimental/smt/FirrtlExpressionSemantics.scala b/src/main/scala/firrtl/backends/experimental/smt/FirrtlExpressionSemantics.scala index 13e0c31295..099b671218 100644 --- a/src/main/scala/firrtl/backends/experimental/smt/FirrtlExpressionSemantics.scala +++ b/src/main/scala/firrtl/backends/experimental/smt/FirrtlExpressionSemantics.scala @@ -8,19 +8,10 @@ import firrtl.PrimOps import firrtl.passes.CheckWidths.WidthTooBig private trait TranslationContext { - def getReference(name: String, tpe: ir.Type): BVExpr = BVSymbol(name, FirrtlExpressionSemantics.getWidth(tpe)) + def getReference(name: String, tpe: ir.Type): BVExpr = BVSymbol(name, firrtl.bitWidth(tpe).toInt) } private object FirrtlExpressionSemantics { - def getWidth(tpe: ir.Type): Int = tpe match { - case ir.UIntType(ir.IntWidth(w)) => w.toInt - case ir.SIntType(ir.IntWidth(w)) => w.toInt - case ir.ClockType => 1 - case ir.ResetType => 1 - case ir.AnalogType(ir.IntWidth(w)) => w.toInt - case other => throw new RuntimeException(s"Cannot handle type $other") - } - def toSMT(e: ir.Expression)(implicit ctx: TranslationContext): BVExpr = { val eSMT = e match { case ir.DoPrim(op, args, consts, _) => onPrim(op, args, consts) @@ -183,5 +174,7 @@ private object FirrtlExpressionSemantics { case _: ir.SIntType => true case _ => false } - private def getWidth(e: ir.Expression): Int = getWidth(e.tpe) + + // Helper function + private def getWidth(e: ir.Expression): Int = firrtl.bitWidth(e.tpe).toInt } diff --git a/src/main/scala/firrtl/backends/experimental/smt/FirrtlToTransitionSystem.scala b/src/main/scala/firrtl/backends/experimental/smt/FirrtlToTransitionSystem.scala index fea92c7587..cfab61b99c 100644 --- a/src/main/scala/firrtl/backends/experimental/smt/FirrtlToTransitionSystem.scala +++ b/src/main/scala/firrtl/backends/experimental/smt/FirrtlToTransitionSystem.scala @@ -4,6 +4,7 @@ package firrtl.backends.experimental.smt import firrtl.annotations.{MemoryInitAnnotation, NoTargetAnnotation, PresetRegAnnotation} +import firrtl.bitWidth import FirrtlExpressionSemantics.getWidth import firrtl.backends.experimental.smt.random._ import firrtl.graph.MutableDiGraph @@ -258,7 +259,7 @@ private class ModuleToTransitionSystem extends LazyLogging { val inputs = connects.filter(_._1.startsWith(m.name)).toMap // derive the type of the memory from the dataType and depth - val dataWidth = getWidth(m.dataType) + val dataWidth = bitWidth(m.dataType).toInt val indexWidth = Utils.getUIntWidth(m.depth - 1).max(1) val memSymbol = ArraySymbol(m.name, indexWidth, dataWidth) @@ -391,7 +392,7 @@ private class ModuleScanner( if (isClock(p.tpe)) { clocks.add(p.name) } else { - inputs.append(BVSymbol(p.name, getWidth(p.tpe))) + inputs.append(BVSymbol(p.name, bitWidth(p.tpe).toInt)) } case ir.Output => if (!isClock(p.tpe)) { // we ignore clock outputs @@ -406,7 +407,7 @@ private class ModuleScanner( assert(!isClock(tpe), "rand should never be a clock!") // we model random sources as inputs and ignore the enable signal infos.append(name -> info) - inputs.append(BVSymbol(name, getWidth(tpe))) + inputs.append(BVSymbol(name, bitWidth(tpe).toInt)) case ir.DefWire(info, name, tpe) => namespace.newName(name) if (!isClock(tpe)) { @@ -427,7 +428,7 @@ private class ModuleScanner( insertDummyAssignsForUnusedOutputs(reset) insertDummyAssignsForUnusedOutputs(init) infos.append(name -> info) - val width = getWidth(tpe) + val width = bitWidth(tpe).toInt val resetExpr = onExpression(reset, 1) val initExpr = onExpression(init, width) registers.append((name, width, resetExpr, initExpr)) @@ -436,7 +437,7 @@ private class ModuleScanner( infos.append(m.name -> m.info) val outputs = getMemOutputs(m) (getMemInputs(m) ++ outputs).foreach(memSignals.append(_)) - val dataWidth = getWidth(m.dataType) + val dataWidth = bitWidth(m.dataType).toInt outputs.foreach(name => unusedOutputs(name) = BVSymbol(name, dataWidth)) memories.append(m) case ir.Connect(info, loc, expr) => @@ -445,7 +446,7 @@ private class ModuleScanner( val name = loc.serialize insertDummyAssignsForUnusedOutputs(expr) infos.append(name -> info) - connects.append((name, onExpression(expr, getWidth(loc.tpe)))) + connects.append((name, onExpression(expr, bitWidth(loc.tpe).toInt))) } case i @ ir.IsInvalid(info, loc) => if (!isGroundType(loc.tpe)) error("All connects should have been lowered to ground type!") @@ -507,7 +508,7 @@ private class ModuleScanner( if (isClock(p.tpe)) { clocks.add(pName) } else { - inputs.append(BVSymbol(pName, getWidth(p.tpe))) + inputs.append(BVSymbol(pName, bitWidth(p.tpe).toInt)) } } else { if (!isClock(p.tpe)) { // we ignore clock outputs @@ -524,8 +525,8 @@ private class ModuleScanner( // sanity checks for ports were done already using the UninterpretedModule.checkModule function val ports = tpe.asInstanceOf[ir.BundleType].fields - val outputs = ports.filter(_.flip == ir.Default).map(p => BVSymbol(p.name, getWidth(p.tpe))) - val inputs = ports.filterNot(_.flip == ir.Default).map(p => BVSymbol(p.name, getWidth(p.tpe))) + val outputs = ports.filter(_.flip == ir.Default).map(p => BVSymbol(p.name, bitWidth(p.tpe).toInt)) + val inputs = ports.filterNot(_.flip == ir.Default).map(p => BVSymbol(p.name, bitWidth(p.tpe).toInt)) assert(anno.stateBits == 0, "TODO: implement support for uninterpreted stateful modules!") From 20890bbd6bef2f33adace433a2d01f89458f0466 Mon Sep 17 00:00:00 2001 From: Jared Barocsi <82000041+jared-barocsi@users.noreply.github.com> Date: Tue, 13 Apr 2021 17:11:10 -0700 Subject: [PATCH 68/88] Add indent parameter to Serializer.serialize() (#2177) Using Utils.indent() gives deprecation warnings to use Serializer instead. However, the Serializer class itself doesn't provide a means to manually indent a FirrtlNode string a certain number of times. The indent variable, previously hardcoded to 0, is now exposed as a second parameter for the modified serialize function, and the old serialize function just calls the modified serialize with indents = 0 for binary compatibility Co-authored-by: Megan Wachs --- src/main/scala/firrtl/ir/Serializer.scala | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/scala/firrtl/ir/Serializer.scala b/src/main/scala/firrtl/ir/Serializer.scala index dca902feef..1c2bfc82a6 100644 --- a/src/main/scala/firrtl/ir/Serializer.scala +++ b/src/main/scala/firrtl/ir/Serializer.scala @@ -10,10 +10,16 @@ object Serializer { val NewLine = '\n' val Indent = " " - /** Converts a `FirrtlNode` into its string representation. */ + /** Converts a `FirrtlNode` into its string representation with + * default indentation. + */ def serialize(node: FirrtlNode): String = { + serialize(node, 0) + } + + /** Converts a `FirrtlNode` into its string representation. */ + def serialize(node: FirrtlNode, indent: Int): String = { val builder = new StringBuilder() - val indent = 0 node match { case n: Info => s(n)(builder, indent) case n: StringLit => s(n)(builder, indent) From fc86112bc09f3e804d76e329fea96acb70e4909d Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Thu, 15 Apr 2021 16:45:38 -0700 Subject: [PATCH 69/88] Add Workflow to automatically update .mergify.yml (#2180) Also make minor updates to CI workflow --- .github/configs/mergify_config.yml | 7 ++++ .github/workflows/test.yml | 4 +-- .github/workflows/update_mergify.yml | 53 ++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 .github/configs/mergify_config.yml create mode 100644 .github/workflows/update_mergify.yml diff --git a/.github/configs/mergify_config.yml b/.github/configs/mergify_config.yml new file mode 100644 index 0000000000..90bf4ad54c --- /dev/null +++ b/.github/configs/mergify_config.yml @@ -0,0 +1,7 @@ +# Configuration for generating .mergify.yml +conditions: + - status-success=all tests passed +branches: + - 1.2.x + - 1.3.x + - 1.4.x diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4e0c81259e..d04f7e05c6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -30,7 +30,7 @@ jobs: with: java-version: adopt@1.8 - name: Cache Scala - uses: coursier/cache-action@v5 + uses: coursier/cache-action@v6 - name: Check Formatting (Scala 2.12 only) if: startsWith(matrix.scala, '2.12') run: sbt ++${{ matrix.scala }} scalafmtCheckAll @@ -95,7 +95,7 @@ jobs: # When adding new jobs, please add them to `needs` below all_tests_passed: name: "all tests passed" - needs: [test, equiv] + needs: [test, mill, equiv] runs-on: ubuntu-latest steps: - run: echo Success! diff --git a/.github/workflows/update_mergify.yml b/.github/workflows/update_mergify.yml new file mode 100644 index 0000000000..d2067a42de --- /dev/null +++ b/.github/workflows/update_mergify.yml @@ -0,0 +1,53 @@ +name: Update .mergify.yml +on: + schedule: + # Runs once an hour + - cron: "0 * * * *" + +jobs: + update-mergify: + name: Update .mergify.yml + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Checkout Chisel Repo Tools + uses: actions/checkout@v2 + with: + repository: ucb-bar/chisel-repo-tools + path: tools + - name: Setup Scala + uses: olafurpg/setup-scala@v10 + - name: Install Ammonite + run: sudo sh -c '(echo "#!/usr/bin/env sh" && curl -L https://github.com/com-lihaoyi/Ammonite/releases/download/2.3.8/2.13-2.3.8) > /usr/local/bin/amm && chmod +x /usr/local/bin/amm' + - name: Cache Scala + uses: coursier/cache-action@v6 + - name: Generate .mergify.yml + id: gen + run: | + ./tools/scripts/mergify.sc .github/configs/mergify_config.yml > .mergify.yml + diff=$(git diff -- .mergify.yml) + echo "::set-output name=diff::$diff" + - name: Commit and Push + if: ${{ steps.gen.outputs.diff }} + run: | + BRANCH="actions/workflows/update-mergify" + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + git checkout -B $BRANCH + git add .mergify.yml + git commit -m "Update .mergify.yml" + git push --follow-tags --force --set-upstream origin $BRANCH + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Open/Update Pull Request + if: ${{ steps.gen.outputs.diff }} + uses: vsoch/pull-request-action@1.0.15 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PULL_REQUEST_FROM_BRANCH: actions/workflows/update-mergify + PULL_REQUEST_BRANCH: master + PULL_REQUEST_TITLE: "Update .mergify.yml" + PULL_REQUEST_BODY: "This is an automated pull request by \"Update .mergify.yml\" workflow" + PASS_IF_EXISTS: true + From e9b2946c962f91a04611e32b1a9d03f78e7edf2b Mon Sep 17 00:00:00 2001 From: Fabian Schuiki Date: Fri, 16 Apr 2021 16:18:32 +0200 Subject: [PATCH 70/88] Fix signedness of xor const prop with zero (#2179) Constant propagation of the Xor op folds `xor(a, SInt(0))` to `asUInt(a)`. For comparison, Or folds to `asUInt(pad(a, W))`. This can be a problem in the following case: circuit Foo : module Foo : input a: UInt<3> output b: UInt<4> b <= asUInt(xor(asSInt(a), SInt<4>(0))) This would emit the assignment as `b = a` instead of the sign-extended `b = {{1{a[2]}},a}`. This requires adjusting the `pad(e, t)` function use in const prop, which currently just inserts a `Pad` prim op with the requested output type. However, the function advertises that it pads *to the width* of the type `t`. Some of the folds rely on this and request the padding of a SInt to the width of a UInt. But the current implementation then then actually returns a `Pad` op with type UInt, instead of the SInt that was requested. --- .../firrtl/transforms/ConstantPropagation.scala | 14 ++++++++++++-- .../firrtlTests/ConstantPropagationTests.scala | 7 +++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/main/scala/firrtl/transforms/ConstantPropagation.scala b/src/main/scala/firrtl/transforms/ConstantPropagation.scala index 5610c7e762..bc1fc9af7e 100644 --- a/src/main/scala/firrtl/transforms/ConstantPropagation.scala +++ b/src/main/scala/firrtl/transforms/ConstantPropagation.scala @@ -29,7 +29,17 @@ object ConstantPropagation { /** Pads e to the width of t */ def pad(e: Expression, t: Type) = (bitWidth(e.tpe), bitWidth(t)) match { - case (we, wt) if we < wt => DoPrim(Pad, Seq(e), Seq(wt), t) + case (we, wt) if we < wt => + DoPrim( + Pad, + Seq(e), + Seq(wt), + e.tpe match { + case UIntType(_) => UIntType(IntWidth(wt)) + case SIntType(_) => SIntType(IntWidth(wt)) + case _ => e.tpe + } + ) case (we, wt) if we == wt => e } @@ -252,7 +262,7 @@ class ConstantPropagation extends Transform with RegisteredTransform with Depend } def simplify(e: Expression, lhs: Literal, rhs: Expression) = lhs match { case UIntLiteral(v, _) if v == BigInt(0) => rhs - case SIntLiteral(v, _) if v == BigInt(0) => asUInt(rhs, e.tpe) + case SIntLiteral(v, _) if v == BigInt(0) => asUInt(pad(rhs, e.tpe), e.tpe) case _ => e } def matchingArgsValue(e: DoPrim, arg: Expression) = UIntLiteral(0, getWidth(arg.tpe)) diff --git a/src/test/scala/firrtlTests/ConstantPropagationTests.scala b/src/test/scala/firrtlTests/ConstantPropagationTests.scala index bc7f92e638..ababb95b9c 100644 --- a/src/test/scala/firrtlTests/ConstantPropagationTests.scala +++ b/src/test/scala/firrtlTests/ConstantPropagationTests.scala @@ -1531,22 +1531,29 @@ class ConstantPropagationIntegrationSpec extends LowTransformSpec { val input = s"""|circuit Foo: | module Foo: + | input in1: SInt<3> | output out1: UInt<2> | output out2: UInt<2> | output out3: UInt<2> + | output out4: UInt<4> | out1 <= xor(SInt<2>(-1), SInt<2>(1)) | out2 <= or(SInt<2>(-1), SInt<2>(1)) | out3 <= and(SInt<2>(-1), SInt<2>(-2)) + | out4 <= xor(in1, SInt<4>(0)) |""".stripMargin val check = s"""|circuit Foo: | module Foo: + | input in1: SInt<3> | output out1: UInt<2> | output out2: UInt<2> | output out3: UInt<2> + | output out4: UInt<4> | out1 <= UInt<2>(2) | out2 <= UInt<2>(3) | out3 <= UInt<2>(2) + | node _GEN_0 = pad(in1, 4) + | out4 <= asUInt(_GEN_0) |""".stripMargin execute(input, check, Seq.empty) } From bf1cf3d2db49195d031f89594baebcc9f307659e Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Fri, 16 Apr 2021 11:41:07 -0700 Subject: [PATCH 71/88] Make InferTypes error on enable conditions > 1-bit wide (#2182) --- src/main/scala/firrtl/passes/CheckTypes.scala | 33 +++--- src/test/scala/firrtlTests/CheckSpec.scala | 100 ++++++++++++++++++ .../scala/firrtlTests/LowerTypesSpec.scala | 4 +- .../scala/firrtlTests/ReplSeqMemTests.scala | 4 +- 4 files changed, 125 insertions(+), 16 deletions(-) diff --git a/src/main/scala/firrtl/passes/CheckTypes.scala b/src/main/scala/firrtl/passes/CheckTypes.scala index f70db14833..50fbfc2e5b 100644 --- a/src/main/scala/firrtl/passes/CheckTypes.scala +++ b/src/main/scala/firrtl/passes/CheckTypes.scala @@ -55,9 +55,9 @@ object CheckTypes extends Pass { class RegReqClk(info: Info, mname: String, name: String) extends PassException(s"$info: [module $mname] Register $name requires a clock typed signal.") class EnNotUInt(info: Info, mname: String) - extends PassException(s"$info: [module $mname] Enable must be a UIntType typed signal.") + extends PassException(s"$info: [module $mname] Enable must be a 1-bit UIntType typed signal.") class PredNotUInt(info: Info, mname: String) - extends PassException(s"$info: [module $mname] Predicate not a UIntType.") + extends PassException(s"$info: [module $mname] Predicate not a 1-bit UIntType.") class OpNotGround(info: Info, mname: String, op: String) extends PassException(s"$info: [module $mname] Primop $op cannot operate on non-ground types.") class OpNotUInt(info: Info, mname: String, op: String, e: String) @@ -81,7 +81,7 @@ object CheckTypes extends Pass { class MuxPassiveTypes(info: Info, mname: String) extends PassException(s"$info: [module $mname] Must mux between passive types.") class MuxCondUInt(info: Info, mname: String) - extends PassException(s"$info: [module $mname] A mux condition must be of type UInt.") + extends PassException(s"$info: [module $mname] A mux condition must be of type 1-bit UInt.") class MuxClock(info: Info, mname: String) extends PassException(s"$info: [module $mname] Firrtl does not support muxing clocks.") class ValidIfPassiveTypes(info: Info, mname: String) @@ -120,6 +120,15 @@ object CheckTypes extends Pass { case _ => false } + private def legalCondType(tpe: Type): Boolean = tpe match { + // If width is known, must be 1 + case UIntType(IntWidth(w)) => w == 1 + // Unknown width or variable widths (for width inference) are acceptable (checked in later run) + case UIntType(_) => true + // Any other type is not okay + case _ => false + } + private def bulk_equals(t1: Type, t2: Type, flip1: Orientation, flip2: Orientation): Boolean = { (t1, t2) match { case (ClockType, ClockType) => flip1 == flip2 @@ -165,7 +174,8 @@ object CheckTypes extends Pass { bulk_equals(con.loc.tpe, con.expr.tpe, Default, Default) //;---------------- Helper Functions -------------- - def ut: UIntType = UIntType(UnknownWidth) + private val UIntUnknown = UIntType(UnknownWidth) + def ut: UIntType = UIntUnknown def st: SIntType = SIntType(UnknownWidth) def run(c: Circuit): Circuit = { @@ -332,9 +342,8 @@ object CheckTypes extends Pass { errors.append(new MuxSameType(info, mname, e.tval.tpe.serialize, e.fval.tpe.serialize)) if (!passive(e.tpe)) errors.append(new MuxPassiveTypes(info, mname)) - e.cond.tpe match { - case _: UIntType => - case _ => errors.append(new MuxCondUInt(info, mname)) + if (!legalCondType(e.cond.tpe)) { + errors.append(new MuxCondUInt(info, mname)) } case (e: ValidIf) => if (!passive(e.tpe)) @@ -375,7 +384,7 @@ object CheckTypes extends Pass { if (sx.clock.tpe != ClockType) { errors.append(new RegReqClk(info, mname, sx.name)) } - case sx: Conditionally if wt(sx.pred.tpe) != wt(ut) => + case sx: Conditionally if !legalCondType(sx.pred.tpe) => errors.append(new PredNotUInt(info, mname)) case sx: DefNode => sx.value.tpe match { @@ -396,16 +405,16 @@ object CheckTypes extends Pass { } case sx: Stop => if (wt(sx.clk.tpe) != wt(ClockType)) errors.append(new ReqClk(info, mname)) - if (wt(sx.en.tpe) != wt(ut)) errors.append(new EnNotUInt(info, mname)) + if (!legalCondType(sx.en.tpe)) errors.append(new EnNotUInt(info, mname)) case sx: Print => if (sx.args.exists(x => wt(x.tpe) != wt(ut) && wt(x.tpe) != wt(st))) errors.append(new PrintfArgNotGround(info, mname)) if (wt(sx.clk.tpe) != wt(ClockType)) errors.append(new ReqClk(info, mname)) - if (wt(sx.en.tpe) != wt(ut)) errors.append(new EnNotUInt(info, mname)) + if (!legalCondType(sx.en.tpe)) errors.append(new EnNotUInt(info, mname)) case sx: Verification => if (wt(sx.clk.tpe) != wt(ClockType)) errors.append(new ReqClk(info, mname)) - if (wt(sx.pred.tpe) != wt(ut)) errors.append(new PredNotUInt(info, mname)) - if (wt(sx.en.tpe) != wt(ut)) errors.append(new EnNotUInt(info, mname)) + if (!legalCondType(sx.pred.tpe)) errors.append(new PredNotUInt(info, mname)) + if (!legalCondType(sx.en.tpe)) errors.append(new EnNotUInt(info, mname)) case sx: DefMemory => sx.dataType match { case AnalogType(w) => errors.append(new IllegalAnalogDeclaration(info, mname, sx.name)) diff --git a/src/test/scala/firrtlTests/CheckSpec.scala b/src/test/scala/firrtlTests/CheckSpec.scala index a3efc78410..547639d609 100644 --- a/src/test/scala/firrtlTests/CheckSpec.scala +++ b/src/test/scala/firrtlTests/CheckSpec.scala @@ -86,6 +86,106 @@ class CheckSpec extends AnyFlatSpec with Matchers { } } + behavior.of("Check Types") + + def runCheckTypes(input: String) = { + val passes = List(InferTypes, CheckTypes) + val wrapped = "circuit test:\n module test:\n " + input.replaceAll("\n", "\n ") + passes.foldLeft(Parser.parse(wrapped)) { case (c, p) => p.run(c) } + } + + it should "disallow mux enable conditions that are not 1-bit UInts (or unknown width)" in { + def mk(tpe: String) = + s"""|input en : $tpe + |input foo : UInt<8> + |input bar : UInt<8> + |node x = mux(en, foo, bar)""".stripMargin + a[CheckTypes.MuxCondUInt] shouldBe thrownBy { runCheckTypes(mk("SInt<1>")) } + a[CheckTypes.MuxCondUInt] shouldBe thrownBy { runCheckTypes(mk("SInt")) } + a[CheckTypes.MuxCondUInt] shouldBe thrownBy { runCheckTypes(mk("UInt<3>")) } + a[CheckTypes.MuxCondUInt] shouldBe thrownBy { runCheckTypes(mk("Clock")) } + a[CheckTypes.MuxCondUInt] shouldBe thrownBy { runCheckTypes(mk("AsyncReset")) } + runCheckTypes(mk("UInt")) + runCheckTypes(mk("UInt<1>")) + } + + it should "disallow when predicates that are not 1-bit UInts (or unknown width)" in { + def mk(tpe: String) = + s"""|input en : $tpe + |input foo : UInt<8> + |input bar : UInt<8> + |output out : UInt<8> + |when en : + | out <= foo + |else: + | out <= bar""".stripMargin + a[CheckTypes.PredNotUInt] shouldBe thrownBy { runCheckTypes(mk("SInt<1>")) } + a[CheckTypes.PredNotUInt] shouldBe thrownBy { runCheckTypes(mk("SInt")) } + a[CheckTypes.PredNotUInt] shouldBe thrownBy { runCheckTypes(mk("UInt<3>")) } + a[CheckTypes.PredNotUInt] shouldBe thrownBy { runCheckTypes(mk("Clock")) } + a[CheckTypes.PredNotUInt] shouldBe thrownBy { runCheckTypes(mk("AsyncReset")) } + runCheckTypes(mk("UInt")) + runCheckTypes(mk("UInt<1>")) + } + + it should "disallow print enables that are not 1-bit UInts (or unknown width)" in { + def mk(tpe: String) = + s"""|input en : $tpe + |input clock : Clock + |printf(clock, en, "Hello World!\\n")""".stripMargin + a[CheckTypes.EnNotUInt] shouldBe thrownBy { runCheckTypes(mk("SInt<1>")) } + a[CheckTypes.EnNotUInt] shouldBe thrownBy { runCheckTypes(mk("SInt")) } + a[CheckTypes.EnNotUInt] shouldBe thrownBy { runCheckTypes(mk("UInt<3>")) } + a[CheckTypes.EnNotUInt] shouldBe thrownBy { runCheckTypes(mk("Clock")) } + a[CheckTypes.EnNotUInt] shouldBe thrownBy { runCheckTypes(mk("AsyncReset")) } + runCheckTypes(mk("UInt")) + runCheckTypes(mk("UInt<1>")) + } + + it should "disallow stop enables that are not 1-bit UInts (or unknown width)" in { + def mk(tpe: String) = + s"""|input en : $tpe + |input clock : Clock + |stop(clock, en, 0)""".stripMargin + a[CheckTypes.EnNotUInt] shouldBe thrownBy { runCheckTypes(mk("SInt<1>")) } + a[CheckTypes.EnNotUInt] shouldBe thrownBy { runCheckTypes(mk("SInt")) } + a[CheckTypes.EnNotUInt] shouldBe thrownBy { runCheckTypes(mk("UInt<3>")) } + a[CheckTypes.EnNotUInt] shouldBe thrownBy { runCheckTypes(mk("Clock")) } + a[CheckTypes.EnNotUInt] shouldBe thrownBy { runCheckTypes(mk("AsyncReset")) } + runCheckTypes(mk("UInt")) + runCheckTypes(mk("UInt<1>")) + } + + it should "disallow verif node predicates that are not 1-bit UInts (or unknown width)" in { + def mk(tpe: String) = + s"""|input en : $tpe + |input cond : UInt<1> + |input clock : Clock + |assert(clock, en, cond, "Howdy!")""".stripMargin + a[CheckTypes.PredNotUInt] shouldBe thrownBy { runCheckTypes(mk("SInt<1>")) } + a[CheckTypes.PredNotUInt] shouldBe thrownBy { runCheckTypes(mk("SInt")) } + a[CheckTypes.PredNotUInt] shouldBe thrownBy { runCheckTypes(mk("UInt<3>")) } + a[CheckTypes.PredNotUInt] shouldBe thrownBy { runCheckTypes(mk("Clock")) } + a[CheckTypes.PredNotUInt] shouldBe thrownBy { runCheckTypes(mk("AsyncReset")) } + runCheckTypes(mk("UInt")) + runCheckTypes(mk("UInt<1>")) + } + + it should "disallow verif node enables that are not 1-bit UInts (or unknown width)" in { + def mk(tpe: String) = + s"""|input en : UInt<1> + |input cond : $tpe + |input clock : Clock + |assert(clock, en, cond, "Howdy!")""".stripMargin + a[CheckTypes.EnNotUInt] shouldBe thrownBy { runCheckTypes(mk("SInt<1>")) } + a[CheckTypes.EnNotUInt] shouldBe thrownBy { runCheckTypes(mk("SInt")) } + a[CheckTypes.EnNotUInt] shouldBe thrownBy { runCheckTypes(mk("UInt<3>")) } + a[CheckTypes.EnNotUInt] shouldBe thrownBy { runCheckTypes(mk("Clock")) } + a[CheckTypes.EnNotUInt] shouldBe thrownBy { runCheckTypes(mk("AsyncReset")) } + runCheckTypes(mk("UInt")) + runCheckTypes(mk("UInt<1>")) + } + "Instance loops a -> b -> a" should "be detected" in { val input = """ diff --git a/src/test/scala/firrtlTests/LowerTypesSpec.scala b/src/test/scala/firrtlTests/LowerTypesSpec.scala index 78d03e68ac..6e774d181c 100644 --- a/src/test/scala/firrtlTests/LowerTypesSpec.scala +++ b/src/test/scala/firrtlTests/LowerTypesSpec.scala @@ -466,10 +466,10 @@ class LowerTypesUniquifySpec extends FirrtlFlatSpec { | input a : { b : UInt<1>, flip c : { d : UInt<2>, e : UInt<3>}[2], c_1_e : UInt<4>}[2] | output a_0_b : UInt<1> | input a__0_c_ : { d : UInt<2>, e : UInt<3>}[2] - | a_0_b <= mux(a[UInt(0)].c_1_e, or(a[or(a[0].b, a[1].b)].b, xorr(a[0].c_1_e)), orr(cat(a__0_c_[0].e, a[1].c_1_e))) + | a_0_b <= mux(bits(a[UInt(0)].c_1_e, 0, 0), or(a[or(a[0].b, a[1].b)].b, xorr(a[0].c_1_e)), orr(cat(a__0_c_[0].e, a[1].c_1_e))) """.stripMargin val expected = Seq( - "a_0_b <= mux(a___0_c_1_e, or(_a_or_b, xorr(a___0_c_1_e)), orr(cat(a__0_c__0_e, a___1_c_1_e)))" + "a_0_b <= mux(bits(a___0_c_1_e, 0, 0), or(_a_or_b, xorr(a___0_c_1_e)), orr(cat(a__0_c__0_e, a___1_c_1_e)))" ) executeTest(input, expected) diff --git a/src/test/scala/firrtlTests/ReplSeqMemTests.scala b/src/test/scala/firrtlTests/ReplSeqMemTests.scala index d21f80c8c6..2156e39223 100644 --- a/src/test/scala/firrtlTests/ReplSeqMemTests.scala +++ b/src/test/scala/firrtlTests/ReplSeqMemTests.scala @@ -422,7 +422,7 @@ circuit CustomMemory : circuit CustomMemory : module CustomMemory : input clock : Clock - output io : { flip en : UInt<1>, out : UInt<8>[2], flip raddr : UInt<10>, flip waddr : UInt<10>, flip wdata : UInt<8>[2], flip mask : UInt<8>[2] } + output io : { flip en : UInt<1>, out : UInt<8>[2], flip raddr : UInt<10>, flip waddr : UInt<10>, flip wdata : UInt<8>[2], flip mask : UInt<1>[2] } smem mem : UInt<8>[2][1024] read mport r = mem[io.raddr], clock @@ -452,7 +452,7 @@ circuit CustomMemory : circuit CustomMemory : module CustomMemory : input clock : Clock - output io : { flip en : UInt<1>, out : UInt<8>[2], flip raddr : UInt<10>, flip waddr : UInt<10>, flip wdata : UInt<8>[2], flip mask : UInt<8>[2] } + output io : { flip en : UInt<1>, out : UInt<8>[2], flip raddr : UInt<10>, flip waddr : UInt<10>, flip wdata : UInt<8>[2], flip mask : UInt<1>[2] } io.out is invalid From 72afdefd0f2e894d5dfe346aae36a23b2fd25def Mon Sep 17 00:00:00 2001 From: Albert Magyar Date: Mon, 19 Apr 2021 08:50:18 -0700 Subject: [PATCH 72/88] Don't use declaration-assigns for wires representing mem ports (#2189) * Fixes #2173 --- .../backends/verilog/VerilogEmitter.scala | 29 ++++++++++--------- src/test/scala/firrtlTests/InfoSpec.scala | 10 +++---- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/main/scala/firrtl/backends/verilog/VerilogEmitter.scala b/src/main/scala/firrtl/backends/verilog/VerilogEmitter.scala index 66df12e03a..13f7b7933f 100644 --- a/src/main/scala/firrtl/backends/verilog/VerilogEmitter.scala +++ b/src/main/scala/firrtl/backends/verilog/VerilogEmitter.scala @@ -1076,8 +1076,11 @@ class VerilogEmitter extends SeqTransform with Emitter { "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) + def createMemWire(firrtlRef: Expression, rhs: InfoExpr): Unit = { + // Don't use declaration-assignment, since this assignment might be emitted earlier than the + // actual connection to the memory port field in the source FIRRTL + declare("wire", LowerTypes.loweredName(firrtlRef), firrtlRef.tpe, MultiInfo(sx.info, rhs.info)) + assign(firrtlRef, rhs) } for (r <- sx.readers) { @@ -1090,7 +1093,7 @@ class VerilogEmitter extends SeqTransform with Emitter { val clkSource = netlist(memPortField(sx, r, "clk")).expr - createMemWire(LowerTypes.loweredName(en), en.tpe, netlist(en)) + createMemWire(en, netlist(en)) if (sx.readLatency == 1 && sx.readUnderWrite != ReadUnderWrite.Old) { val InfoExpr(addrInfo, addrDriver) = netlist(addr) @@ -1098,7 +1101,7 @@ class VerilogEmitter extends SeqTransform with Emitter { initialize(WRef(LowerTypes.loweredName(addr), addr.tpe), zero, zero) update(addr, addrDriver, clkSource, en, addrInfo) } else { - createMemWire(LowerTypes.loweredName(addr), addr.tpe, netlist(addr)) + createMemWire(addr, netlist(addr)) } if (sx.readLatency == 1 && sx.readUnderWrite == ReadUnderWrite.Old) { @@ -1122,10 +1125,10 @@ class VerilogEmitter extends SeqTransform with Emitter { 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)) + createMemWire(data, netlist(data)) + createMemWire(addr, netlist(addr)) + createMemWire(mask, netlist(mask)) + createMemWire(en, netlist(en)) val memPort = WSubAccess(WRef(sx), addr, sx.dataType, UnknownFlow) update(memPort, data, clkSource, AND(en, mask), sx.info) @@ -1142,11 +1145,11 @@ class VerilogEmitter extends SeqTransform with Emitter { 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)) + createMemWire(wdata, netlist(wdata)) + createMemWire(addr, netlist(addr)) + createMemWire(wmode, netlist(wmode)) + createMemWire(wmask, netlist(wmask)) + createMemWire(en, netlist(en)) declare("reg", LowerTypes.loweredName(rdata), rdata.tpe, sx.info) initialize(WRef(LowerTypes.loweredName(rdata), rdata.tpe), zero, zero) diff --git a/src/test/scala/firrtlTests/InfoSpec.scala b/src/test/scala/firrtlTests/InfoSpec.scala index db4828f680..43fb6ee1c7 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 = 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"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"assign m_r_data = m[m_r_addr]; //$Info1") result should containLine(s"m[m_w_addr] <= m_w_data; //$Info1") } From 9c868ca02e5eb99bc317f92fd75252e0ab9fb7a2 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Mon, 19 Apr 2021 13:58:21 -0700 Subject: [PATCH 73/88] Simplify "update .mergify.yml" workflow (#2192) --- .github/workflows/update_mergify.yml | 42 +++++++++++----------------- 1 file changed, 17 insertions(+), 25 deletions(-) diff --git a/.github/workflows/update_mergify.yml b/.github/workflows/update_mergify.yml index d2067a42de..fbcdebd743 100644 --- a/.github/workflows/update_mergify.yml +++ b/.github/workflows/update_mergify.yml @@ -11,6 +11,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@v2 + with: + ref: master - name: Checkout Chisel Repo Tools uses: actions/checkout@v2 with: @@ -23,31 +25,21 @@ jobs: - name: Cache Scala uses: coursier/cache-action@v6 - name: Generate .mergify.yml - id: gen run: | ./tools/scripts/mergify.sc .github/configs/mergify_config.yml > .mergify.yml - diff=$(git diff -- .mergify.yml) - echo "::set-output name=diff::$diff" - - name: Commit and Push - if: ${{ steps.gen.outputs.diff }} - run: | - BRANCH="actions/workflows/update-mergify" - git config --local user.email "action@github.com" - git config --local user.name "GitHub Action" - git checkout -B $BRANCH - git add .mergify.yml - git commit -m "Update .mergify.yml" - git push --follow-tags --force --set-upstream origin $BRANCH - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Open/Update Pull Request - if: ${{ steps.gen.outputs.diff }} - uses: vsoch/pull-request-action@1.0.15 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PULL_REQUEST_FROM_BRANCH: actions/workflows/update-mergify - PULL_REQUEST_BRANCH: master - PULL_REQUEST_TITLE: "Update .mergify.yml" - PULL_REQUEST_BODY: "This is an automated pull request by \"Update .mergify.yml\" workflow" - PASS_IF_EXISTS: true + # Delete tools so they don't get included in commit + rm -rf tools/ + - name: Create Pull Request + uses: peter-evans/create-pull-request@v3.8.2 + with: + # TODO Default GITHUB_TOKEN cannot trigger PR CI + token: ${{ secrets.GITHUB_TOKEN }} + commit-message: Update .mergify.yml + branch: actions/workflows/update-mergify + delete-branch: true + title: Update .mergify.yml + body: | + This is an automated pull request by "Update .mergify.yml" workflow created with [create-pull-request][1]. + + [1]: https://github.com/peter-evans/create-pull-request From 0a1aa5f56fe5eb563de7c33faa8eae33caa65441 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Mon, 19 Apr 2021 14:11:21 -0700 Subject: [PATCH 74/88] Hoist Transform timing to the Phase level (#2190) With Stage/Phase, users can provide complex functionality at the phase level rather than just the transform level. It is useful to have the same logging information at that level. Note that this change still logs transforms in the same way, but now the time in inclusive of annotation renaming which can also [unfortunately] be slow. Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- src/main/scala/firrtl/Compiler.scala | 16 +--------------- .../scala/firrtl/options/DependencyManager.scala | 9 ++++++++- .../stage/transforms/UpdateAnnotations.scala | 2 +- src/main/scala/logger/Logger.scala | 1 + 4 files changed, 11 insertions(+), 17 deletions(-) diff --git a/src/main/scala/firrtl/Compiler.scala b/src/main/scala/firrtl/Compiler.scala index 38b71f4a4d..2998af3c6e 100644 --- a/src/main/scala/firrtl/Compiler.scala +++ b/src/main/scala/firrtl/Compiler.scala @@ -210,22 +210,9 @@ final case object UnknownForm extends CircuitForm(-1) { // Internal utilities to keep code DRY, not a clean interface private[firrtl] object Transform { - // Run transform with logging - def runTransform(name: String, mk: => CircuitState, logger: Logger): CircuitState = { - logger.info(s"======== Starting Transform $name ========") - - val (timeMillis, result) = Utils.time(mk) - - logger.info(s"""----------------------------${"-" * name.size}---------\n""") - logger.info(f"Time: $timeMillis%.1f ms") - - result - } - def remapAnnotations(name: String, before: CircuitState, after: CircuitState, logger: Logger): CircuitState = { val remappedAnnotations = propagateAnnotations(name, logger, before.annotations, after.annotations, after.renames) - logger.info(s"Form: ${after.form}") logger.trace(s"Annotations:") logger.trace { JsonProtocol @@ -240,7 +227,6 @@ private[firrtl] object Transform { } logger.trace(s"Circuit:\n${after.circuit.serialize}") - logger.info(s"======== Finished Transform $name ========\n") CircuitState(after.circuit, after.form, remappedAnnotations, None) } @@ -385,7 +371,7 @@ trait Transform extends TransformLike[CircuitState] with DependencyAPI[Transform * @return A transformed Firrtl AST */ final def runTransform(state: CircuitState): CircuitState = { - val result = Transform.runTransform(name, execute(prepare(state)), logger) + val result = execute(prepare(state)) Transform.remapAnnotations(name, state, result, logger) } diff --git a/src/main/scala/firrtl/options/DependencyManager.scala b/src/main/scala/firrtl/options/DependencyManager.scala index 5e9119401d..f8299bbb1b 100644 --- a/src/main/scala/firrtl/options/DependencyManager.scala +++ b/src/main/scala/firrtl/options/DependencyManager.scala @@ -275,7 +275,14 @@ trait DependencyManager[A, B <: TransformLike[A] with DependencyAPI[B]] extends | prerequisites: ${prerequisites.mkString("\n -", "\n -", "")}""".stripMargin ) } - (t.transform(a), ((state + wrapperToClass(t)).map(dToO).filterNot(t.invalidates).map(oToD))) + val logger = t.getLogger + logger.info(s"======== Starting ${t.name} ========") + val (timeMillis, annosx) = firrtl.Utils.time { t.transform(a) } + logger.info(s"""----------------------------${"-" * t.name.size}---------\n""") + logger.info(f"Time: $timeMillis%.1f ms") + logger.info(s"======== Finished ${t.name} ========") + val statex = (state + wrapperToClass(t)).map(dToO).filterNot(t.invalidates).map(oToD) + (annosx, statex) }._1 } diff --git a/src/main/scala/firrtl/stage/transforms/UpdateAnnotations.scala b/src/main/scala/firrtl/stage/transforms/UpdateAnnotations.scala index 4fa7788d2c..8bd29b9c3c 100644 --- a/src/main/scala/firrtl/stage/transforms/UpdateAnnotations.scala +++ b/src/main/scala/firrtl/stage/transforms/UpdateAnnotations.scala @@ -19,7 +19,7 @@ class UpdateAnnotations(val underlying: Transform) } def internalTransform(b: (CircuitState, CircuitState)): (CircuitState, CircuitState) = { - val result = Transform.runTransform(name, underlying.transform(b._2), logger) + val result = underlying.transform(b._2) (b._1, result) } } diff --git a/src/main/scala/logger/Logger.scala b/src/main/scala/logger/Logger.scala index 20c1338ec1..09fc0924ef 100644 --- a/src/main/scala/logger/Logger.scala +++ b/src/main/scala/logger/Logger.scala @@ -47,6 +47,7 @@ object LogLevel extends Enumeration { */ trait LazyLogging { protected val logger = new Logger(this.getClass.getName) + def getLogger: Logger = logger } /** From 1f7faaa36816fc145a39bb7e736fd5ab739112c4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 19 Apr 2021 15:37:10 -0700 Subject: [PATCH 75/88] Update .mergify.yml (#2181) Co-authored-by: jackkoenig --- .mergify.yml | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/.mergify.yml b/.mergify.yml index 3929b42343..5e77c8d945 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -22,6 +22,8 @@ pull_request_rules: backport: branches: - 1.4.x + labels: + - Backport ignore_conflicts: true label_conflicts: bp-conflict label: @@ -37,6 +39,8 @@ pull_request_rules: branches: - 1.3.x - 1.4.x + labels: + - Backport ignore_conflicts: true label_conflicts: bp-conflict label: @@ -53,18 +57,13 @@ pull_request_rules: - 1.2.x - 1.3.x - 1.4.x + labels: + - Backport ignore_conflicts: true label_conflicts: bp-conflict label: add: - Backported -- name: label Mergify backport PR - conditions: - - title~=\(bp \#\d+\) - actions: - label: - add: - - Backport - name: automatic squash-and-mege of 1.2.x backport PRs conditions: - status-success=all tests passed From adc2ad9aaf2e760f2f138fd688b2b01604bb6b8f Mon Sep 17 00:00:00 2001 From: Fabian Schuiki Date: Thu, 22 Apr 2021 16:21:35 +0200 Subject: [PATCH 76/88] Fix CheckWidths error message for uninferred width (#2196) Looks like a typo/auto-merge hiccup. --- src/main/scala/firrtl/passes/CheckWidths.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/firrtl/passes/CheckWidths.scala b/src/main/scala/firrtl/passes/CheckWidths.scala index 150a1bc8c0..a9f4442165 100644 --- a/src/main/scala/firrtl/passes/CheckWidths.scala +++ b/src/main/scala/firrtl/passes/CheckWidths.scala @@ -23,7 +23,7 @@ object CheckWidths extends Pass { val MaxWidth = 1000000 val DshlMaxWidth = getUIntWidth(MaxWidth) class UninferredWidth(info: Info, target: String) - extends PassException(s"""|$info : Uninferred width for target below.serialize}. (Did you forget to assign to it?) + extends PassException(s"""|$info : Uninferred width for target below. (Did you forget to assign to it?) |$target""".stripMargin) class UninferredBound(info: Info, target: String, bound: String) extends PassException(s"""|$info : Uninferred $bound bound for target. (Did you forget to assign to it?) From 33c0b4312aec944caa62087663ba9ca41c9c9a6e Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Tue, 27 Apr 2021 03:02:58 +0000 Subject: [PATCH 77/88] deprecate memlib APIs modifided in #2191. (#2199) --- ...rateMems.scala => CreateMemoryAnnotations.scala} | 1 + .../firrtl/passes/memlib/ReplaceMemMacros.scala | 13 +++++++++++++ .../firrtl/passes/memlib/ReplaceMemTransform.scala | 4 ++++ src/test/scala/firrtlTests/ReplSeqMemTests.scala | 1 + 4 files changed, 19 insertions(+) rename src/main/scala/firrtl/passes/memlib/{DecorateMems.scala => CreateMemoryAnnotations.scala} (87%) diff --git a/src/main/scala/firrtl/passes/memlib/DecorateMems.scala b/src/main/scala/firrtl/passes/memlib/CreateMemoryAnnotations.scala similarity index 87% rename from src/main/scala/firrtl/passes/memlib/DecorateMems.scala rename to src/main/scala/firrtl/passes/memlib/CreateMemoryAnnotations.scala index 1cdecdfa42..ce7eea5eb1 100644 --- a/src/main/scala/firrtl/passes/memlib/DecorateMems.scala +++ b/src/main/scala/firrtl/passes/memlib/CreateMemoryAnnotations.scala @@ -6,6 +6,7 @@ package memlib import firrtl.stage.Forms +@deprecated("CreateMemoryAnnotations will not take reader: Option[YamlFileReader] as argument since 1.5.", "FIRRTL 1.4") class CreateMemoryAnnotations(reader: Option[YamlFileReader]) extends Transform with DependencyAPIMigration { override def prerequisites = Forms.MidForm diff --git a/src/main/scala/firrtl/passes/memlib/ReplaceMemMacros.scala b/src/main/scala/firrtl/passes/memlib/ReplaceMemMacros.scala index 63ae049032..bb2bed336a 100644 --- a/src/main/scala/firrtl/passes/memlib/ReplaceMemMacros.scala +++ b/src/main/scala/firrtl/passes/memlib/ReplaceMemMacros.scala @@ -24,6 +24,7 @@ object ReplaceMemMacros { * This will not generate wmask ports if not needed. * Creates the minimum # of black boxes needed by the design. */ +@deprecated("ReplaceMemMacros will not take writer: ConfWriter as argument since 1.5.", "FIRRTL 1.4") class ReplaceMemMacros(writer: ConfWriter) extends Transform with DependencyAPIMigration { override def prerequisites = Forms.MidForm @@ -121,11 +122,14 @@ class ReplaceMemMacros(writer: ConfWriter) extends Transform with DependencyAPIM }) ) + @deprecated("memToBundle will become private in 1.5.", "FIRRTL 1.4") def memToBundle(s: DefAnnotatedMemory) = BundleType( s.readers.map(Field(_, Flip, rPortToBundle(s))) ++ s.writers.map(Field(_, Flip, wPortToBundle(s))) ++ s.readwriters.map(Field(_, Flip, rwPortToBundle(s))) ) + + @deprecated("memToFlattenBundle will become private in 1.5.", "FIRRTL 1.4") def memToFlattenBundle(s: DefAnnotatedMemory) = BundleType( s.readers.map(Field(_, Flip, rPortToFlattenBundle(s))) ++ s.writers.map(Field(_, Flip, wPortToFlattenBundle(s))) ++ @@ -136,6 +140,7 @@ class ReplaceMemMacros(writer: ConfWriter) extends Transform with DependencyAPIM * The wrapper module has the same type as the memory it replaces * The external module */ + @deprecated("createMemModule will become private in 1.5.", "FIRRTL 1.4") def createMemModule(m: DefAnnotatedMemory, wrapperName: String): Seq[DefModule] = { assert(m.dataType != UnknownType) val wrapperIoType = memToBundle(m) @@ -162,18 +167,22 @@ class ReplaceMemMacros(writer: ConfWriter) extends Transform with DependencyAPIM // TODO(shunshou): get rid of copy pasta // Connects the clk, en, and addr fields from the wrapperPort to the bbPort + @deprecated("defaultConnects will become private in 1.5.", "FIRRTL 1.4") def defaultConnects(wrapperPort: WRef, bbPort: WSubField): Seq[Connect] = Seq("clk", "en", "addr").map(f => connectFields(bbPort, f, wrapperPort, f)) // Generates mask bits (concatenates an aggregate to ground type) // depending on mask granularity (# bits = data width / mask granularity) + @deprecated("maskBits will become private in 1.5.", "FIRRTL 1.4") def maskBits(mask: WSubField, dataType: Type, fillMask: Boolean): Expression = if (fillMask) toBitMask(mask, dataType) else toBits(mask) + @deprecated("adaptReader will become private in 1.5.", "FIRRTL 1.4") def adaptReader(wrapperPort: WRef, bbPort: WSubField): Seq[Statement] = defaultConnects(wrapperPort, bbPort) :+ fromBits(WSubField(wrapperPort, "data"), WSubField(bbPort, "data")) + @deprecated("adaptWriter will become private in 1.5.", "FIRRTL 1.4") def adaptWriter(wrapperPort: WRef, bbPort: WSubField, hasMask: Boolean, fillMask: Boolean): Seq[Statement] = { val wrapperData = WSubField(wrapperPort, "data") val defaultSeq = defaultConnects(wrapperPort, bbPort) :+ @@ -189,6 +198,7 @@ class ReplaceMemMacros(writer: ConfWriter) extends Transform with DependencyAPIM } } + @deprecated("adaptReadWriter will become private in 1.5.", "FIRRTL 1.4") def adaptReadWriter(wrapperPort: WRef, bbPort: WSubField, hasMask: Boolean, fillMask: Boolean): Seq[Statement] = { val wrapperWData = WSubField(wrapperPort, "wdata") val defaultSeq = defaultConnects(wrapperPort, bbPort) ++ Seq( @@ -211,6 +221,7 @@ class ReplaceMemMacros(writer: ConfWriter) extends Transform with DependencyAPIM private type NameMap = collection.mutable.HashMap[(String, String), String] /** Construct NameMap by assigning unique names for each memory blackbox */ + @deprecated("constructNameMap will become private in 1.5.", "FIRRTL 1.4") def constructNameMap(namespace: Namespace, nameMap: NameMap, mname: String)(s: Statement): Statement = { s match { case m: DefAnnotatedMemory => @@ -223,6 +234,7 @@ class ReplaceMemMacros(writer: ConfWriter) extends Transform with DependencyAPIM s.map(constructNameMap(namespace, nameMap, mname)) } + @deprecated("updateMemStmts will be private in 1.5.", "FIRRTL 1.4") def updateMemStmts( namespace: Namespace, nameMap: NameMap, @@ -250,6 +262,7 @@ class ReplaceMemMacros(writer: ConfWriter) extends Transform with DependencyAPIM case sx => sx.map(updateMemStmts(namespace, nameMap, mname, memPortMap, memMods)) } + @deprecated("updateMemMods will be private in 1.5.", "FIRRTL 1.4") def updateMemMods(namespace: Namespace, nameMap: NameMap, memMods: Modules)(m: DefModule) = { val memPortMap = new MemPortMap diff --git a/src/main/scala/firrtl/passes/memlib/ReplaceMemTransform.scala b/src/main/scala/firrtl/passes/memlib/ReplaceMemTransform.scala index f0325e8ec7..515ac69164 100644 --- a/src/main/scala/firrtl/passes/memlib/ReplaceMemTransform.scala +++ b/src/main/scala/firrtl/passes/memlib/ReplaceMemTransform.scala @@ -43,6 +43,7 @@ object PassConfigUtil { } } +@deprecated("ConfWriter will be removed in 1.5.", "FIRRTL 1.4") class ConfWriter(filename: String) { val outputBuffer = new CharArrayWriter def append(m: DefAnnotatedMemory) = { @@ -111,6 +112,7 @@ class SimpleTransform(p: Pass, form: CircuitForm) extends Transform { class SimpleMidTransform(p: Pass) extends SimpleTransform(p, MidForm) // SimpleRun instead of PassBased because of the arguments to passSeq +@deprecated("Migrate to a SeqTransform. API will be changed in 1.5.", "FIRRTL 1.4") class ReplSeqMem extends Transform with HasShellOptions with DependencyAPIMigration { override def prerequisites = Forms.MidForm @@ -132,6 +134,7 @@ class ReplSeqMem extends Transform with HasShellOptions with DependencyAPIMigrat ) ) + @deprecated("API will be replaced with a val in 1.5.", "FIRRTL 1.4") def transforms(inConfigFile: Option[YamlFileReader], outConfigFile: ConfWriter): Seq[Transform] = Seq( new SimpleMidTransform(Legalize), @@ -144,6 +147,7 @@ class ReplSeqMem extends Transform with HasShellOptions with DependencyAPIMigrat new WiringTransform ) + @deprecated("API will be removed in 1.5.", "FIRRTL 1.4") def execute(state: CircuitState): CircuitState = { val annos = state.annotations.collect { case a: ReplSeqMemAnnotation => a } annos match { diff --git a/src/test/scala/firrtlTests/ReplSeqMemTests.scala b/src/test/scala/firrtlTests/ReplSeqMemTests.scala index 2156e39223..84709a211e 100644 --- a/src/test/scala/firrtlTests/ReplSeqMemTests.scala +++ b/src/test/scala/firrtlTests/ReplSeqMemTests.scala @@ -30,6 +30,7 @@ class ReplSeqMemSpec extends SimpleTransformSpec { } ) + @deprecated("API will be changed in 1.5.", "FIRRTL 1.4") def checkMemConf(filename: String, mems: Set[MemConf]) { // Read the mem conf val text = FileUtils.getText(filename) From 54b6d8713bddb0e16e475a4f47e376217c5261e3 Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Tue, 27 Apr 2021 03:26:39 +0000 Subject: [PATCH 78/88] Memlib Refactor (#2191) * remove all deprecations, switch to new API. * Add MemLibOutConfigFileAnnotation to replace ConfWriter. * Inline CreateMemoryAnnotations in ReplSeqMem. * Dont use ConfWriter anymore. * Fix ReplSeqMemTests, rewrite checkMemConf to directly read from annoation. * Fix for review. 0. Since DependencyAPI only initiate transform only once, ListBuffer is dangerous to use, remove defAnnotatedMemories from Transform. 1. Add trait HasAnnotatedMemories to store ListBuffer, MemLibOutConfigFileAnnotation also extends from which now. * Use two annotations converting and storing DefMemory. 0. rewrite CreateMemoryAnnotations to match ReplSeqMemAnnotation creating PinAnnotation. 1. add DumpMemoryAnnotations to convert from AnnotatedMemoriesCollectorAnnotation to MemLibOutConfigFileAnnotation 2. refactor MemLibOutConfigFileAnnotation and remove HasAnnotatedMemories 3. add private AnnotatedMemoriesCollectorAnnotation to store mutable DefAnnotatedMemory 4. change ReplSeqMem to SeqTransform * Fix for review. 0. replace AnnotatedMemoriesCollectorAnnotation with immutable AnnotatedMemoriesAnnotation. 1. add ListBuffer[DefAnnotatedMemory] in ReplaceMemMacros.execute. * private functions in ReplaceMemMacros transform. * scalafmt * remove ConfWriter API. --- .../memlib/CreateMemoryAnnotations.scala | 29 +++-- .../passes/memlib/DumpMemoryAnnotations.scala | 29 +++++ .../passes/memlib/ReplaceMemMacros.scala | 119 +++++++++--------- .../passes/memlib/ReplaceMemTransform.scala | 73 +++++------ .../scala/firrtlTests/ReplSeqMemTests.scala | 36 +++--- 5 files changed, 154 insertions(+), 132 deletions(-) create mode 100644 src/main/scala/firrtl/passes/memlib/DumpMemoryAnnotations.scala diff --git a/src/main/scala/firrtl/passes/memlib/CreateMemoryAnnotations.scala b/src/main/scala/firrtl/passes/memlib/CreateMemoryAnnotations.scala index ce7eea5eb1..240c2c9ad6 100644 --- a/src/main/scala/firrtl/passes/memlib/CreateMemoryAnnotations.scala +++ b/src/main/scala/firrtl/passes/memlib/CreateMemoryAnnotations.scala @@ -4,26 +4,29 @@ package firrtl package passes package memlib +import firrtl.Utils.error import firrtl.stage.Forms -@deprecated("CreateMemoryAnnotations will not take reader: Option[YamlFileReader] as argument since 1.5.", "FIRRTL 1.4") -class CreateMemoryAnnotations(reader: Option[YamlFileReader]) extends Transform with DependencyAPIMigration { +import java.io.File + +class CreateMemoryAnnotations extends Transform with DependencyAPIMigration { override def prerequisites = Forms.MidForm override def optionalPrerequisites = Seq.empty override def optionalPrerequisiteOf = Forms.MidEmitters override def invalidates(a: Transform) = false - def execute(state: CircuitState): CircuitState = reader match { - case None => state - case Some(r) => - import CustomYAMLProtocol._ - val configs = r.parse[Config] - val oldAnnos = state.annotations - val (as, pins) = configs.foldLeft((oldAnnos, Seq.empty[String])) { - case ((annos, pins), config) => - (annos, pins :+ config.pin.name) - } - state.copy(annotations = PinAnnotation(pins.toSeq) +: as) + def execute(state: CircuitState): CircuitState = { + state.copy(annotations = state.annotations.flatMap { + case ReplSeqMemAnnotation(inputFileName, outputConfig) => + Seq(MemLibOutConfigFileAnnotation(outputConfig, Nil)) ++ { + if (inputFileName.isEmpty) None + else if (new File(inputFileName).exists) { + import CustomYAMLProtocol._ + Some(PinAnnotation(new YamlFileReader(inputFileName).parse[Config].map(_.pin.name))) + } else error("Input configuration file does not exist!") + } + case a => Seq(a) + }) } } diff --git a/src/main/scala/firrtl/passes/memlib/DumpMemoryAnnotations.scala b/src/main/scala/firrtl/passes/memlib/DumpMemoryAnnotations.scala new file mode 100644 index 0000000000..5cc1e0bf0f --- /dev/null +++ b/src/main/scala/firrtl/passes/memlib/DumpMemoryAnnotations.scala @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 + +package firrtl +package passes +package memlib + +import firrtl.stage.Forms + +class DumpMemoryAnnotations extends Transform with DependencyAPIMigration { + + override def prerequisites = Forms.MidForm + override def optionalPrerequisites = Seq.empty + override def optionalPrerequisiteOf = Forms.MidEmitters + override def invalidates(a: Transform) = false + + def execute(state: CircuitState): CircuitState = { + state.copy(annotations = state.annotations.flatMap { + // convert and remove AnnotatedMemoriesAnnotation to CustomFileEmission + case AnnotatedMemoriesAnnotation(annotatedMemories) => + state.annotations.collect { + case a: MemLibOutConfigFileAnnotation => + a.copy(annotatedMemories = annotatedMemories) + // todo convert xxx to verilogs here. + } + case MemLibOutConfigFileAnnotation(_, Nil) => Nil + case a => Seq(a) + }) + } +} diff --git a/src/main/scala/firrtl/passes/memlib/ReplaceMemMacros.scala b/src/main/scala/firrtl/passes/memlib/ReplaceMemMacros.scala index bb2bed336a..4b43118ba0 100644 --- a/src/main/scala/firrtl/passes/memlib/ReplaceMemMacros.scala +++ b/src/main/scala/firrtl/passes/memlib/ReplaceMemMacros.scala @@ -3,15 +3,17 @@ package firrtl.passes package memlib -import firrtl._ -import firrtl.ir._ -import firrtl.Utils._ import firrtl.Mappers._ -import MemPortUtils.{MemPortMap, Modules} -import MemTransformUtils._ +import firrtl.Utils._ +import firrtl._ import firrtl.annotations._ +import firrtl.ir._ +import firrtl.passes.MemPortUtils.{MemPortMap, Modules} +import firrtl.passes.memlib.MemTransformUtils._ +import firrtl.passes.wiring._ import firrtl.stage.Forms -import wiring._ + +import scala.collection.mutable.ListBuffer /** Annotates the name of the pins to add for WiringTransform */ case class PinAnnotation(pins: Seq[String]) extends NoTargetAnnotation @@ -24,9 +26,7 @@ object ReplaceMemMacros { * This will not generate wmask ports if not needed. * Creates the minimum # of black boxes needed by the design. */ -@deprecated("ReplaceMemMacros will not take writer: ConfWriter as argument since 1.5.", "FIRRTL 1.4") -class ReplaceMemMacros(writer: ConfWriter) extends Transform with DependencyAPIMigration { - +class ReplaceMemMacros extends Transform with DependencyAPIMigration { override def prerequisites = Forms.MidForm override def optionalPrerequisites = Seq.empty override def optionalPrerequisiteOf = Forms.MidEmitters @@ -122,15 +122,13 @@ class ReplaceMemMacros(writer: ConfWriter) extends Transform with DependencyAPIM }) ) - @deprecated("memToBundle will become private in 1.5.", "FIRRTL 1.4") - def memToBundle(s: DefAnnotatedMemory) = BundleType( + private def memToBundle(s: DefAnnotatedMemory) = BundleType( s.readers.map(Field(_, Flip, rPortToBundle(s))) ++ s.writers.map(Field(_, Flip, wPortToBundle(s))) ++ s.readwriters.map(Field(_, Flip, rwPortToBundle(s))) ) - @deprecated("memToFlattenBundle will become private in 1.5.", "FIRRTL 1.4") - def memToFlattenBundle(s: DefAnnotatedMemory) = BundleType( + private def memToFlattenBundle(s: DefAnnotatedMemory) = BundleType( s.readers.map(Field(_, Flip, rPortToFlattenBundle(s))) ++ s.writers.map(Field(_, Flip, wPortToFlattenBundle(s))) ++ s.readwriters.map(Field(_, Flip, rwPortToFlattenBundle(s))) @@ -140,8 +138,11 @@ class ReplaceMemMacros(writer: ConfWriter) extends Transform with DependencyAPIM * The wrapper module has the same type as the memory it replaces * The external module */ - @deprecated("createMemModule will become private in 1.5.", "FIRRTL 1.4") - def createMemModule(m: DefAnnotatedMemory, wrapperName: String): Seq[DefModule] = { + private def createMemModule( + m: DefAnnotatedMemory, + wrapperName: String, + annotatedMemoriesBuffer: ListBuffer[DefAnnotatedMemory] + ): Seq[DefModule] = { assert(m.dataType != UnknownType) val wrapperIoType = memToBundle(m) val wrapperIoPorts = wrapperIoType.fields.map(f => Port(NoInfo, f.name, Input, f.tpe)) @@ -161,29 +162,25 @@ class ReplaceMemMacros(writer: ConfWriter) extends Transform with DependencyAPIM // TODO: Annotate? -- use actual annotation map // add to conf file - writer.append(m) + annotatedMemoriesBuffer += m Seq(bb, wrapper) } // TODO(shunshou): get rid of copy pasta // Connects the clk, en, and addr fields from the wrapperPort to the bbPort - @deprecated("defaultConnects will become private in 1.5.", "FIRRTL 1.4") - def defaultConnects(wrapperPort: WRef, bbPort: WSubField): Seq[Connect] = + private def defaultConnects(wrapperPort: WRef, bbPort: WSubField): Seq[Connect] = Seq("clk", "en", "addr").map(f => connectFields(bbPort, f, wrapperPort, f)) // Generates mask bits (concatenates an aggregate to ground type) // depending on mask granularity (# bits = data width / mask granularity) - @deprecated("maskBits will become private in 1.5.", "FIRRTL 1.4") - def maskBits(mask: WSubField, dataType: Type, fillMask: Boolean): Expression = + private def maskBits(mask: WSubField, dataType: Type, fillMask: Boolean): Expression = if (fillMask) toBitMask(mask, dataType) else toBits(mask) - @deprecated("adaptReader will become private in 1.5.", "FIRRTL 1.4") - def adaptReader(wrapperPort: WRef, bbPort: WSubField): Seq[Statement] = + private def adaptReader(wrapperPort: WRef, bbPort: WSubField): Seq[Statement] = defaultConnects(wrapperPort, bbPort) :+ fromBits(WSubField(wrapperPort, "data"), WSubField(bbPort, "data")) - @deprecated("adaptWriter will become private in 1.5.", "FIRRTL 1.4") - def adaptWriter(wrapperPort: WRef, bbPort: WSubField, hasMask: Boolean, fillMask: Boolean): Seq[Statement] = { + private def adaptWriter(wrapperPort: WRef, bbPort: WSubField, hasMask: Boolean, fillMask: Boolean): Seq[Statement] = { val wrapperData = WSubField(wrapperPort, "data") val defaultSeq = defaultConnects(wrapperPort, bbPort) :+ Connect(NoInfo, WSubField(bbPort, "data"), toBits(wrapperData)) @@ -198,8 +195,12 @@ class ReplaceMemMacros(writer: ConfWriter) extends Transform with DependencyAPIM } } - @deprecated("adaptReadWriter will become private in 1.5.", "FIRRTL 1.4") - def adaptReadWriter(wrapperPort: WRef, bbPort: WSubField, hasMask: Boolean, fillMask: Boolean): Seq[Statement] = { + private def adaptReadWriter( + wrapperPort: WRef, + bbPort: WSubField, + hasMask: Boolean, + fillMask: Boolean + ): Seq[Statement] = { val wrapperWData = WSubField(wrapperPort, "wdata") val defaultSeq = defaultConnects(wrapperPort, bbPort) ++ Seq( fromBits(WSubField(wrapperPort, "rdata"), WSubField(bbPort, "rdata")), @@ -221,8 +222,7 @@ class ReplaceMemMacros(writer: ConfWriter) extends Transform with DependencyAPIM private type NameMap = collection.mutable.HashMap[(String, String), String] /** Construct NameMap by assigning unique names for each memory blackbox */ - @deprecated("constructNameMap will become private in 1.5.", "FIRRTL 1.4") - def constructNameMap(namespace: Namespace, nameMap: NameMap, mname: String)(s: Statement): Statement = { + private def constructNameMap(namespace: Namespace, nameMap: NameMap, mname: String)(s: Statement): Statement = { s match { case m: DefAnnotatedMemory => m.memRef match { @@ -234,14 +234,14 @@ class ReplaceMemMacros(writer: ConfWriter) extends Transform with DependencyAPIM s.map(constructNameMap(namespace, nameMap, mname)) } - @deprecated("updateMemStmts will be private in 1.5.", "FIRRTL 1.4") - def updateMemStmts( - namespace: Namespace, - nameMap: NameMap, - mname: String, - memPortMap: MemPortMap, - memMods: Modules - )(s: Statement + private def updateMemStmts( + namespace: Namespace, + nameMap: NameMap, + mname: String, + memPortMap: MemPortMap, + memMods: Modules, + annotatedMemoriesBuffer: ListBuffer[DefAnnotatedMemory] + )(s: Statement ): Statement = s match { case m: DefAnnotatedMemory => if (m.maskGran.isEmpty) { @@ -254,42 +254,49 @@ class ReplaceMemMacros(writer: ConfWriter) extends Transform with DependencyAPIM val newWrapperName = nameMap(mname -> m.name) val newMemBBName = namespace.newName(s"${newWrapperName}_ext") val newMem = m.copy(name = newMemBBName) - memMods ++= createMemModule(newMem, newWrapperName) + memMods ++= createMemModule(newMem, newWrapperName, annotatedMemoriesBuffer) WDefInstance(m.info, m.name, newWrapperName, UnknownType) case Some((module, mem)) => WDefInstance(m.info, m.name, nameMap(module -> mem), UnknownType) } - case sx => sx.map(updateMemStmts(namespace, nameMap, mname, memPortMap, memMods)) + case sx => sx.map(updateMemStmts(namespace, nameMap, mname, memPortMap, memMods, annotatedMemoriesBuffer)) } - @deprecated("updateMemMods will be private in 1.5.", "FIRRTL 1.4") - def updateMemMods(namespace: Namespace, nameMap: NameMap, memMods: Modules)(m: DefModule) = { + private def updateMemMods( + namespace: Namespace, + nameMap: NameMap, + memMods: Modules, + annotatedMemoriesBuffer: ListBuffer[DefAnnotatedMemory] + )(m: DefModule + ) = { val memPortMap = new MemPortMap - (m.map(updateMemStmts(namespace, nameMap, m.name, memPortMap, memMods)) + (m.map(updateMemStmts(namespace, nameMap, m.name, memPortMap, memMods, annotatedMemoriesBuffer)) .map(updateStmtRefs(memPortMap))) } def execute(state: CircuitState): CircuitState = { + val annotatedMemoriesBuffer: collection.mutable.ListBuffer[DefAnnotatedMemory] = ListBuffer[DefAnnotatedMemory]() val c = state.circuit val namespace = Namespace(c) val memMods = new Modules val nameMap = new NameMap c.modules.map(m => m.map(constructNameMap(namespace, nameMap, m.name))) - val modules = c.modules.map(updateMemMods(namespace, nameMap, memMods)) - // print conf - writer.serialize() - val pannos = state.annotations.collect { case a: PinAnnotation => a } - val pins = pannos match { - case Seq() => Nil - case Seq(PinAnnotation(pins)) => pins - case _ => throwInternalError("Something went wrong") - } - val annos = pins.foldLeft(Seq[Annotation]()) { (seq, pin) => - seq ++ memMods.collect { - case m: ExtModule => SinkAnnotation(ModuleName(m.name, CircuitName(c.main)), pin) - } - } ++ state.annotations - state.copy(circuit = c.copy(modules = modules ++ memMods), annotations = annos) + val modules = c.modules.map(updateMemMods(namespace, nameMap, memMods, annotatedMemoriesBuffer)) + state.copy( + circuit = c.copy(modules = modules ++ memMods), + annotations = + state.annotations ++ + (state.annotations.collectFirst { case a: PinAnnotation => a } match { + case None => Nil + case Some(PinAnnotation(pins)) => + pins.foldLeft(Seq[Annotation]()) { (seq, pin) => + seq ++ memMods.collect { + case m: ExtModule => SinkAnnotation(ModuleName(m.name, CircuitName(c.main)), pin) + } + } + }) :+ + AnnotatedMemoriesAnnotation(annotatedMemoriesBuffer.toList) + ) } } diff --git a/src/main/scala/firrtl/passes/memlib/ReplaceMemTransform.scala b/src/main/scala/firrtl/passes/memlib/ReplaceMemTransform.scala index 515ac69164..f9df27a789 100644 --- a/src/main/scala/firrtl/passes/memlib/ReplaceMemTransform.scala +++ b/src/main/scala/firrtl/passes/memlib/ReplaceMemTransform.scala @@ -3,14 +3,15 @@ package firrtl.passes package memlib +import firrtl.Utils.error import firrtl._ import firrtl.annotations._ -import firrtl.options.{HasShellOptions, ShellOption} -import Utils.error -import java.io.{CharArrayWriter, File, PrintWriter} -import wiring._ +import firrtl.options.{CustomFileEmission, HasShellOptions, ShellOption} +import firrtl.passes.wiring._ import firrtl.stage.{Forms, RunFirrtlTransformAnnotation} +import java.io.{CharArrayWriter, PrintWriter} + sealed trait PassOption case object InputConfigFileName extends PassOption case object OutputConfigFileName extends PassOption @@ -43,15 +44,20 @@ object PassConfigUtil { } } -@deprecated("ConfWriter will be removed in 1.5.", "FIRRTL 1.4") -class ConfWriter(filename: String) { - val outputBuffer = new CharArrayWriter - def append(m: DefAnnotatedMemory) = { - // legacy - // assert that we don't overflow going from BigInt to Int conversion +case class ReplSeqMemAnnotation(inputFileName: String, outputConfig: String) extends NoTargetAnnotation + +/** Generate conf file for a sequence of [[DefAnnotatedMemory]] + * @note file already has its suffix adding by `--replSeqMem` + */ +case class MemLibOutConfigFileAnnotation(file: String, annotatedMemories: Seq[DefAnnotatedMemory]) + extends NoTargetAnnotation + with CustomFileEmission { + def baseFileName(annotations: AnnotationSeq) = file + def suffix = None + def getBytes = annotatedMemories.map { m => require(bitWidth(m.dataType) <= Int.MaxValue) - m.maskGran.foreach { case x => require(x <= Int.MaxValue) } - val conf = MemConf( + m.maskGran.foreach(x => require(x <= Int.MaxValue)) + MemConf( m.name, m.depth, bitWidth(m.dataType).toInt, @@ -59,17 +65,12 @@ class ConfWriter(filename: String) { m.writers.length, m.readwriters.length, m.maskGran.map(_.toInt) - ) - outputBuffer.append(conf.toString) - } - def serialize() = { - val outputFile = new PrintWriter(filename) - outputFile.write(outputBuffer.toString) - outputFile.close() - } + ).toString + }.mkString("\n").getBytes } -case class ReplSeqMemAnnotation(inputFileName: String, outputConfig: String) extends NoTargetAnnotation +private[memlib] case class AnnotatedMemoriesAnnotation(annotatedMemories: List[DefAnnotatedMemory]) + extends NoTargetAnnotation object ReplSeqMemAnnotation { def parse(t: String): ReplSeqMemAnnotation = { @@ -112,8 +113,7 @@ class SimpleTransform(p: Pass, form: CircuitForm) extends Transform { class SimpleMidTransform(p: Pass) extends SimpleTransform(p, MidForm) // SimpleRun instead of PassBased because of the arguments to passSeq -@deprecated("Migrate to a SeqTransform. API will be changed in 1.5.", "FIRRTL 1.4") -class ReplSeqMem extends Transform with HasShellOptions with DependencyAPIMigration { +class ReplSeqMem extends SeqTransform with HasShellOptions with DependencyAPIMigration { override def prerequisites = Forms.MidForm override def optionalPrerequisites = Seq.empty @@ -134,33 +134,16 @@ class ReplSeqMem extends Transform with HasShellOptions with DependencyAPIMigrat ) ) - @deprecated("API will be replaced with a val in 1.5.", "FIRRTL 1.4") - def transforms(inConfigFile: Option[YamlFileReader], outConfigFile: ConfWriter): Seq[Transform] = + val transforms: Seq[Transform] = Seq( new SimpleMidTransform(Legalize), new SimpleMidTransform(ToMemIR), new SimpleMidTransform(ResolveMaskGranularity), new SimpleMidTransform(RenameAnnotatedMemoryPorts), + new CreateMemoryAnnotations, new ResolveMemoryReference, - new CreateMemoryAnnotations(inConfigFile), - new ReplaceMemMacros(outConfigFile), - new WiringTransform + new ReplaceMemMacros, + new WiringTransform, + new DumpMemoryAnnotations ) - - @deprecated("API will be removed in 1.5.", "FIRRTL 1.4") - def execute(state: CircuitState): CircuitState = { - val annos = state.annotations.collect { case a: ReplSeqMemAnnotation => a } - annos match { - case Nil => state // Do nothing if there are no annotations - case Seq(ReplSeqMemAnnotation(inputFileName, outputConfig)) => - val inConfigFile = { - if (inputFileName.isEmpty) None - else if (new File(inputFileName).exists) Some(new YamlFileReader(inputFileName)) - else error("Input configuration file does not exist!") - } - val outConfigFile = new ConfWriter(outputConfig) - transforms(inConfigFile, outConfigFile).foldLeft(state) { (in, xform) => xform.runTransform(in) } - case _ => error("Unexpected transform annotation") - } - } } diff --git a/src/test/scala/firrtlTests/ReplSeqMemTests.scala b/src/test/scala/firrtlTests/ReplSeqMemTests.scala index 84709a211e..4e00cb3ad8 100644 --- a/src/test/scala/firrtlTests/ReplSeqMemTests.scala +++ b/src/test/scala/firrtlTests/ReplSeqMemTests.scala @@ -3,14 +3,13 @@ package firrtlTests import firrtl._ +import firrtl.annotations._ import firrtl.ir._ import firrtl.passes._ -import firrtl.transforms._ import firrtl.passes.memlib._ -import firrtl.FileUtils +import firrtl.testutils.FirrtlCheckers._ import firrtl.testutils._ -import annotations._ -import FirrtlCheckers._ +import firrtl.transforms._ class ReplSeqMemSpec extends SimpleTransformSpec { def emitter = new LowFirrtlEmitter @@ -30,10 +29,11 @@ class ReplSeqMemSpec extends SimpleTransformSpec { } ) - @deprecated("API will be changed in 1.5.", "FIRRTL 1.4") - def checkMemConf(filename: String, mems: Set[MemConf]) { + def checkMemConf(circuitState: CircuitState, mems: Set[MemConf]) { // Read the mem conf - val text = FileUtils.getText(filename) + val text = circuitState.annotations.collectFirst { + case a: MemLibOutConfigFileAnnotation => a.getBytes.map(_.toChar).mkString + }.get // Verify that this does not throw an exception val fromConf = MemConf.fromString(text) // Verify the mems in the conf are the same as the expected ones @@ -75,7 +75,7 @@ circuit Top : // Check correctness of firrtl parse(res.getEmittedCircuit.value) // Check the emitted conf - checkMemConf(confLoc, mems) + checkMemConf(res, mems) (new java.io.File(confLoc)).delete() } @@ -100,7 +100,7 @@ circuit Top : // Check correctness of firrtl parse(res.getEmittedCircuit.value) // Check the emitted conf - checkMemConf(confLoc, mems) + checkMemConf(res, mems) (new java.io.File(confLoc)).delete() } @@ -128,7 +128,7 @@ circuit CustomMemory : // Check correctness of firrtl parse(res.getEmittedCircuit.value) // Check the emitted conf - checkMemConf(confLoc, mems) + checkMemConf(res, mems) (new java.io.File(confLoc)).delete() } @@ -156,7 +156,7 @@ circuit CustomMemory : // Check correctness of firrtl parse(res.getEmittedCircuit.value) // Check the emitted conf - checkMemConf(confLoc, mems) + checkMemConf(res, mems) (new java.io.File(confLoc)).delete() } @@ -245,7 +245,7 @@ circuit CustomMemory : } numExtMods should be(2) // Check the emitted conf - checkMemConf(confLoc, mems) + checkMemConf(res, mems) (new java.io.File(confLoc)).delete() } @@ -292,7 +292,7 @@ circuit CustomMemory : } numExtMods should be(2) // Check the emitted conf - checkMemConf(confLoc, mems) + checkMemConf(res, mems) (new java.io.File(confLoc)).delete() } @@ -354,7 +354,7 @@ circuit CustomMemory : // would be 3 ExtModules numExtMods should be(2) // Check the emitted conf - checkMemConf(confLoc, mems) + checkMemConf(res, mems) (new java.io.File(confLoc)).delete() } @@ -414,7 +414,7 @@ circuit CustomMemory : val res = compileAndEmit(CircuitState(parse(input), ChirrtlForm, annos)) res.getEmittedCircuit.value shouldNot include("mask") // Check the emitted conf - checkMemConf(confLoc, mems) + checkMemConf(res, mems) (new java.io.File(confLoc)).delete() } @@ -444,7 +444,7 @@ circuit CustomMemory : res should containLine("mem.W0_mask_0 <= validif(io_en, io_mask_0)") res should containLine("mem.W0_mask_1 <= validif(io_en, io_mask_1)") // Check the emitted conf - checkMemConf(confLoc, mems) + checkMemConf(res, mems) (new java.io.File(confLoc)).delete() } @@ -478,7 +478,7 @@ circuit CustomMemory : res should containLine("mem.RW0_wmask_0 <= validif(io_en, io_mask_0)") res should containLine("mem.RW0_wmask_1 <= validif(io_en, io_mask_1)") // Check the emitted conf - checkMemConf(confLoc, mems) + checkMemConf(res, mems) (new java.io.File(confLoc)).delete() } @@ -499,7 +499,7 @@ circuit NoMemsHere : val annos = Seq(ReplSeqMemAnnotation.parse("-c:CustomMemory:-o:" + confLoc), InferReadWriteAnnotation) val res = compileAndEmit(CircuitState(parse(input), ChirrtlForm, annos)) // Check the emitted conf - checkMemConf(confLoc, mems) + checkMemConf(res, mems) (new java.io.File(confLoc)).delete() } From 8638891d262a4c23b6a11419f79b330a0f393d8d Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Wed, 28 Apr 2021 11:40:32 +0200 Subject: [PATCH 79/88] Update scalatest to 3.2.8 (#2194) Co-authored-by: Jiuyang Liu --- build.sbt | 2 +- build.sc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index f047f35b8c..33a143854e 100644 --- a/build.sbt +++ b/build.sbt @@ -29,7 +29,7 @@ lazy val firrtlSettings = Seq( javacOptions ++= Seq("-source", "1.8", "-target", "1.8"), libraryDependencies ++= Seq( "org.scala-lang" % "scala-reflect" % scalaVersion.value, - "org.scalatest" %% "scalatest" % "3.2.0" % "test", + "org.scalatest" %% "scalatest" % "3.2.8" % "test", "org.scalatestplus" %% "scalacheck-1-14" % "3.1.3.0" % "test", "com.github.scopt" %% "scopt" % "3.7.1", "net.jcazevedo" %% "moultingyaml" % "0.4.2", diff --git a/build.sc b/build.sc index ec154b8946..8fc43fee86 100644 --- a/build.sc +++ b/build.sc @@ -59,7 +59,7 @@ class firrtlCrossModule(val crossScalaVersion: String) extends CrossSbtModule wi object test extends Tests { override def ivyDeps = T { Agg( - ivy"org.scalatest::scalatest:3.2.0", + ivy"org.scalatest::scalatest:3.2.8", ivy"org.scalatestplus::scalacheck-1-14:3.1.3.0" ) } From e1f30d6eaa1915c67bed1f75adc30a5a9dc7eab9 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Wed, 28 Apr 2021 11:55:19 +0200 Subject: [PATCH 80/88] Update scala-parallel-collections to 1.0.2 (#2163) Co-authored-by: Jiuyang Liu --- build.sbt | 2 +- build.sc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index 33a143854e..3e5f591b13 100644 --- a/build.sbt +++ b/build.sbt @@ -50,7 +50,7 @@ lazy val firrtlSettings = Seq( libraryDependencies ++= { CrossVersion.partialVersion(scalaVersion.value) match { case Some((2, major)) if major <= 12 => Seq() - case _ => Seq("org.scala-lang.modules" %% "scala-parallel-collections" % "0.2.0") + case _ => Seq("org.scala-lang.modules" %% "scala-parallel-collections" % "1.0.2") } }, resolvers ++= Seq( diff --git a/build.sc b/build.sc index 8fc43fee86..90daeb2291 100644 --- a/build.sc +++ b/build.sc @@ -48,7 +48,7 @@ class firrtlCrossModule(val crossScalaVersion: String) extends CrossSbtModule wi ivy"com.google.protobuf:protobuf-java:$protocVersion" ) ++ { if (majorVersion == 13) - Agg(ivy"org.scala-lang.modules::scala-parallel-collections:0.2.0") + Agg(ivy"org.scala-lang.modules::scala-parallel-collections:1.0.2") else Agg() } From c765e4349f0d77bf117285ff495e22b9dd842c5c Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Wed, 28 Apr 2021 19:14:14 +0200 Subject: [PATCH 81/88] Update sbt-scoverage to 1.7.0 (#2204) --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 888e09dab2..bc09f7801c 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -10,7 +10,7 @@ addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.10.0") addSbtPlugin("com.eed3si9n" % "sbt-unidoc" % "0.4.3") -addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.6.1") +addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.7.0") addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.10") From 326dfa7314cd5184167f0d664de246ee749decab Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Wed, 28 Apr 2021 19:53:42 +0200 Subject: [PATCH 82/88] Update antlr4, antlr4-runtime to 4.9.2 (#2137) Co-authored-by: Jiuyang Liu Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- build.sbt | 2 +- build.sc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index 3e5f591b13..7945f512c5 100644 --- a/build.sbt +++ b/build.sbt @@ -93,7 +93,7 @@ lazy val antlrSettings = Seq( antlr4GenVisitor in Antlr4 := true, antlr4GenListener in Antlr4 := false, antlr4PackageName in Antlr4 := Option("firrtl.antlr"), - antlr4Version in Antlr4 := "4.8", + antlr4Version in Antlr4 := "4.9.2", javaSource in Antlr4 := (sourceManaged in Compile).value ) diff --git a/build.sc b/build.sc index 90daeb2291..19a78069c6 100644 --- a/build.sc +++ b/build.sc @@ -90,7 +90,7 @@ class firrtlCrossModule(val crossScalaVersion: String) extends CrossSbtModule wi } /* antlr4 */ - def antlr4Version = "4.8" + def antlr4Version = "4.9.2" def antlrSource = T.source { millSourcePath / "src" / "main" / "antlr4" / "FIRRTL.g4" From 03f717136a2adf9434e733e9185a30ea76a8295e Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Wed, 28 Apr 2021 20:38:52 +0200 Subject: [PATCH 83/88] Update sbt-scalafix to 0.9.27 (#2161) Co-authored-by: Jiuyang Liu --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index bc09f7801c..dcc1fb997b 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -18,7 +18,7 @@ addSbtPlugin("com.simplytyped" % "sbt-antlr4" % "0.8.2") addSbtPlugin("com.github.gseitz" % "sbt-protobuf" % "0.6.5") -addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.25") +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.27") addSbtPlugin("com.thoughtworks.sbt-api-mappings" % "sbt-api-mappings" % "3.0.0") From 8c3557bae70f78e761cbfef2c5554773686a5642 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Wed, 28 Apr 2021 20:49:06 +0200 Subject: [PATCH 84/88] Update sbt-ci-release to 1.5.7 (#2148) Co-authored-by: Jiuyang Liu Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index dcc1fb997b..5b6beedf0f 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -26,6 +26,6 @@ addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.2") addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.8.1") -addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.4") +addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.7") libraryDependencies += "com.github.os72" % "protoc-jar" % "3.11.4" From 8eb92a72b7e08cb547ee32f143e64664f88b5a7f Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Wed, 28 Apr 2021 21:06:47 +0200 Subject: [PATCH 85/88] Update sbt-protobuf to 0.7.0 (#2134) Co-authored-by: Jiuyang Liu Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 5b6beedf0f..53996b6999 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -16,7 +16,7 @@ addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.10") addSbtPlugin("com.simplytyped" % "sbt-antlr4" % "0.8.2") -addSbtPlugin("com.github.gseitz" % "sbt-protobuf" % "0.6.5") +addSbtPlugin("com.github.sbt" % "sbt-protobuf" % "0.7.0") addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.27") From 3774ac0e8e36bc5ab5fe26cfbb4380c2e977bb8a Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Wed, 28 Apr 2021 21:19:46 +0200 Subject: [PATCH 86/88] Update sbt to 1.3.13 (#1730) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> Co-authored-by: Jiuyang Liu --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index 797e7ccfdb..0837f7a132 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.3.10 +sbt.version=1.3.13 From 190419be314a6e5cbc4d7fdfa14b476fc51246c3 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Wed, 28 Apr 2021 21:34:13 +0200 Subject: [PATCH 87/88] Update json4s-native to 3.6.11 (#2138) Co-authored-by: Jiuyang Liu Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- build.sbt | 2 +- build.sc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index 7945f512c5..395c18861c 100644 --- a/build.sbt +++ b/build.sbt @@ -33,7 +33,7 @@ lazy val firrtlSettings = Seq( "org.scalatestplus" %% "scalacheck-1-14" % "3.1.3.0" % "test", "com.github.scopt" %% "scopt" % "3.7.1", "net.jcazevedo" %% "moultingyaml" % "0.4.2", - "org.json4s" %% "json4s-native" % "3.6.9", + "org.json4s" %% "json4s-native" % "3.6.11", "org.apache.commons" % "commons-text" % "1.8", "io.github.alexarchambault" %% "data-class" % "0.2.5", ), diff --git a/build.sc b/build.sc index 19a78069c6..36412024f3 100644 --- a/build.sc +++ b/build.sc @@ -41,7 +41,7 @@ class firrtlCrossModule(val crossScalaVersion: String) extends CrossSbtModule wi ivy"${scalaOrganization()}:scala-reflect:${scalaVersion()}", ivy"com.github.scopt::scopt:3.7.1", ivy"net.jcazevedo::moultingyaml:0.4.2", - ivy"org.json4s::json4s-native:3.6.9", + ivy"org.json4s::json4s-native:3.6.11", ivy"org.apache.commons:commons-text:1.8", ivy"io.github.alexarchambault::data-class:0.2.5", ivy"org.antlr:antlr4-runtime:$antlr4Version", From 39b89435319bae1cf9b13fa6a3528f48d96223b7 Mon Sep 17 00:00:00 2001 From: Scala Steward <43047562+scala-steward@users.noreply.github.com> Date: Wed, 28 Apr 2021 22:36:36 +0200 Subject: [PATCH 88/88] Update sbt to 1.5.1 (#2205) --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index 0837f7a132..f0be67b9f7 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.3.13 +sbt.version=1.5.1