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 | |
| 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')
| -rw-r--r-- | src/main/scala/firrtl/EmissionOption.scala | 12 | ||||
| -rw-r--r-- | src/main/scala/firrtl/Emitter.scala | 60 | ||||
| -rw-r--r-- | src/main/scala/firrtl/annotations/MemoryInitAnnotation.scala | 35 | ||||
| -rw-r--r-- | src/main/scala/firrtl/passes/LowerTypes.scala | 19 | ||||
| -rw-r--r-- | src/test/scala/firrtlTests/MemoryInitSpec.scala | 203 | ||||
| -rw-r--r-- | src/test/scala/firrtlTests/execution/VerilogExecution.scala | 6 |
6 files changed, 319 insertions, 16 deletions
diff --git a/src/main/scala/firrtl/EmissionOption.scala b/src/main/scala/firrtl/EmissionOption.scala index 11c16e2c..91db1f53 100644 --- a/src/main/scala/firrtl/EmissionOption.scala +++ b/src/main/scala/firrtl/EmissionOption.scala @@ -9,6 +9,18 @@ package firrtl */ trait EmissionOption +/** Emission customization options for memories */ +trait MemoryEmissionOption extends EmissionOption { + def initValue: MemoryInitValue = MemoryRandomInit +} + +sealed trait MemoryInitValue +case object MemoryRandomInit extends MemoryInitValue +case class MemoryScalarInit(value: BigInt) extends MemoryInitValue +case class MemoryArrayInit(values: Seq[BigInt]) extends MemoryInitValue + +/** default Emitter behavior for memories */ +case object MemoryEmissionOptionDefault extends MemoryEmissionOption /** Emission customization options for registers */ trait RegisterEmissionOption extends EmissionOption { diff --git a/src/main/scala/firrtl/Emitter.scala b/src/main/scala/firrtl/Emitter.scala index 60099137..3b065bf4 100644 --- a/src/main/scala/firrtl/Emitter.scala +++ b/src/main/scala/firrtl/Emitter.scala @@ -475,12 +475,16 @@ class VerilogEmitter extends SeqTransform with Emitter { */ private[firrtl] class EmissionOptions(annotations: AnnotationSeq) { // Private so that we can present an immutable API + private val memoryEmissionOption = new EmissionOptionMap[MemoryEmissionOption](MemoryEmissionOptionDefault) private val registerEmissionOption = new EmissionOptionMap[RegisterEmissionOption](RegisterEmissionOptionDefault) private val wireEmissionOption = new EmissionOptionMap[WireEmissionOption](WireEmissionOptionDefault) private val portEmissionOption = new EmissionOptionMap[PortEmissionOption](PortEmissionOptionDefault) private val nodeEmissionOption = new EmissionOptionMap[NodeEmissionOption](NodeEmissionOptionDefault) private val connectEmissionOption = new EmissionOptionMap[ConnectEmissionOption](ConnectEmissionOptionDefault) + def getMemoryEmissionOption(target: ReferenceTarget): MemoryEmissionOption = + memoryEmissionOption(target) + def getRegisterEmissionOption(target: ReferenceTarget): RegisterEmissionOption = registerEmissionOption(target) @@ -500,6 +504,7 @@ class VerilogEmitter extends SeqTransform with Emitter { case m : SingleTargetAnnotation[ReferenceTarget] @unchecked with EmissionOption => m } // using multiple foreach instead of a single partial function as an Annotation can gather multiple EmissionOptions for simplicity + emissionAnnos.foreach { case a :MemoryEmissionOption => memoryEmissionOption += ((a.target,a)) case _ => } emissionAnnos.foreach { case a :RegisterEmissionOption => registerEmissionOption += ((a.target,a)) case _ => } emissionAnnos.foreach { case a :WireEmissionOption => wireEmissionOption += ((a.target,a)) case _ => } emissionAnnos.foreach { case a :PortEmissionOption => portEmissionOption += ((a.target,a)) case _ => } @@ -586,6 +591,8 @@ class VerilogEmitter extends SeqTransform with Emitter { // reset. To fix this, we need extra initial block logic to reset async reset registers again // post-randomize. val asyncInitials = ArrayBuffer[Seq[Any]]() + // memories need to be initialized even when randomization is disabled + val memoryInitials = ArrayBuffer[Seq[Any]]() val simulates = ArrayBuffer[Seq[Any]]() def bigIntToVLit(bi: BigInt): String = @@ -751,15 +758,45 @@ class VerilogEmitter extends SeqTransform with Emitter { } } - def initialize_mem(s: DefMemory): Unit = { + def initialize_mem(s: DefMemory, opt: MemoryEmissionOption): Unit = { if (s.depth > maxMemSize) { maxMemSize = s.depth } - val index = wref("initvar", s.dataType) - val rstring = rand_string(s.dataType, "RANDOMIZE_MEM_INIT") - ifdefInitials("RANDOMIZE_MEM_INIT") += Seq("for (initvar = 0; initvar < ", bigIntToVLit(s.depth), "; initvar = initvar+1)") - ifdefInitials("RANDOMIZE_MEM_INIT") += Seq(tab, WSubAccess(wref(s.name, s.dataType), index, s.dataType, SinkFlow), - " = ", rstring, ";") + + val dataWidth = bitWidth(s.dataType) + val maxDataValue = (BigInt(1) << dataWidth.toInt) - 1 + + def checkValueRange(value: BigInt, at: String): Unit = { + if(value < 0) throw EmitterException(s"Memory ${at} cannot be initialized with negative value: $value") + if(value > maxDataValue) throw EmitterException(s"Memory ${at} cannot be initialized with value: $value. Too large (> $maxDataValue)!") + } + + opt.initValue match { + case MemoryArrayInit(values) => + if(values.length != s.depth) throw EmitterException( + s"Memory ${s.name} of depth ${s.depth} cannot be initialized with an array of length ${values.length}!" + ) + val memName = LowerTypes.loweredName(wref(s.name, s.dataType)) + values.zipWithIndex.foreach { case (value, addr) => + checkValueRange(value, s"${s.name}[$addr]") + val access = s"$memName[${bigIntToVLit(addr)}]" + memoryInitials += Seq(access, " = ", bigIntToVLit(value), ";") + } + case MemoryScalarInit(value) => + checkValueRange(value, s.name) + // note: s.dataType is the incorrect type for initvar, but it is ignored in the serialization + val index = wref("initvar", s.dataType) + memoryInitials += Seq("for (initvar = 0; initvar < ", bigIntToVLit(s.depth), "; initvar = initvar+1)") + memoryInitials += Seq(tab, WSubAccess(wref(s.name, s.dataType), index, s.dataType, SinkFlow), + " = ", bigIntToVLit(value), ";") + case MemoryRandomInit => + // note: s.dataType is the incorrect type for initvar, but it is ignored in the serialization + val index = wref("initvar", s.dataType) + val rstring = rand_string(s.dataType, "RANDOMIZE_MEM_INIT") + ifdefInitials("RANDOMIZE_MEM_INIT") += Seq("for (initvar = 0; initvar < ", bigIntToVLit(s.depth), "; initvar = initvar+1)") + ifdefInitials("RANDOMIZE_MEM_INIT") += Seq(tab, WSubAccess(wref(s.name, s.dataType), index, s.dataType, SinkFlow), + " = ", rstring, ";") + } } def simulate(clk: Expression, en: Expression, s: Seq[Any], cond: Option[String], info: Info) = { @@ -914,12 +951,13 @@ class VerilogEmitter extends SeqTransform with Emitter { } instdeclares += Seq(");") case sx: DefMemory => + val options = emissionOptions.getMemoryEmissionOption(moduleTarget.ref(sx.name)) val fullSize = sx.depth * (sx.dataType match { case GroundType(IntWidth(width)) => width }) val decl = if (fullSize > (1 << 29)) "reg /* sparse */" else "reg" declareVectorType(decl, sx.name, sx.dataType, sx.depth, sx.info) - initialize_mem(sx) + initialize_mem(sx, options) if (sx.readLatency != 0 || sx.writeLatency != 1) throw EmitterException("All memories should be transformed into " + "blackboxes or combinational by previous passses") @@ -1014,7 +1052,7 @@ class VerilogEmitter extends SeqTransform with Emitter { emit(Seq(tab, "end")) } - if (initials.nonEmpty || ifdefInitials.nonEmpty) { + if (initials.nonEmpty || ifdefInitials.nonEmpty || memoryInitials.nonEmpty) { emit(Seq("// Register and memory initialization")) emit(Seq("`ifdef RANDOMIZE_GARBAGE_ASSIGN")) emit(Seq("`define RANDOMIZE")) @@ -1031,7 +1069,8 @@ class VerilogEmitter extends SeqTransform with Emitter { emit(Seq("`ifndef RANDOM")) emit(Seq("`define RANDOM $random")) emit(Seq("`endif")) - emit(Seq("`ifdef RANDOMIZE_MEM_INIT")) + // the initvar is also used to initialize memories to constants + if(memoryInitials.isEmpty) emit(Seq("`ifdef RANDOMIZE_MEM_INIT")) // Since simulators don't actually support memories larger than 2^31 - 1, there is no reason // to change Verilog emission in the common case. Instead, we only emit a larger initvar // where necessary @@ -1042,7 +1081,7 @@ class VerilogEmitter extends SeqTransform with Emitter { val width = maxMemSize.bitLength - 1 // minus one because [width-1:0] has a width of "width" emit(Seq(s" reg [$width:0] initvar;")) } - emit(Seq("`endif")) + if(memoryInitials.isEmpty) emit(Seq("`endif")) emit(Seq("`ifndef SYNTHESIS")) // User-defined macro of code to run before an initial block emit(Seq("`ifdef FIRRTL_BEFORE_INITIAL")) @@ -1072,6 +1111,7 @@ 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)) emit(Seq("end // initial")) // User-defined macro of code to run after an initial block emit(Seq("`ifdef FIRRTL_AFTER_INITIAL")) diff --git a/src/main/scala/firrtl/annotations/MemoryInitAnnotation.scala b/src/main/scala/firrtl/annotations/MemoryInitAnnotation.scala new file mode 100644 index 00000000..44a8e3b5 --- /dev/null +++ b/src/main/scala/firrtl/annotations/MemoryInitAnnotation.scala @@ -0,0 +1,35 @@ +// See LICENSE for license details. + +package firrtl.annotations + +import firrtl.{MemoryArrayInit, MemoryEmissionOption, MemoryInitValue, MemoryRandomInit, MemoryScalarInit} + +/** + * Represents the initial value of the annotated memory. + * While not supported on normal ASIC flows, it can be useful for simulation and FPGA flows. + * This annotation is consumed by the verilog emitter. + */ +sealed trait MemoryInitAnnotation extends SingleTargetAnnotation[ReferenceTarget] with MemoryEmissionOption { + def isRandomInit: Boolean +} + +/** Randomly initialize the `target` memory. This is the same as the default behavior. */ +case class MemoryRandomInitAnnotation(target: ReferenceTarget) extends MemoryInitAnnotation { + override def duplicate(n: ReferenceTarget): Annotation = copy(n) + override def initValue: MemoryInitValue = MemoryRandomInit + override def isRandomInit: Boolean = true +} + +/** Initialize all entries of the `target` memory with the scalar `value`. */ +case class MemoryScalarInitAnnotation(target: ReferenceTarget, value: BigInt) extends MemoryInitAnnotation { + override def duplicate(n: ReferenceTarget): Annotation = copy(n) + override def initValue: MemoryInitValue = MemoryScalarInit(value) + override def isRandomInit: Boolean = false +} + +/** Initialize the `target` memory with the array of `values` which must be the same size as the memory depth. */ +case class MemoryArrayInitAnnotation(target: ReferenceTarget, values: Seq[BigInt]) extends MemoryInitAnnotation { + override def duplicate(n: ReferenceTarget): Annotation = copy(n) + override def initValue: MemoryInitValue = MemoryArrayInit(values) + override def isRandomInit: Boolean = false +}
\ No newline at end of file diff --git a/src/main/scala/firrtl/passes/LowerTypes.scala b/src/main/scala/firrtl/passes/LowerTypes.scala index 4a87ff8b..ccfe5828 100644 --- a/src/main/scala/firrtl/passes/LowerTypes.scala +++ b/src/main/scala/firrtl/passes/LowerTypes.scala @@ -8,6 +8,7 @@ import firrtl.ir._ import firrtl.Utils._ import MemPortUtils.memType import firrtl.Mappers._ +import firrtl.annotations.MemoryInitAnnotation /** Removes all aggregate types from a [[firrtl.ir.Circuit]] * @@ -166,9 +167,9 @@ object LowerTypes extends Transform with DependencyAPIMigration { case e @ (_: UIntLiteral | _: SIntLiteral) => e } def lowerTypesStmt(memDataTypeMap: MemDataTypeMap, - minfo: Info, mname: String, renames: RenameMap)(s: Statement): Statement = { + minfo: Info, mname: String, renames: RenameMap, initializedMems: Set[(String, String)])(s: Statement): Statement = { val info = get_info(s) match {case NoInfo => minfo case x => x} - s map lowerTypesStmt(memDataTypeMap, info, mname, renames) match { + s map lowerTypesStmt(memDataTypeMap, info, mname, renames, initializedMems) match { case s: DefWire => s.tpe match { case _: GroundType => s case _ => @@ -210,6 +211,10 @@ object LowerTypes extends Transform with DependencyAPIMigration { sx.dataType match { case _: GroundType => sx case _ => + // right now only ground type memories can be initialized + if(initializedMems.contains((mname, sx.name))) { + error(s"Cannot initialize memory of non ground type ${sx.dataType.serialize}")(info, mname) + } // Rename ports val seen: mutable.Set[String] = mutable.Set[String]() create_exps(sx.name, memType(sx)) foreach { e => @@ -264,7 +269,7 @@ object LowerTypes extends Transform with DependencyAPIMigration { } } - def lowerTypes(renames: RenameMap)(m: DefModule): DefModule = { + def lowerTypes(renames: RenameMap, initializedMems: Set[(String, String)])(m: DefModule): DefModule = { val memDataTypeMap = new MemDataTypeMap renames.setModule(m.name) // Lower Ports @@ -280,15 +285,19 @@ object LowerTypes extends Transform with DependencyAPIMigration { case m: ExtModule => m copy (ports = portsx) case m: Module => - m copy (ports = portsx) map lowerTypesStmt(memDataTypeMap, m.info, m.name, renames) + m copy (ports = portsx) map lowerTypesStmt(memDataTypeMap, m.info, m.name, renames, initializedMems) } } def execute(state: CircuitState): CircuitState = { + // remember which memories need to be initialized, for these memories, lowering non-ground types is not supported + val initializedMems = state.annotations.collect{ + case m : MemoryInitAnnotation if !m.isRandomInit => + (m.target.encapsulatingModule, m.target.ref) }.toSet val c = state.circuit val renames = RenameMap() renames.setCircuit(c.main) - val result = c copy (modules = c.modules map lowerTypes(renames)) + val result = c copy (modules = c.modules map lowerTypes(renames, initializedMems)) CircuitState(result, outputForm, state.annotations, Some(renames)) } } 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") |
