From e516690acb5b5d6b4c36e0ae1d2905af0c32d63a Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Tue, 11 Jan 2022 21:07:33 -0800 Subject: [PATCH 1/6] Set mimaPreviousArtifacts for binary compatibility checking --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index ae920f5e45..13e0117ce8 100644 --- a/build.sbt +++ b/build.sbt @@ -61,7 +61,7 @@ lazy val firrtlSettings = Seq( ) lazy val mimaSettings = Seq( - mimaPreviousArtifacts := Set() + mimaPreviousArtifacts := Set("edu.berkeley.cs" %% "firrtl" % "1.5.0") ) lazy val protobufSettings = Seq( From ddd49890777d185a99978316acd87f4126b54746 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 17 Jan 2022 23:44:37 +0000 Subject: [PATCH 2/6] [smt] correct comparison for out-of-bounds memory access check (#2463) (#2464) This fixes an off by one error, where 3 was erroneously accepted as in-bounds for a memory of depth=3 (cherry picked from commit 5569c72c1b6246efd203e00f7af6041567575eec) Co-authored-by: Kevin Laeufer --- .../smt/random/UndefinedMemoryBehaviorPass.scala | 4 ++-- .../smt/random/UndefinedMemoryBehaviorSpec.scala | 6 +++--- 2 files changed, 5 insertions(+), 5 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 5fd0e68091..965827787e 100644 --- a/src/main/scala/firrtl/backends/experimental/smt/random/UndefinedMemoryBehaviorPass.scala +++ b/src/main/scala/firrtl/backends/experimental/smt/random/UndefinedMemoryBehaviorPass.scala @@ -414,8 +414,8 @@ private class InstrumentMems( private def isInBounds(depth: BigInt, addr: Expression): Expression = { val width = getWidth(addr) - // depth >= addr - DoPrim(PrimOps.Geq, List(UIntLiteral(depth, width), addr), List(), BoolType) + // depth > addr (e.g. if the depth is 3, then the address must be in {0, 1, 2}) + DoPrim(PrimOps.Gt, List(UIntLiteral(depth, width), addr), List(), BoolType) } private def isPow2(v: BigInt): Boolean = ((v - 1) & v) == 0 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 f8f889ac33..3cc4f831f7 100644 --- a/src/test/scala/firrtl/backends/experimental/smt/random/UndefinedMemoryBehaviorSpec.scala +++ b/src/test/scala/firrtl/backends/experimental/smt/random/UndefinedMemoryBehaviorSpec.scala @@ -91,7 +91,7 @@ class UndefinedMemoryBehaviorSpec extends LeanTransformSpec(Seq(Dependency(Undef assert( result.contains( - """assert(m_a_clk, geq(UInt<5>("h1e"), m_a_addr), UInt<1>("h1"), "out of bounds read")""" + """assert(m_a_clk, gt(UInt<5>("h1e"), m_a_addr), UInt<1>("h1"), "out of bounds read")""" ) ) } @@ -102,7 +102,7 @@ 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(gt(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")) @@ -137,7 +137,7 @@ class UndefinedMemoryBehaviorSpec extends LeanTransformSpec(Seq(Dependency(Undef 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(gt(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)")) From 98c7b17cd8594e64ec7931293fd5fd2955e62dd4 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Mon, 17 Jan 2022 17:21:36 -0800 Subject: [PATCH 3/6] Add 1.5.x to Github Actions push triggers (#2466) --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d50137ce77..85e5bde61c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,6 +5,7 @@ on: push: branches: - master + - 1.5.x - 1.4.x - 1.3.x - 1.2.x From 9dd588dc339c02a713d2c8e1b3fb3867636899f3 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 19 Jan 2022 19:53:29 +0000 Subject: [PATCH 4/6] preset: make PropagatePreset play nice with verification statements (#2453) (#2467) Verification statements are guarded by reset. If this reset happens to be a "preset" type reset, they should always be active. The easiest way to achieve that is to replace all uses of "preset" resets with zero. (cherry picked from commit c98ee33827d21f88911f2ca1a6e04bcbb3864fe8) Co-authored-by: Kevin Laeufer --- .../PropagatePresetAnnotations.scala | 25 ++-- src/test/scala/firrtlTests/PresetSpec.scala | 109 +++++++++++++----- 2 files changed, 99 insertions(+), 35 deletions(-) diff --git a/src/main/scala/firrtl/transforms/PropagatePresetAnnotations.scala b/src/main/scala/firrtl/transforms/PropagatePresetAnnotations.scala index f9b635d454..802050a2f2 100644 --- a/src/main/scala/firrtl/transforms/PropagatePresetAnnotations.scala +++ b/src/main/scala/firrtl/transforms/PropagatePresetAnnotations.scala @@ -11,8 +11,10 @@ import firrtl.options.Dependency import scala.collection.mutable object PropagatePresetAnnotations { + @deprecated("This message will be removed in the next release.", "FIRRTL 1.5") val advice = "Please Note that a Preset-annotated AsyncReset shall NOT be casted to other types with any of the following functions: asInterval, asUInt, asSInt, asClock, asFixedPoint, asAsyncReset." + @deprecated("This exception will no longer be thrown.", "FIRRTL 1.5") case class TreeCleanUpOrphanException(message: String) extends FirrtlUserException(s"Node left an orphan during tree cleanup: $message $advice") } @@ -366,15 +368,22 @@ class PropagatePresetAnnotations extends Transform with DependencyAPIMigration { } } + // replaces all references to removed (clean up) preset signals with zero + def replaceCleanUpRefs(e: ir.Expression): ir.Expression = e match { + case r: ir.RefLikeExpression => + getRef(r) match { + case rt: ReferenceTarget if toCleanUp.contains(rt) => + Utils.getGroundZero(r.tpe.asInstanceOf[ir.GroundType]) + case _ => r.mapExpr(replaceCleanUpRefs) + } + case other => other.mapExpr(replaceCleanUpRefs) + } + def processNode(n: DefNode): Statement = { if (toCleanUp.contains(moduleTarget.ref(n.name))) { EmptyStmt } else { - getRef(n.value) match { - case rt: ReferenceTarget if (toCleanUp.contains(rt)) => - throw TreeCleanUpOrphanException(s"Orphan (${moduleTarget.ref(n.name)}) the way.") - case _ => n - } + n } } @@ -383,7 +392,7 @@ class PropagatePresetAnnotations extends Transform with DependencyAPIMigration { case rhs: ReferenceTarget if (toCleanUp.contains(rhs)) => getRef(c.loc) match { case lhs: ReferenceTarget if (!toCleanUp.contains(lhs)) => - throw TreeCleanUpOrphanException(s"Orphan ${lhs} connected deleted node $rhs.") + c case _ => EmptyStmt } case _ => c @@ -402,14 +411,14 @@ class PropagatePresetAnnotations extends Transform with DependencyAPIMigration { } def processStatements(statement: Statement): Statement = { - statement match { + (statement match { case i: WDefInstance => processInstance(i) case r: DefRegister => processRegister(r) case w: DefWire => processWire(w) case n: DefNode => processNode(n) case c: Connect => processConnect(c) case s => s.mapStmt(processStatements) - } + }).mapExpr(replaceCleanUpRefs) } m match { diff --git a/src/test/scala/firrtlTests/PresetSpec.scala b/src/test/scala/firrtlTests/PresetSpec.scala index 369a277609..d967aa6575 100644 --- a/src/test/scala/firrtlTests/PresetSpec.scala +++ b/src/test/scala/firrtlTests/PresetSpec.scala @@ -6,15 +6,15 @@ import firrtl._ import firrtl.annotations._ import firrtl.testutils._ import firrtl.testutils.FirrtlCheckers._ +import logger.{LogLevel, LogLevelAnnotation, Logger} -class PresetSpec extends FirrtlFlatSpec { +class PresetSpec extends VerilogTransformSpec { type Mod = Seq[String] type ModuleSeq = Seq[Mod] - def compile(input: String, annos: AnnotationSeq): CircuitState = - (new VerilogCompiler).compileAndEmit(CircuitState(parse(input), ChirrtlForm, annos), List.empty) + def compileBody(modules: ModuleSeq) = { val annos = - Seq(new PresetAnnotation(CircuitTarget("Test").module("Test").ref("reset")), firrtl.transforms.NoDCEAnnotation) + Seq(PresetAnnotation(CircuitTarget("Test").module("Test").ref("reset")), firrtl.transforms.NoDCEAnnotation) var str = """ |circuit Test : |""".stripMargin @@ -25,7 +25,10 @@ class PresetSpec extends FirrtlFlatSpec { str += """ |""".stripMargin }) - compile(str, annos) + val logLevel = LogLevel.Warn + Logger.makeScope(Seq(LogLevelAnnotation(logLevel))) { + compile(str, annos) + } } "Preset" should """behave properly given a `Preset` annotated `AsyncReset` INPUT reset: @@ -74,6 +77,7 @@ class PresetSpec extends FirrtlFlatSpec { ) ) ) + result shouldNot containLine("always @(posedge clock or posedge reset) begin") result shouldNot containLines("if (reset) begin", "r = 1'h0;", "end") result should containLine("always @(posedge clock) begin") @@ -82,31 +86,31 @@ class PresetSpec extends FirrtlFlatSpec { result shouldNot containLine("wire reset;") result shouldNot containLine("assign reset = 1'h0;") } - it should "raise TreeCleanUpOrphantException on cast of annotated AsyncReset" in { - an[firrtl.transforms.PropagatePresetAnnotations.TreeCleanUpOrphanException] shouldBe thrownBy { - compileBody( + it should "replace usages of the preset reset with the constant 0 since the reset is never active" in { + val result = compileBody( + Seq( Seq( - Seq( - "Test", - s""" - |input clock : Clock - |input x : UInt<1> - |output z : UInt<1> - |output sz : UInt<1> - |wire reset : AsyncReset - |reset <= asAsyncReset(UInt(0)) - |reg r : UInt<1>, clock with : (reset => (reset, UInt(0))) - |wire sreset : UInt<1> - |sreset <= asUInt(reset) ; this is FORBIDDEN - |reg s : UInt<1>, clock with : (reset => (sreset, UInt(0))) - |r <= x - |s <= x - |z <= r - |sz <= s""".stripMargin - ) + "Test", + s""" + |input clock : Clock + |input x : UInt<1> + |output z : UInt<1> + |output sz : UInt<1> + |wire reset : AsyncReset + |reset <= asAsyncReset(UInt(0)) + |reg r : UInt<1>, clock with : (reset => (reset, UInt(0))) + |wire sreset : UInt<1> + |sreset <= asUInt(reset) ; this is ok, essentially like assigning zero to the wire + |reg s : UInt<1>, clock with : (reset => (sreset, UInt(0))) + |r <= x + |s <= x + |z <= r + |sz <= s""".stripMargin ) ) - } + ) + + result should containLine("wire sreset = 1'h0;") } it should "propagate through bundles" in { @@ -235,6 +239,31 @@ class PresetSpec extends FirrtlFlatSpec { result should containLine("reg r = 1'h0;") } + it should "propagate zeros for all other uses of an async reset" in { + val result = compileBody( + Seq( + Seq( + "Test", + s""" + |input clock : Clock + |input reset : AsyncReset + |output a : UInt<2> + |output b : UInt<2> + | + |node t = reset + |node not_t = not(asUInt(reset)) + |a <= cat(asUInt(t), not_t) + |node t2 = asUInt(t) + |node t3 = asAsyncReset(t2) + |b <= cat(asUInt(asSInt(reset)), asUInt(t3)) + |""".stripMargin + ) + ) + ) + + result should containLine("assign b = {1'h0,1'h0};") + } + it should "propagate even through disordonned statements" in { val result = compileBody( Seq( @@ -261,6 +290,32 @@ class PresetSpec extends FirrtlFlatSpec { result should containLine("reg r = 1'h0;") } + it should "work with verification statements that are guarded by a preset reset" in { + val result = compileBody( + Seq( + Seq( + "Test", + s""" + |input clock : Clock + |input in : UInt<4> + |input reset : AsyncReset + | + |node _T = eq(in, UInt<2>("h3")) @[main.scala 19:15] + |node _T_1 = asUInt(reset) @[main.scala 19:11] + |node _T_2 = eq(_T_1, UInt<1>("h0")) @[main.scala 19:11] + |when _T_2 : @[main.scala 19:11] + | assert(clock, _T, UInt<1>("h1"), "") : assert @[main.scala 19:11] + | node _T_3 = eq(_T, UInt<1>("h0")) @[main.scala 19:11] + | when _T_3 : @[main.scala 19:11] + | printf(clock, UInt<1>("h1"), "Assertion failed") : printf @[main.scala 19:11] + | + |""".stripMargin + ) + ) + ) + // just getting here without falling over the fact that `reset` gets removed is great! + } + } class PresetExecutionTest From 034e8ef2767073afa9d32c2dcfc516984e527acd Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 25 Jan 2022 21:38:49 +0000 Subject: [PATCH 5/6] emission-options flags swapped (#2472) (#2474) emission-options flags swapped to match their functionality (cherry picked from commit 922f58c8ce2619739456b24f702a91807c023fb6) Co-authored-by: Andrea Nardi --- src/main/scala/firrtl/Emitter.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/scala/firrtl/Emitter.scala b/src/main/scala/firrtl/Emitter.scala index 82b033b658..e0f95dcbec 100644 --- a/src/main/scala/firrtl/Emitter.scala +++ b/src/main/scala/firrtl/Emitter.scala @@ -199,10 +199,10 @@ object EmitAllModulesAnnotation extends HasShellOptions { toAnnotationSeq = s => s.split(",") .map { - case "disableMemRandomization" => + case "disableRegisterRandomization" => CustomDefaultRegisterEmission(useInitAsPreset = false, disableRandomization = true) - case "disableRegisterRandomization" => CustomDefaultMemoryEmission(MemoryNoInit) - case a => throw new PhaseException(s"Unknown emission options '$a'! (Did you misspell it?)") + case "disableMemRandomization" => CustomDefaultMemoryEmission(MemoryNoInit) + case a => throw new PhaseException(s"Unknown emission options '$a'! (Did you misspell it?)") } .toSeq, helpText = "Options to disable random initialization for memory and registers", From 2f007ce47ad0ed7cad937c332c63718c414c250b Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 27 Jan 2022 05:03:53 +0000 Subject: [PATCH 6/6] Fix faulty MemorySynthInit behavior (#2468) (#2476) - Fix & test MemorySynthInit behavior with MemoryArrayInitAnnotation and MemoryScalarInitAnnotation. Add test case for MemoryRandomInitAnnotation which is, on the contrary, expected not to leak any randomization statement in synthesis context. - Refactor MemoryInitSpec for improved results readability Context: PR #2166 (commit: 4530152) introduced MemorySynthInit annotation to control whether statement generated with Memory*InitAnnotation (emitted within initial begin block in verilog) should be guarded with ifndef SYNTHESIS or not. Unfortunately only one configuration (MemoryFileInlineAnnotation) has been tested while the others have been generating incorrect verilog statements (MemoryArrayInitAnnotation and MemoryScalarInitAnnotation). Signed-off-by: Jean Bruant (cherry picked from commit 475c165ccf8a52f79a94766450c23090f15d1393) Co-authored-by: John's Brew <46595442+johnsbrew@users.noreply.github.com> --- .../backends/verilog/VerilogEmitter.scala | 14 +- .../scala/firrtlTests/MemoryInitSpec.scala | 121 +++++++++++++----- 2 files changed, 91 insertions(+), 44 deletions(-) diff --git a/src/main/scala/firrtl/backends/verilog/VerilogEmitter.scala b/src/main/scala/firrtl/backends/verilog/VerilogEmitter.scala index f48e4846a6..30d2e89107 100644 --- a/src/main/scala/firrtl/backends/verilog/VerilogEmitter.scala +++ b/src/main/scala/firrtl/backends/verilog/VerilogEmitter.scala @@ -900,14 +900,8 @@ class VerilogEmitter extends SeqTransform with Emitter { case MemoryLoadFileType.Binary => "$readmemb" case MemoryLoadFileType.Hex => "$readmemh" } - 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) - } + memoryInitials += Seq(s"""$readmem("$filename", ${s.name});""") + case MemoryNoInit => // do nothing } @@ -1292,8 +1286,10 @@ class VerilogEmitter extends SeqTransform with Emitter { emit(Seq("`FIRRTL_AFTER_INITIAL")) emit(Seq("`endif")) emit(Seq("`endif // SYNTHESIS")) - if (!emissionOptions.emitMemoryInitAsNoSynth) { + if (!emissionOptions.emitMemoryInitAsNoSynth && !memoryInitials.isEmpty) { + emit(Seq("initial begin")) for (x <- memoryInitials) emit(Seq(tab, x)) + emit(Seq("end")) } } diff --git a/src/test/scala/firrtlTests/MemoryInitSpec.scala b/src/test/scala/firrtlTests/MemoryInitSpec.scala index 688adcb996..c600c8565f 100644 --- a/src/test/scala/firrtlTests/MemoryInitSpec.scala +++ b/src/test/scala/firrtlTests/MemoryInitSpec.scala @@ -61,14 +61,15 @@ class MemInitSpec extends FirrtlFlatSpec { result should containLine(" m[initvar] = _RAND_0[31:0];") } - "MemoryScalarInitAnnotation w/ 0" should "create an initialization with all zeros" in { + behavior.of("MemoryScalarInitAnnotation") + it should "create an initialization with all zeros" in { val annos = Seq(MemoryScalarInitAnnotation(mRef, 0)) val result = compile(basicTest(), annos) result should containLine(" m[initvar] = 0;") } Seq(1, 3, 30, 400, 12345).foreach { value => - s"MemoryScalarInitAnnotation w/ $value" should + it should s"create an initialization with all values set to $value" in { val annos = Seq(MemoryScalarInitAnnotation(mRef, value)) val result = compile(basicTest(), annos) @@ -76,57 +77,58 @@ class MemInitSpec extends FirrtlFlatSpec { } } - "MemoryArrayInitAnnotation" should "initialize all addresses" in { - val values = Seq.tabulate(32)(ii => 2 * ii + 5).map(BigInt(_)) - val annos = Seq(MemoryArrayInitAnnotation(mRef, values)) - val result = compile(basicTest(), annos) - values.zipWithIndex.foreach { - case (value, addr) => - result should containLine(s" m[$addr] = $value;") - } - } - - "MemoryScalarInitAnnotation" should "fail for a negative value" in { + it should "fail for a negative value" in { assertThrows[EmitterException] { compile(basicTest(), Seq(MemoryScalarInitAnnotation(mRef, -1))) } } - "MemoryScalarInitAnnotation" should "fail for a value that is too large" in { + it should "fail for a value that is too large" in { assertThrows[EmitterException] { compile(basicTest(), Seq(MemoryScalarInitAnnotation(mRef, BigInt(1) << 32))) } } - "MemoryArrayInitAnnotation" should "fail for a negative value" in { + behavior.of("MemoryArrayInitAnnotation") + it should "initialize all addresses" in { + val values = Seq.tabulate(32)(ii => 2 * ii + 5).map(BigInt(_)) + val annos = Seq(MemoryArrayInitAnnotation(mRef, values)) + val result = compile(basicTest(), annos) + values.zipWithIndex.foreach { + case (value, addr) => + result should containLine(s" m[$addr] = $value;") + } + } + + it should "fail for a negative value" in { assertThrows[EmitterException] { val values = Seq.tabulate(32)(_ => BigInt(-1)) compile(basicTest(), Seq(MemoryArrayInitAnnotation(mRef, values))) } } - "MemoryArrayInitAnnotation" should "fail for a value that is too large" in { + it should "fail for a value that is too large" in { assertThrows[EmitterException] { val values = Seq.tabulate(32)(_ => BigInt(1) << 32) compile(basicTest(), Seq(MemoryArrayInitAnnotation(mRef, values))) } } - "MemoryArrayInitAnnotation" should "fail if the number of values is too small" in { + it should "fail if the number of values is too small" in { assertThrows[EmitterException] { val values = Seq.tabulate(31)(_ => BigInt(1)) compile(basicTest(), Seq(MemoryArrayInitAnnotation(mRef, values))) } } - "MemoryArrayInitAnnotation" should "fail if the number of values is too large" in { + it should "fail if the number of values is too large" in { assertThrows[EmitterException] { val values = Seq.tabulate(33)(_ => BigInt(1)) compile(basicTest(), Seq(MemoryArrayInitAnnotation(mRef, values))) } } - "MemoryScalarInitAnnotation on Memory with Vector type" should "fail" in { + it should "fail on Memory with Vector type" in { val caught = intercept[Exception] { val annos = Seq(MemoryScalarInitAnnotation(mRef, 0)) compile(basicTest("UInt<32>[2]"), annos) @@ -134,7 +136,7 @@ class MemInitSpec extends FirrtlFlatSpec { assert(caught.getMessage.endsWith("Cannot initialize memory m of non ground type UInt<32>[2]")) } - "MemoryScalarInitAnnotation on Memory with Bundle type" should "fail" in { + it should "fail on Memory with Bundle type" in { val caught = intercept[Exception] { val annos = Seq(MemoryScalarInitAnnotation(mRef, 0)) compile(basicTest("{real: SInt<10>, imag: SInt<10>}"), annos) @@ -147,44 +149,48 @@ class MemInitSpec extends FirrtlFlatSpec { private def jsonAnno(name: String, suffix: String): String = s"""[{"class": "firrtl.annotations.$name", "target": "~MemTest|MemTest>m"$suffix}]""" - "MemoryRandomInitAnnotation" should "load from JSON" in { + behavior.of("MemoryInit load from JSON") + it should "work with MemoryRandomInitAnnotation" in { val json = jsonAnno("MemoryRandomInitAnnotation", "") val annos = JsonProtocol.deserialize(json) assert(annos == Seq(MemoryRandomInitAnnotation(mRef))) } - "MemoryScalarInitAnnotation" should "load from JSON" in { + it should "work with MemoryScalarInitAnnotation" in { val json = jsonAnno("MemoryScalarInitAnnotation", """, "value": 1234567890""") val annos = JsonProtocol.deserialize(json) assert(annos == Seq(MemoryScalarInitAnnotation(mRef, 1234567890))) } - "MemoryArrayInitAnnotation" should "load from JSON" in { + it should "work with MemoryArrayInitAnnotation" in { val json = jsonAnno("MemoryArrayInitAnnotation", """, "values": [10000000000, 20000000000]""") val annos = JsonProtocol.deserialize(json) val largeSeq = Seq(BigInt("10000000000"), BigInt("20000000000")) assert(annos == Seq(MemoryArrayInitAnnotation(mRef, largeSeq))) } - "MemoryFileInlineAnnotation" should "emit $readmemh for text.hex" in { + behavior.of("MemoryFileInlineAnnotation") + it should "emit $readmemh for text.hex" in { val annos = Seq(MemoryFileInlineAnnotation(mRef, filename = "text.hex")) val result = compile(basicTest(), annos) result should containLine("""$readmemh("text.hex", """ + mRef.name + """);""") } - "MemoryFileInlineAnnotation" should "emit $readmemb for text.bin" in { + it should "emit $readmemb for text.bin" in { val annos = Seq(MemoryFileInlineAnnotation(mRef, filename = "text.bin", hexOrBinary = MemoryLoadFileType.Binary)) val result = compile(basicTest(), annos) result should containLine("""$readmemb("text.bin", """ + mRef.name + """);""") } - "MemoryFileInlineAnnotation" should "fail with blank filename" in { + it should "fail with blank filename" in { assertThrows[Exception] { compile(basicTest(), Seq(MemoryFileInlineAnnotation(mRef, filename = ""))) } } - "MemoryInitialization" should "emit readmem in `ifndef SYNTHESIS` block by default" in { + behavior.of("MemorySynthesisInitialization") + + it should "emit readmem in `ifndef SYNTHESIS` block by default" in { val annos = Seq( MemoryFileInlineAnnotation(mRef, filename = "text.hex", hexOrBinary = MemoryLoadFileType.Hex) ) @@ -196,7 +202,7 @@ class MemInitSpec extends FirrtlFlatSpec { ) } - "MemoryInitialization" should "emit readmem outside `ifndef SYNTHESIS` block with MemorySynthInit annotation" in { + it should "emit readmem outside `ifndef SYNTHESIS` block with MemorySynthInit annotation" in { val annos = Seq( MemoryFileInlineAnnotation(mRef, filename = "text.hex", hexOrBinary = MemoryLoadFileType.Hex) ) ++ Seq(MemorySynthInit) @@ -209,7 +215,50 @@ class MemInitSpec extends FirrtlFlatSpec { ) } - "MemoryInitialization" should "emit readmem outside `ifndef SYNTHESIS` block with MemoryNoSynthInit annotation" in { + it should "emit MemoryScalarInit outside `ifndef SYNTHESIS` block with MemorySynthInit annotation" in { + val annos = Seq(MemoryScalarInitAnnotation(mRef, 0), MemorySynthInit) + val result = compile(basicTest(), annos) + result should containLines( + """`endif // SYNTHESIS""", + """initial begin""", + """ for (initvar = 0; initvar < 32; initvar = initvar+1)""", + """ m[initvar] = 0;""", + """end""" + ) + } + + it should "always emit MemoryRandomInit inside `ifndef SYNTHESIS` block even with MemorySynthInit annotation" in { + // randomization should never make it to synthesis! + val annos = Seq(MemoryRandomInitAnnotation(mRef), MemorySynthInit) + val result = compile(basicTest(), annos) + result shouldNot containLines( + """`endif // SYNTHESIS""", + """initial begin""", + """ for (initvar = 0; initvar < 32; initvar = initvar+1)""", + """ m[initvar] = _RAND_0[31:0];""", + """end""" + ) + result should containLines( + """`ifdef RANDOMIZE_MEM_INIT""", + """_RAND_0 = {1{`RANDOM}};""", + """ for (initvar = 0; initvar < 32; initvar = initvar+1)""", + """ m[initvar] = _RAND_0[31:0];""" + ) + } + + it should "emit MemoryArrayInit outside `ifndef SYNTHESIS` block with MemorySynthInit annotation" in { + val values = Seq.tabulate(32)(ii => 2 * ii + 5).map(BigInt(_)) + val annos = Seq(MemoryArrayInitAnnotation(mRef, values), MemorySynthInit) + val result = compile(basicTest(), annos) + val expInit = values.zipWithIndex.map { case (value, addr) => s" m[$addr] = $value;" } + result should containLines( + ((Seq("""`endif // SYNTHESIS""", """initial begin""") ++ + expInit) :+ + """end"""): _* + ) + } + + it should "emit readmem inside `ifndef SYNTHESIS` block with MemoryNoSynthInit annotation" in { val annos = Seq( MemoryFileInlineAnnotation(mRef, filename = "text.hex", hexOrBinary = MemoryLoadFileType.Hex) ) ++ Seq(MemoryNoSynthInit) @@ -295,7 +344,9 @@ class MemInitSpec extends FirrtlFlatSpec { // Final deduplicated reference val dedupedRef = CircuitTarget("Top").module("Child").ref("m") - "MemoryRandomInitAnnotation" should "randomize memory in single deduped module" in { + behavior.of("MemoryInitDeduplication") + + it should "allow MemoryRandomInitAnnotation to randomize memory in single deduped module" in { val annos = Seq( MemoryRandomInitAnnotation(child1MRef), MemoryRandomInitAnnotation(child2MRef) @@ -304,7 +355,7 @@ class MemInitSpec extends FirrtlFlatSpec { result should containLine(" m[initvar] = _RAND_0[7:0];") } - "MemoryScalarInitAnnotation" should "initialize memory to 0 in deduped module" in { + it should "allow MemoryScalarInitAnnotation to initialize memory to 0 in deduped module" in { val annos = Seq( MemoryScalarInitAnnotation(child1MRef, value = 0), MemoryScalarInitAnnotation(child2MRef, value = 0) @@ -313,7 +364,7 @@ class MemInitSpec extends FirrtlFlatSpec { result should containLine(" m[initvar] = 0;") } - "MemoryArrayInitAnnotation" should "initialize memory with array of values in deduped module" in { + it should "allow MemoryArrayInitAnnotation to initialize memory with array of values in deduped module" in { val values = Seq.tabulate(32)(ii => 2 * ii + 5).map(BigInt(_)) val annos = Seq( MemoryArrayInitAnnotation(child1MRef, values), @@ -327,7 +378,7 @@ class MemInitSpec extends FirrtlFlatSpec { } } - "MemoryFileInlineAnnotation" should "emit $readmemh in deduped module" in { + it should "allow MemoryFileInlineAnnotation to emit $readmemh in deduped module" in { val annos = Seq( MemoryFileInlineAnnotation(child1MRef, filename = "text.hex"), MemoryFileInlineAnnotation(child2MRef, filename = "text.hex") @@ -336,7 +387,7 @@ class MemInitSpec extends FirrtlFlatSpec { result should containLine("""$readmemh("text.hex", """ + dedupedRef.name + """);""") } - "MemoryFileInlineAnnotation" should "fail dedup if not all instances have the annotation" in { + it should "fail dedup if not all instances have the MemoryFileInlineAnnotation" in { val annos = Seq( MemoryFileInlineAnnotation(child1MRef, filename = "text.hex") ) @@ -345,7 +396,7 @@ class MemInitSpec extends FirrtlFlatSpec { } } - "MemoryFileInlineAnnotation" should "fail dedup if instances have different init files" in { + it should "fail dedup if instances have different MemoryFileInlineAnnotation filenames" in { val annos = Seq( MemoryFileInlineAnnotation(child1MRef, filename = "text.hex"), MemoryFileInlineAnnotation(child2MRef, filename = "text.bin")