diff options
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") |
