diff options
| author | Jack Koenig | 2019-04-22 13:46:37 -0700 |
|---|---|---|
| committer | GitHub | 2019-04-22 13:46:37 -0700 |
| commit | 99ae1d6649f1731c5dec2098b10733735232b72c (patch) | |
| tree | 04e7b0f4515fc9f79aa5f0d80aff2bb5805637c9 | |
| parent | bf66997b1a2438a322cd619ca2b6aeb0f0ac0ba0 (diff) | |
Change Memory Depth to a BigInt (#1075)
| -rw-r--r-- | src/main/proto/firrtl.proto | 15 | ||||
| -rw-r--r-- | src/main/scala/firrtl/Emitter.scala | 33 | ||||
| -rw-r--r-- | src/main/scala/firrtl/Visitor.scala | 38 | ||||
| -rw-r--r-- | src/main/scala/firrtl/WIR.scala | 2 | ||||
| -rw-r--r-- | src/main/scala/firrtl/ir/IR.scala | 2 | ||||
| -rw-r--r-- | src/main/scala/firrtl/passes/memlib/MemConf.scala | 4 | ||||
| -rw-r--r-- | src/main/scala/firrtl/passes/memlib/MemIR.scala | 2 | ||||
| -rw-r--r-- | src/main/scala/firrtl/proto/FromProto.scala | 21 | ||||
| -rw-r--r-- | src/main/scala/firrtl/proto/ToProto.scala | 10 | ||||
| -rw-r--r-- | src/test/scala/firrtlTests/MemSpec.scala | 68 | ||||
| -rw-r--r-- | src/test/scala/firrtlTests/ProtoBufSpec.scala | 45 |
11 files changed, 201 insertions, 39 deletions
diff --git a/src/main/proto/firrtl.proto b/src/main/proto/firrtl.proto index 8f115c5e..2552b989 100644 --- a/src/main/proto/firrtl.proto +++ b/src/main/proto/firrtl.proto @@ -110,7 +110,10 @@ message Firrtl { // Required. Type type = 2; // Required. - uint32 depth = 3; + oneof depth { + uint32 uint_depth = 3; + BigInt bigint_depth = 9; + } // Required. uint32 write_latency = 4; // Required. @@ -121,10 +124,18 @@ message Firrtl { } message CMemory { + // As alternative to using VectorType as type + message TypeAndDepth { + Type data_type = 1; + BigInt depth = 2; + } // Required. string id = 1; // Required. - Type.VectorType type = 2; + oneof type { + Type.VectorType vector_type = 2; + TypeAndDepth type_and_depth = 4; + } // Required. bool sync_read = 3; } diff --git a/src/main/scala/firrtl/Emitter.scala b/src/main/scala/firrtl/Emitter.scala index edbe9df6..7204eea6 100644 --- a/src/main/scala/firrtl/Emitter.scala +++ b/src/main/scala/firrtl/Emitter.scala @@ -419,6 +419,8 @@ class VerilogEmitter extends SeqTransform with Emitter { // An alternative approach is to have one always block per combination of clock and async reset, // but Formality doesn't allow more than 1 statement inside async reset always blocks val asyncResetAlwaysBlocks = mutable.ArrayBuffer[(Expression, Expression, Seq[Any])]() + // Used to determine type of initvar for initializing memories + var maxMemSize: BigInt = BigInt(0) val initials = ArrayBuffer[Seq[Any]]() // In Verilog, async reset registers are expressed using always blocks of the form: // always @(posedge clock or posedge reset) begin @@ -432,11 +434,18 @@ class VerilogEmitter extends SeqTransform with Emitter { val asyncInitials = ArrayBuffer[Seq[Any]]() val simulates = ArrayBuffer[Seq[Any]]() + def bigIntToVLit(bi: BigInt): String = + if (bi.isValidInt) bi.toString else s"${bi.bitLength}'d$bi" + + def declareVectorType(b: String, n: String, tpe: Type, size: BigInt, info: Info) = { + declares += Seq(b, " ", tpe, " ", n, " [0:", bigIntToVLit(size - 1), "];", info) + } + def declare(b: String, n: String, t: Type, info: Info) = t match { case tx: VectorType => - declares += Seq(b, " ", tx.tpe, " ", n, " [0:", tx.size - 1, "];", info) + declareVectorType(b, n, tx.tpe, tx.size, info) case tx => - declares += Seq(b, " ", tx, " ", n, ";", info) + declares += Seq(b, " ", tx, " ", n,";",info) } def assign(e: Expression, value: Expression, info: Info) { @@ -533,10 +542,13 @@ class VerilogEmitter extends SeqTransform with Emitter { } def initialize_mem(s: DefMemory) { + if (s.depth > maxMemSize) { + maxMemSize = s.depth + } val index = wref("initvar", s.dataType) val rstring = rand_string(s.dataType) initials += Seq("`ifdef RANDOMIZE_MEM_INIT") - initials += Seq("for (initvar = 0; initvar < ", s.depth, "; initvar = initvar+1)") + initials += Seq("for (initvar = 0; initvar < ", bigIntToVLit(s.depth), "; initvar = initvar+1)") initials += Seq(tab, WSubAccess(wref(s.name, s.dataType), index, s.dataType, FEMALE), " = ", rstring, ";") initials += Seq("`endif // RANDOMIZE_MEM_INIT") @@ -687,7 +699,7 @@ class VerilogEmitter extends SeqTransform with Emitter { case GroundType(IntWidth(width)) => width }) val decl = if (fullSize > (1 << 29)) "reg /* sparse */" else "reg" - declare(decl, sx.name, VectorType(sx.dataType, sx.depth), sx.info) + declareVectorType(decl, sx.name, sx.dataType, sx.depth, sx.info) initialize_mem(sx) if (sx.readLatency != 0 || sx.writeLatency != 1) throw EmitterException("All memories should be transformed into " + @@ -708,7 +720,7 @@ class VerilogEmitter extends SeqTransform with Emitter { // assign(en, netlist(en)) //;Connects value to m.r.en val mem = WRef(sx.name, memType(sx), MemKind, UNKNOWNGENDER) val memPort = WSubAccess(mem, addr, sx.dataType, UNKNOWNGENDER) - val depthValue = UIntLiteral(sx.depth, IntWidth(BigInt(sx.depth).bitLength)) + 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) @@ -788,7 +800,16 @@ class VerilogEmitter extends SeqTransform with Emitter { emit(Seq("`define RANDOM $random")) emit(Seq("`endif")) emit(Seq("`ifdef RANDOMIZE_MEM_INIT")) - emit(Seq(" integer initvar;")) + // 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 + if (maxMemSize.isValidInt) { + emit(Seq(" integer initvar;")) + } else { + // Width must be able to represent maxMemSize because that's the upper bound in init loop + 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")) emit(Seq("initial begin")) emit(Seq(" `ifdef RANDOMIZE")) diff --git a/src/main/scala/firrtl/Visitor.scala b/src/main/scala/firrtl/Visitor.scala index 91dfaae9..9914de70 100644 --- a/src/main/scala/firrtl/Visitor.scala +++ b/src/main/scala/firrtl/Visitor.scala @@ -139,6 +139,19 @@ class Visitor(infoMode: InfoMode) extends FIRRTLBaseVisitor[FirrtlNode] { } } + // Special case "type" of CHIRRTL mems because their size can be BigInt + private def visitCMemType(ctx: TypeContext): (Type, BigInt) = { + def loc: String = s"${ctx.getStart.getLine}:${ctx.getStart.getCharPositionInLine}" + ctx.getChild(0) match { + case typeContext: TypeContext => + val tpe = visitType(ctx.`type`) + val size = string2BigInt(ctx.intLit(0).getText) + (tpe, size) + case _ => + throw new ParserException(s"[$loc] Must provide cmem or smem with vector type, got ${ctx.getText}") + } + } + private def visitField[FirrtlNode](ctx: FieldContext): Field = { val flip = if (ctx.getChild(0).getText == "flip") Flip else Default Field(ctx.fieldId.getText, flip, visitType(ctx.`type`)) @@ -156,7 +169,7 @@ class Visitor(infoMode: InfoMode) extends FIRRTLBaseVisitor[FirrtlNode] { val readers = mutable.ArrayBuffer.empty[String] val writers = mutable.ArrayBuffer.empty[String] val readwriters = mutable.ArrayBuffer.empty[String] - case class ParamValue(typ: Option[Type] = None, lit: Option[Int] = None, ruw: Option[String] = None, unique: Boolean = true) + case class ParamValue(typ: Option[Type] = None, lit: Option[BigInt] = None, ruw: Option[String] = None, unique: Boolean = true) val fieldMap = mutable.HashMap[String, ParamValue]() def parseMemFields(memFields: Seq[MemFieldContext]): Unit = @@ -171,7 +184,7 @@ class Visitor(infoMode: InfoMode) extends FIRRTLBaseVisitor[FirrtlNode] { val paramDef = fieldName match { case "data-type" => ParamValue(typ = Some(visitType(field.`type`()))) case "read-under-write" => ParamValue(ruw = Some(field.ruw().getText)) // TODO - case _ => ParamValue(lit = Some(field.intLit().getText.toInt)) + case _ => ParamValue(lit = Some(BigInt(field.intLit().getText))) } if (fieldMap.contains(fieldName)) throw new ParameterRedefinedException(s"Redefinition of $fieldName in FIRRTL line:${field.start.getLine}") @@ -201,7 +214,8 @@ class Visitor(infoMode: InfoMode) extends FIRRTLBaseVisitor[FirrtlNode] { DefMemory(info, name = ctx.id(0).getText, dataType = fieldMap("data-type").typ.get, depth = lit("depth"), - writeLatency = lit("write-latency"), readLatency = lit("read-latency"), + writeLatency = lit("write-latency").toInt, + readLatency = lit("read-latency").toInt, readers = readers, writers = writers, readwriters = readwriters, readUnderWrite = ruw ) @@ -250,21 +264,11 @@ class Visitor(infoMode: InfoMode) extends FIRRTLBaseVisitor[FirrtlNode] { DefRegister(info, name, tpe, visitExp(ctx_exp(0)), reset, init) case "mem" => visitMem(ctx) case "cmem" => - val t = visitType(ctx.`type`()) - t match { - case (t: VectorType) => CDefMemory(info, ctx.id(0).getText, t.tpe, t.size, seq = false) - case _ => throw new ParserException(s"${ - info - }: Must provide cmem with vector type") - } + val (tpe, size) = visitCMemType(ctx.`type`()) + CDefMemory(info, ctx.id(0).getText, tpe, size, seq = false) case "smem" => - val t = visitType(ctx.`type`()) - t match { - case (t: VectorType) => CDefMemory(info, ctx.id(0).getText, t.tpe, t.size, seq = true) - case _ => throw new ParserException(s"${ - info - }: Must provide cmem with vector type") - } + val (tpe, size) = visitCMemType(ctx.`type`()) + CDefMemory(info, ctx.id(0).getText, tpe, size, seq = true) case "inst" => DefInstance(info, ctx.id(0).getText, ctx.id(1).getText) case "node" => DefNode(info, ctx.id(0).getText, visitExp(ctx_exp(0))) diff --git a/src/main/scala/firrtl/WIR.scala b/src/main/scala/firrtl/WIR.scala index 0ec37d34..cc2d87e7 100644 --- a/src/main/scala/firrtl/WIR.scala +++ b/src/main/scala/firrtl/WIR.scala @@ -313,7 +313,7 @@ case class CDefMemory( info: Info, name: String, tpe: Type, - size: Int, + size: BigInt, seq: Boolean) extends Statement with HasInfo { def serialize: String = (if (seq) "smem" else "cmem") + s" $name : ${tpe.serialize} [$size]" + info.serialize diff --git a/src/main/scala/firrtl/ir/IR.scala b/src/main/scala/firrtl/ir/IR.scala index 4c00bdd1..8124e1e6 100644 --- a/src/main/scala/firrtl/ir/IR.scala +++ b/src/main/scala/firrtl/ir/IR.scala @@ -289,7 +289,7 @@ case class DefMemory( info: Info, name: String, dataType: Type, - depth: Int, + depth: BigInt, writeLatency: Int, readLatency: Int, readers: Seq[String], diff --git a/src/main/scala/firrtl/passes/memlib/MemConf.scala b/src/main/scala/firrtl/passes/memlib/MemConf.scala index e53a5de7..18fcbf37 100644 --- a/src/main/scala/firrtl/passes/memlib/MemConf.scala +++ b/src/main/scala/firrtl/passes/memlib/MemConf.scala @@ -29,7 +29,7 @@ object MemPort { case class MemConf( name: String, - depth: Int, + depth: BigInt, width: Int, ports: Map[MemPort, Int], maskGranularity: Option[Int] @@ -57,7 +57,7 @@ object MemConf { }).flatten } - def apply(name: String, depth: Int, width: Int, readPorts: Int, writePorts: Int, readWritePorts: Int, maskGranularity: Option[Int]): MemConf = { + def apply(name: String, depth: BigInt, width: Int, readPorts: Int, writePorts: Int, readWritePorts: Int, maskGranularity: Option[Int]): MemConf = { val ports: Map[MemPort, Int] = (if (maskGranularity.isEmpty) { (if (writePorts == 0) Map.empty[MemPort, Int] else Map(WritePort -> writePorts)) ++ (if (readWritePorts == 0) Map.empty[MemPort, Int] else Map(ReadWritePort -> readWritePorts)) diff --git a/src/main/scala/firrtl/passes/memlib/MemIR.scala b/src/main/scala/firrtl/passes/memlib/MemIR.scala index 55f0f571..41691a0a 100644 --- a/src/main/scala/firrtl/passes/memlib/MemIR.scala +++ b/src/main/scala/firrtl/passes/memlib/MemIR.scala @@ -30,7 +30,7 @@ case class DefAnnotatedMemory( info: Info, name: String, dataType: Type, - depth: Int, + depth: BigInt, writeLatency: Int, readLatency: Int, readers: Seq[String], diff --git a/src/main/scala/firrtl/proto/FromProto.scala b/src/main/scala/firrtl/proto/FromProto.scala index e7d415cb..44e505f1 100644 --- a/src/main/scala/firrtl/proto/FromProto.scala +++ b/src/main/scala/firrtl/proto/FromProto.scala @@ -133,9 +133,19 @@ object FromProto { ir.Conditionally(convert(info), convert(when.getPredicate), conseq, alt) } + def convert(dt: Firrtl.Statement.CMemory.TypeAndDepth): (ir.Type, BigInt) = + (convert(dt.getDataType), convert(dt.getDepth)) + def convert(cmem: Firrtl.Statement.CMemory, info: Firrtl.SourceInfo): ir.Statement = { - val vtpe = convert(cmem.getType) - CDefMemory(convert(info), cmem.getId, vtpe.tpe, vtpe.size, cmem.getSyncRead) + import Firrtl.Statement.CMemory._ + val (tpe, depth) = cmem.getTypeCase.getNumber match { + case VECTOR_TYPE_FIELD_NUMBER => + val vtpe = convert(cmem.getVectorType) + (vtpe.tpe, BigInt(vtpe.size)) + case TYPE_AND_DEPTH_FIELD_NUMBER => + convert(cmem.getTypeAndDepth) + } + CDefMemory(convert(info), cmem.getId, tpe, depth, cmem.getSyncRead) } import Firrtl.Statement.MemoryPort.Direction._ @@ -165,7 +175,12 @@ object FromProto { val rs = mem.getReaderIdList.asScala val ws = mem.getWriterIdList.asScala val rws = mem.getReadwriterIdList.asScala - ir.DefMemory(convert(info), mem.getId, dtype, mem.getDepth, mem.getWriteLatency, mem.getReadLatency, + import Firrtl.Statement.Memory._ + val depth = mem.getDepthCase.getNumber match { + case UINT_DEPTH_FIELD_NUMBER => BigInt(mem.getUintDepth) + case BIGINT_DEPTH_FIELD_NUMBER => convert(mem.getBigintDepth) + } + ir.DefMemory(convert(info), mem.getId, dtype, depth, mem.getWriteLatency, mem.getReadLatency, rs, ws, rws, None) } diff --git a/src/main/scala/firrtl/proto/ToProto.scala b/src/main/scala/firrtl/proto/ToProto.scala index b0b59e06..8681e8f2 100644 --- a/src/main/scala/firrtl/proto/ToProto.scala +++ b/src/main/scala/firrtl/proto/ToProto.scala @@ -190,6 +190,11 @@ object ToProto { } } + def convert(tpe: ir.Type, depth: BigInt): Firrtl.Statement.CMemory.TypeAndDepth.Builder = + Firrtl.Statement.CMemory.TypeAndDepth.newBuilder() + .setDataType(convert(tpe)) + .setDepth(convertToBigInt(depth)) + def convert(stmt: ir.Statement): Seq[Firrtl.Statement.Builder] = { stmt match { case ir.Block(stmts) => stmts.flatMap(convert(_)) @@ -259,7 +264,7 @@ object ToProto { val mem = Firrtl.Statement.Memory.newBuilder() .setId(name) .setType(convert(dtype)) - .setDepth(depth) + .setBigintDepth(convertToBigInt(depth)) .setWriteLatency(wlat) .setReadLatency(rlat) mem.addAllReaderId(rs.asJava) @@ -267,10 +272,9 @@ object ToProto { mem.addAllReadwriterId(rws.asJava) sb.setMemory(mem) case CDefMemory(_, name, tpe, size, seq) => - val tpeb = convert(ir.VectorType(tpe, size)) val mb = Firrtl.Statement.CMemory.newBuilder() .setId(name) - .setType(tpeb) + .setTypeAndDepth(convert(tpe, size)) .setSyncRead(seq) sb.setCmemory(mb) case CDefMPort(_, name, _, mem, exprs, dir) => diff --git a/src/test/scala/firrtlTests/MemSpec.scala b/src/test/scala/firrtlTests/MemSpec.scala index 67b7e74d..612a952d 100644 --- a/src/test/scala/firrtlTests/MemSpec.scala +++ b/src/test/scala/firrtlTests/MemSpec.scala @@ -2,7 +2,10 @@ package firrtlTests -class MemSpec extends FirrtlPropSpec { +import firrtl._ +import FirrtlCheckers._ + +class MemSpec extends FirrtlPropSpec with FirrtlMatchers { property("Zero-ported mems should be supported!") { runFirrtlTest("ZeroPortMem", "/features") @@ -11,5 +14,68 @@ class MemSpec extends FirrtlPropSpec { property("Mems with zero-width elements should be supported!") { runFirrtlTest("ZeroWidthMem", "/features") } + + property("Very large memories should be supported") { + val addrWidth = 65 + val memSize = BigInt(1) << addrWidth + val input = + s""" + |circuit Test : + | module Test : + | input clock : Clock + | input raddr : UInt<$addrWidth> + | output rdata : UInt<8> + | input wdata : UInt<8> + | input waddr : UInt<$addrWidth> + | input wen : UInt<1> + | + | mem m : + | data-type => UInt<8> + | depth => $memSize + | reader => r + | writer => w + | read-latency => 1 + | write-latency => 1 + | read-under-write => undefined + | rdata <= m.r.data + | m.r.addr <= raddr + | m.r.en <= UInt(1) + | m.r.clk <= clock + | m.w.addr <= waddr + | m.w.data <= wdata + | m.w.en <= wen + | m.w.clk <= clock + | m.w.mask <= UInt(1) + """.stripMargin + val result = (new VerilogCompiler).compileAndEmit(CircuitState(parse(input), ChirrtlForm, List.empty)) + // TODO Not great that it includes the sparse comment for VCS + result should containLine (s"reg /* sparse */ [7:0] m [0:$addrWidth'd${memSize-1}];") + } + + property("Very large CHIRRTL memories should be supported") { + val addrWidth = 65 + val memSize = BigInt(1) << addrWidth + val input = + s""" + |circuit Test : + | module Test : + | input clock : Clock + | input raddr : UInt<$addrWidth> + | output rdata : UInt<8> + | input wdata : UInt<8> + | input waddr : UInt<$addrWidth> + | input wen : UInt<1> + | + | cmem m : UInt<8>[$memSize] + | read mport r = m[raddr], clock + | rdata <= r + | write mport w = m[waddr], clock + | when wen : + | w <= wdata + """.stripMargin + val result = (new VerilogCompiler).compileAndEmit(CircuitState(parse(input), ChirrtlForm, List.empty)) + // TODO Not great that it includes the sparse comment for VCS + result should containLine (s"reg /* sparse */ [7:0] m [0:$addrWidth'd${memSize-1}];") + } } diff --git a/src/test/scala/firrtlTests/ProtoBufSpec.scala b/src/test/scala/firrtlTests/ProtoBufSpec.scala index 090a7fea..7a0c3eeb 100644 --- a/src/test/scala/firrtlTests/ProtoBufSpec.scala +++ b/src/test/scala/firrtlTests/ProtoBufSpec.scala @@ -107,12 +107,12 @@ class ProtoBufSpec extends FirrtlFlatSpec { FromProto.convert(ToProto.convert(ext).build) should equal (ext) } - it should "supported FixedType" in { + it should "support FixedType" in { val ftpe = ir.FixedType(IntWidth(8), IntWidth(4)) FromProto.convert(ToProto.convert(ftpe).build) should equal (ftpe) } - it should "supported FixedLiteral" in { + it should "support FixedLiteral" in { val flit = ir.FixedLiteral(3, IntWidth(8), IntWidth(4)) FromProto.convert(ToProto.convert(flit).build) should equal (flit) } @@ -138,6 +138,47 @@ class ProtoBufSpec extends FirrtlFlatSpec { FromProto.convert(ToProto.convert(slit).build) should equal (slit) } + // Backwards compatibility + it should "support mems using old uint32 and new BigInt" in { + val size = 128 + val mem = DefMemory(NoInfo, "m", UIntType(IntWidth(8)), size, 1, 1, List("r"), List("w"), List("rw")) + val builder = ToProto.convert(mem).head + val defaultProto = builder.build() + val oldProto = Firrtl.Statement.newBuilder().setMemory( + builder.getMemoryBuilder.clearDepth().setUintDepth(size) + ).build() + // These Proto messages are not the same + defaultProto shouldNot equal (oldProto) + + val defaultMem = FromProto.convert(defaultProto) + val oldMem = FromProto.convert(oldProto) + + // But they both deserialize to the original! + defaultMem should equal (mem) + oldMem should equal (mem) + } + + // Backwards compatibility + it should "support cmems using old VectorType and new TypeAndDepth" in { + val size = 128 + val cmem = CDefMemory(NoInfo, "m", UIntType(IntWidth(8)), size, true) + val vtpe = ToProto.convert(VectorType(UIntType(IntWidth(8)), size)) + val builder = ToProto.convert(cmem).head + val defaultProto = builder.build() + val oldProto = Firrtl.Statement.newBuilder().setCmemory( + builder.getCmemoryBuilder.clearTypeAndDepth().setVectorType(vtpe) + ).build() + // These Proto messages are not the same + defaultProto shouldNot equal (oldProto) + + val defaultCMem = FromProto.convert(defaultProto) + val oldCMem = FromProto.convert(oldProto) + + // But they both deserialize to the original! + defaultCMem should equal (cmem) + oldCMem should equal (cmem) + } + it should "support AsyncResetTypes" in { val port = ir.Port(ir.NoInfo, "reset", ir.Input, ir.AsyncResetType) FromProto.convert(ToProto.convert(port).build) should equal (port) |
