diff options
| author | Kevin Laeufer | 2020-06-22 19:35:41 +0000 |
|---|---|---|
| committer | GitHub | 2020-06-22 19:35:41 +0000 |
| commit | 732d08761a97faedb878f022927c2cb429398d6f (patch) | |
| tree | b1aa4fe6e55906da5217719ea860bce15f470327 /src/test | |
| parent | a25b1af3b6b842b8ce8de36e5f0c11b88756f09e (diff) | |
Support Memory Initialization for Simulation and FPGA Flows (#1645)
* Support Memory Initialization for Simulation and FPGA Flows
This adds a minimal annotation that allows users to
influence if memories are randomly initialized,
if all entries are initialized to the same scalar or
if each entry should be initialized to a different value.
We use the init block in order to initialize memories
which is supported by verilator as well as yosys
and has previously been used to randomize the initial
memory contents.
* LowerTypes: error when trying to split up a memory with MemoryInitAnnotation
Currently the MemoryInitAnnotation only works for
ground-type memories.
We catch misuse of this annotation at the point of
the firrtl compiler at which memories on non-ground type
get split up, i.e., the LowerTypes transform.
Chisel should try to prevent annotating non-ground
type memories in the frontend, but it is nice to
have an additional check.
* MemoryInitSpec: test JSON deserialization
* MemoryInitAnnotation: split up into three different annotations instead of exposing MemoryInitValue
Co-authored-by: Albert Magyar <albert.magyar@gmail.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Diffstat (limited to 'src/test')
| -rw-r--r-- | src/test/scala/firrtlTests/MemoryInitSpec.scala | 203 | ||||
| -rw-r--r-- | src/test/scala/firrtlTests/execution/VerilogExecution.scala | 6 |
2 files changed, 208 insertions, 1 deletions
diff --git a/src/test/scala/firrtlTests/MemoryInitSpec.scala b/src/test/scala/firrtlTests/MemoryInitSpec.scala new file mode 100644 index 00000000..0826746b --- /dev/null +++ b/src/test/scala/firrtlTests/MemoryInitSpec.scala @@ -0,0 +1,203 @@ +// See LICENSE for license details. + +package firrtlTests + +import firrtl._ +import firrtl.annotations._ +import firrtl.testutils.FirrtlCheckers.containLine +import firrtl.testutils.FirrtlFlatSpec +import firrtlTests.execution._ + +class MemInitSpec extends FirrtlFlatSpec { + def input(tpe: String): String = + s""" + |circuit MemTest: + | module MemTest: + | input clock : Clock + | input rAddr : UInt<5> + | input rEnable : UInt<1> + | input wAddr : UInt<5> + | input wData : $tpe + | input wEnable : UInt<1> + | output rData : $tpe + | + | mem m: + | data-type => $tpe + | depth => 32 + | reader => r + | writer => w + | read-latency => 1 + | write-latency => 1 + | read-under-write => new + | + | m.r.clk <= clock + | m.r.addr <= rAddr + | m.r.en <= rEnable + | rData <= m.r.data + | + | m.w.clk <= clock + | m.w.addr <= wAddr + | m.w.en <= wEnable + | m.w.data <= wData + | m.w.mask is invalid + | + |""".stripMargin + + val mRef = CircuitTarget("MemTest").module("MemTest").ref("m") + def compile(annos: AnnotationSeq, tpe: String = "UInt<32>"): CircuitState = { + (new VerilogCompiler).compileAndEmit(CircuitState(parse(input(tpe)), ChirrtlForm, annos)) + } + + "NoAnnotation" should "create a randomized initialization" in { + val annos = Seq() + val result = compile(annos) + result should containLine (" m[initvar] = _RAND_0[31:0];") + } + + "MemoryRandomInitAnnotation" should "create a randomized initialization" in { + val annos = Seq(MemoryRandomInitAnnotation(mRef)) + val result = compile(annos) + result should containLine (" m[initvar] = _RAND_0[31:0];") + } + + "MemoryScalarInitAnnotation w/ 0" should "create an initialization with all zeros" in { + val annos = Seq(MemoryScalarInitAnnotation(mRef, 0)) + val result = compile(annos) + result should containLine(" m[initvar] = 0;") + } + + Seq(1, 3, 30, 400, 12345).foreach { value => + s"MemoryScalarInitAnnotation w/ $value" should + s"create an initialization with all values set to $value" in { + val annos = Seq(MemoryScalarInitAnnotation(mRef, value)) + val result = compile(annos) + result should containLine(s" m[initvar] = $value;") + } + } + + "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(annos) + values.zipWithIndex.foreach { case (value, addr) => + result should containLine(s" m[$addr] = $value;") + } + } + + "MemoryScalarInitAnnotation" should "fail for a negative value" in { + assertThrows[EmitterException] { + compile(Seq(MemoryScalarInitAnnotation(mRef, -1))) + } + } + + "MemoryScalarInitAnnotation" should "fail for a value that is too large" in { + assertThrows[EmitterException] { + compile(Seq(MemoryScalarInitAnnotation(mRef, BigInt(1) << 32))) + } + } + + "MemoryArrayInitAnnotation" should "fail for a negative value" in { + assertThrows[EmitterException] { + val values = Seq.tabulate(32)(_ => BigInt(-1)) + compile(Seq(MemoryArrayInitAnnotation(mRef, values))) + } + } + + "MemoryArrayInitAnnotation" should "fail for a value that is too large" in { + assertThrows[EmitterException] { + val values = Seq.tabulate(32)(_ => BigInt(1) << 32) + compile(Seq(MemoryArrayInitAnnotation(mRef, values))) + } + } + + "MemoryArrayInitAnnotation" should "fail if the number of values is too small" in { + assertThrows[EmitterException] { + val values = Seq.tabulate(31)(_ => BigInt(1)) + compile(Seq(MemoryArrayInitAnnotation(mRef, values))) + } + } + + "MemoryArrayInitAnnotation" should "fail if the number of values is too large" in { + assertThrows[EmitterException] { + val values = Seq.tabulate(33)(_ => BigInt(1)) + compile(Seq(MemoryArrayInitAnnotation(mRef, values))) + } + } + + "MemoryScalarInitAnnotation on Memory with Vector type" should "fail" in { + val caught = intercept[Exception] { + val annos = Seq(MemoryScalarInitAnnotation(mRef, 0)) + compile(annos, "UInt<32>[2]") + } + assert(caught.getMessage.endsWith("[module MemTest] Cannot initialize memory of non ground type UInt<32>[2]")) + } + + "MemoryScalarInitAnnotation on Memory with Bundle type" should "fail" in { + val caught = intercept[Exception] { + val annos = Seq(MemoryScalarInitAnnotation(mRef, 0)) + compile(annos, "{real: SInt<10>, imag: SInt<10>}") + } + assert(caught.getMessage.endsWith("[module MemTest] Cannot initialize memory of non ground type { real : SInt<10>, imag : SInt<10>}")) + } + + private def jsonAnno(name: String, suffix: String): String = + s"""[{"class": "firrtl.annotations.$name", "target": "~MemTest|MemTest>m"$suffix}]""" + + "MemoryRandomInitAnnotation" should "load from JSON" in { + val json = jsonAnno("MemoryRandomInitAnnotation", "") + val annos = JsonProtocol.deserialize(json) + assert(annos == Seq(MemoryRandomInitAnnotation(mRef))) + } + + "MemoryScalarInitAnnotation" should "load from JSON" in { + val json = jsonAnno("MemoryScalarInitAnnotation", """, "value": 1234567890""") + val annos = JsonProtocol.deserialize(json) + assert(annos == Seq(MemoryScalarInitAnnotation(mRef, 1234567890))) + } + + "MemoryArrayInitAnnotation" should "load from JSON" 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))) + } + +} + +abstract class MemInitExecutionSpec(values: Seq[Int], init: ReferenceTarget => Annotation) + extends SimpleExecutionTest with VerilogExecution { + override val body: String = + s""" + |mem m: + | data-type => UInt<32> + | depth => ${values.length} + | reader => r + | read-latency => 1 + | write-latency => 1 + | read-under-write => new + |m.r.clk <= clock + |m.r.en <= UInt<1>(1) + |""".stripMargin + + val mRef = CircuitTarget("dut").module("dut").ref("m") + override val customAnnotations: AnnotationSeq = Seq(init(mRef)) + + override def commands: Seq[SimpleTestCommand] = (Seq(-1) ++ values).zipWithIndex.map { case (value, addr) => + if(value == -1) { Seq(Poke("m.r.addr", addr)) } + else if(addr >= values.length) { Seq(Expect("m.r.data", value)) } + else { Seq(Poke("m.r.addr", addr), Expect("m.r.data", value)) } + }.flatMap(_ ++ Seq(Step(1))) +} + +class MemScalarInit0ExecutionSpec extends MemInitExecutionSpec( + Seq.tabulate(31)(_ => 0), r => MemoryScalarInitAnnotation(r, 0) +) {} + +class MemScalarInit17ExecutionSpec extends MemInitExecutionSpec( + Seq.tabulate(31)(_ => 17), r => MemoryScalarInitAnnotation(r, 17) +) {} + +class MemArrayInitExecutionSpec extends MemInitExecutionSpec( + Seq.tabulate(31)(ii => ii * 5 + 7), + r => MemoryArrayInitAnnotation(r, Seq.tabulate(31)(ii => ii * 5 + 7).map(BigInt(_))) +) {} diff --git a/src/test/scala/firrtlTests/execution/VerilogExecution.scala b/src/test/scala/firrtlTests/execution/VerilogExecution.scala index f80a5ee6..bf3d1461 100644 --- a/src/test/scala/firrtlTests/execution/VerilogExecution.scala +++ b/src/test/scala/firrtlTests/execution/VerilogExecution.scala @@ -13,11 +13,15 @@ import firrtl.options.TargetDirAnnotation */ trait VerilogExecution extends TestExecution { this: SimpleExecutionTest => + + /** can be overwritten to mix-in custom annotations */ + val customAnnotations: AnnotationSeq = Seq() + def runEmittedDUT(c: Circuit, testDir: File): Unit = { // Run FIRRTL, emit Verilog file val cAnno = FirrtlCircuitAnnotation(c) val tdAnno = TargetDirAnnotation(testDir.getAbsolutePath) - (new FirrtlStage).run(AnnotationSeq(Seq(cAnno, tdAnno))) + (new FirrtlStage).run(AnnotationSeq(Seq(cAnno, tdAnno) ++ customAnnotations)) // Copy harness resource to test directory val harness = new File(testDir, s"top.cpp") |
