diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 733ebcf6..00000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "mdf"] - path = mdf - url = https://github.com/ucb-bar/plsi-mdf.git diff --git a/build.sbt b/build.sbt index d8840865..1fe83505 100644 --- a/build.sbt +++ b/build.sbt @@ -14,7 +14,8 @@ lazy val commonSettings = Seq( dep: String => "edu.berkeley.cs" %% dep % sys.props.getOrElse(dep + "Version", defaultVersions(dep)) }, libraryDependencies ++= Seq( - "org.scalatest" %% "scalatest" % "3.2.2" % "test", + "com.typesafe.play" %% "play-json" % "2.9.2", + "org.scalatest" %% "scalatest" % "3.2.9" % "test", ), resolvers ++= Seq( Resolver.sonatypeRepo("snapshots"), @@ -25,17 +26,10 @@ lazy val commonSettings = Seq( disablePlugins(sbtassembly.AssemblyPlugin) -lazy val mdf = (project in file("mdf/scalalib")) -lazy val macros = (project in file("macros")) - .dependsOn(mdf) - .settings(commonSettings) - .settings( - mainClass := Some("barstools.macros.MacroCompiler") - ) - .enablePlugins(sbtassembly.AssemblyPlugin) +enablePlugins(sbtassembly.AssemblyPlugin) lazy val tapeout = (project in file("tapeout")) .settings(commonSettings) .settings(scalacOptions in Test ++= Seq("-language:reflectiveCalls")) -lazy val root = (project in file(".")).aggregate(macros, tapeout) +lazy val root = (project in file(".")).aggregate(tapeout) diff --git a/macros/build.sbt b/macros/build.sbt deleted file mode 100644 index 65e9704a..00000000 --- a/macros/build.sbt +++ /dev/null @@ -1 +0,0 @@ -enablePlugins(sbtassembly.AssemblyPlugin) diff --git a/mdf b/mdf deleted file mode 160000 index e588024d..00000000 --- a/mdf +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e588024d706220b73f2c97ca75d6fec8dd0d41b1 diff --git a/iocell/src/main/resources/barstools/iocell/vsrc/Analog.v b/tapeout/src/main/resources/barstools/iocell/vsrc/Analog.v similarity index 100% rename from iocell/src/main/resources/barstools/iocell/vsrc/Analog.v rename to tapeout/src/main/resources/barstools/iocell/vsrc/Analog.v diff --git a/iocell/src/main/resources/barstools/iocell/vsrc/IOCell.v b/tapeout/src/main/resources/barstools/iocell/vsrc/IOCell.v similarity index 100% rename from iocell/src/main/resources/barstools/iocell/vsrc/IOCell.v rename to tapeout/src/main/resources/barstools/iocell/vsrc/IOCell.v diff --git a/iocell/src/main/scala/barstools/iocell/chisel/Analog.scala b/tapeout/src/main/scala/barstools/iocell/chisel/Analog.scala similarity index 100% rename from iocell/src/main/scala/barstools/iocell/chisel/Analog.scala rename to tapeout/src/main/scala/barstools/iocell/chisel/Analog.scala diff --git a/iocell/src/main/scala/barstools/iocell/chisel/IOCell.scala b/tapeout/src/main/scala/barstools/iocell/chisel/IOCell.scala similarity index 99% rename from iocell/src/main/scala/barstools/iocell/chisel/IOCell.scala rename to tapeout/src/main/scala/barstools/iocell/chisel/IOCell.scala index d244d298..47b7cf2e 100644 --- a/iocell/src/main/scala/barstools/iocell/chisel/IOCell.scala +++ b/tapeout/src/main/scala/barstools/iocell/chisel/IOCell.scala @@ -148,7 +148,7 @@ object IOCell { padSignal: T, name: Option[String] = None, typeParams: IOCellTypeParams = GenericIOCellParams(), - concretizeResetFn: (Reset) => R = toSyncReset + concretizeResetFn: (Reset) => R = toSyncReset _ ): Seq[IOCell] = { def genCell[T <: Data]( castToBool: (T) => Bool, diff --git a/macros/src/main/scala/barstools/macros/CostMetric.scala b/tapeout/src/main/scala/barstools/macros/CostMetric.scala similarity index 100% rename from macros/src/main/scala/barstools/macros/CostMetric.scala rename to tapeout/src/main/scala/barstools/macros/CostMetric.scala diff --git a/macros/src/main/scala/barstools/macros/MacroCompiler.scala b/tapeout/src/main/scala/barstools/macros/MacroCompiler.scala similarity index 100% rename from macros/src/main/scala/barstools/macros/MacroCompiler.scala rename to tapeout/src/main/scala/barstools/macros/MacroCompiler.scala diff --git a/macros/src/main/scala/barstools/macros/SynFlops.scala b/tapeout/src/main/scala/barstools/macros/SynFlops.scala similarity index 98% rename from macros/src/main/scala/barstools/macros/SynFlops.scala rename to tapeout/src/main/scala/barstools/macros/SynFlops.scala index 77ea4c96..5d39cda9 100644 --- a/macros/src/main/scala/barstools/macros/SynFlops.scala +++ b/tapeout/src/main/scala/barstools/macros/SynFlops.scala @@ -64,7 +64,7 @@ class SynFlopsPass(synflops: Boolean, libs: Seq[Macro]) extends firrtl.passes.Pa val readConnects = real_macro.readers.zipWithIndex.flatMap { case (r, i) => val clock = portToExpression(r.src.clock.get) val address = portToExpression(r.src.address) - val enable = (r.src chipEnable, r.src readEnable) match { + val enable = (r.src.chipEnable, r.src.readEnable) match { case (Some(en_port), Some(re_port)) => and(portToExpression(en_port), portToExpression(re_port)) case (Some(en_port), None) => portToExpression(en_port) diff --git a/macros/src/main/scala/barstools/macros/Utils.scala b/tapeout/src/main/scala/barstools/macros/Utils.scala similarity index 100% rename from macros/src/main/scala/barstools/macros/Utils.scala rename to tapeout/src/main/scala/barstools/macros/Utils.scala diff --git a/tapeout/src/main/scala/mdf/macrolib/ConfReader.scala b/tapeout/src/main/scala/mdf/macrolib/ConfReader.scala new file mode 100644 index 00000000..ec701d6e --- /dev/null +++ b/tapeout/src/main/scala/mdf/macrolib/ConfReader.scala @@ -0,0 +1,95 @@ +package mdf.macrolib + +object ConfReader { + import scala.util.matching.Regex._ + + type ConfPort = (String, Boolean) // prefix (e.g. "RW0") and true if masked + + /** Rename ports like "read" to R0, "write" to W0, and "rw" to RW0, and + * return a count of read, write, and readwrite ports. + */ + def renamePorts(ports: Seq[String]): (Seq[ConfPort], Int, Int, Int) = { + var readCount = 0 + var writeCount = 0 + var readWriteCount = 0 + ( + ports.map { + _ match { + case "read" => readCount += 1; (s"R${readCount - 1}", false) + case "write" => writeCount += 1; (s"W${writeCount - 1}", false) + case "mwrite" => writeCount += 1; (s"W${writeCount - 1}", true) + case "rw" => readWriteCount += 1; (s"RW${readWriteCount - 1}", false) + case "mrw" => readWriteCount += 1; (s"RW${readWriteCount - 1}", true) + } + }, + readCount, + writeCount, + readWriteCount + ) + } + + def generateFirrtlPort(port: ConfPort, width: Int, depth: Int, maskGran: Option[Int]): MacroPort = { + val (prefix, masked) = port + val isReadWriter = prefix.startsWith("RW") + val isReader = prefix.startsWith("R") && !isReadWriter + val isWriter = prefix.startsWith("W") + val r = if (isReadWriter) "r" else "" + val w = if (isReadWriter) "w" else "" + MacroPort( + address = PolarizedPort(s"${prefix}_addr", ActiveHigh), + clock = Some(PolarizedPort(s"${prefix}_clk", PositiveEdge)), + writeEnable = if (isReadWriter) Some(PolarizedPort(s"${prefix}_${w}mode", ActiveHigh)) else None, + output = if (isReader || isReadWriter) Some(PolarizedPort(s"${prefix}_${w}data", ActiveHigh)) else None, + input = if (isWriter || isReadWriter) Some(PolarizedPort(s"${prefix}_${r}data", ActiveHigh)) else None, + maskPort = if (masked) Some(PolarizedPort(s"${prefix}_${w}mask", ActiveHigh)) else None, + maskGran = if (masked) maskGran else None, + width = Some(width), + depth = Some(depth) + ) + } + + /** Read a conf line into a SRAMMacro, but returns an error string in Left + * instead of throwing errors if the line is malformed. + */ + def readSingleLineSafe(line: String): Either[String, SRAMMacro] = { + val pattern = """name ([^\s]+) depth (\d+) width (\d+) ports ([a-z,]+)\s?(?:mask_gran (\d+))?""".r + pattern.findFirstMatchIn(line) match { + case Some(m: Match) => { + val name: String = m.group(1) + val depth: Int = (m.group(2)).toInt + val width: Int = (m.group(3)).toInt + val ports: Seq[String] = (m.group(4)).split(",") + val (firrtlPorts, readPortCount, writePortCount, readWritePortCount) = renamePorts(ports) + val familyStr = + (if (readPortCount > 0) s"${readPortCount}r" else "") + + (if (writePortCount > 0) s"${writePortCount}w" else "") + + (if (readWritePortCount > 0) s"${readWritePortCount}rw" else "") + val maskGran: Option[Int] = Option(m.group(5)).map(_.toInt) + Right( + SRAMMacro( + name = name, + width = width, + depth = depth, + family = familyStr, + vt = "", + mux = 1, + ports = firrtlPorts.map(generateFirrtlPort(_, width, depth, maskGran)), + extraPorts = List() + ) + ) + } + case _ => Left("Input line did not match conf regex") + } + } + + /** Read a conf line into a SRAMMacro. */ + def readSingleLine(line: String): SRAMMacro = { + readSingleLineSafe(line).right.get + } + + /** Read the contents of the conf file into a seq of SRAMMacro. */ + def readFromString(contents: String): Seq[SRAMMacro] = { + // Trim, remove empty lines, then pass to readSingleLine + contents.split("\n").map(_.trim).filter(_ != "").map(readSingleLine(_)) + } +} diff --git a/tapeout/src/main/scala/mdf/macrolib/FillerMacroBase.scala b/tapeout/src/main/scala/mdf/macrolib/FillerMacroBase.scala new file mode 100644 index 00000000..688871b5 --- /dev/null +++ b/tapeout/src/main/scala/mdf/macrolib/FillerMacroBase.scala @@ -0,0 +1,61 @@ +package mdf.macrolib + +import play.api.libs.json._ +import scala.language.implicitConversions + +// Filler and metal filler +abstract class FillerMacroBase(name: String, vt: String) extends Macro { + override def toString(): String = { + s"${this.getClass.getSimpleName}(name=${name}, vt=${vt})" + } + + override def toJSON(): JsObject = { + JsObject( + Seq( + "type" -> JsString(typeStr), + "name" -> Json.toJson(name), + "vt" -> Json.toJson(vt) + ) + ) + } +} +object FillerMacroBase { + def parseJSON(json: Map[String, JsValue]): Option[FillerMacroBase] = { + val typee: String = json.get("type") match { + case Some(x: JsString) => + x.value match { + case "" => return None + case x => x + } + case _ => return None + } + val name: String = json.get("name") match { + case Some(x: JsString) => + x.value match { + case "" => return None + case x => x + } + case _ => return None + } + val vt: String = json.get("vt") match { + case Some(x: JsString) => + x.value match { + case "" => return None + case x => x + } + case _ => return None + } + typee match { + case "metal filler cell" => Some(MetalFillerMacro(name, vt)) + case "filler cell" => Some(FillerMacro(name, vt)) + case _ => None + } + } +} + +case class FillerMacro(name: String, vt: String) extends FillerMacroBase(name, vt) { + override def typeStr = "filler cell" +} +case class MetalFillerMacro(name: String, vt: String) extends FillerMacroBase(name, vt) { + override def typeStr = "metal filler cell" +} diff --git a/tapeout/src/main/scala/mdf/macrolib/FlipChipMacro.scala b/tapeout/src/main/scala/mdf/macrolib/FlipChipMacro.scala new file mode 100644 index 00000000..45b49d86 --- /dev/null +++ b/tapeout/src/main/scala/mdf/macrolib/FlipChipMacro.scala @@ -0,0 +1,72 @@ +package mdf.macrolib + +import play.api.libs.json._ +import scala.collection.mutable.ListBuffer +import scala.language.implicitConversions + +// Flip Chip Macro +case class FlipChipMacro( + name: String, + bumpDimensions: (Int, Int), + bumpLocations: Seq[Seq[String]]) + extends Macro { + override def toJSON(): JsObject = { + + val output = new ListBuffer[(String, JsValue)]() + output.appendAll( + Seq( + "name" -> Json.toJson(name), + "type" -> Json.toJson(typeStr), + "bump_dimensions" -> JsArray(Seq(bumpDimensions._1, bumpDimensions._2).map { JsNumber(_) }), + "bump_locations" -> JsArray(bumpLocations.map(l => JsArray(l.map(JsString)))) + ) + ) + + JsObject(output) + } + val maxIONameSize = bumpLocations.foldLeft(0) { (size, row) => + row.foldLeft(size) { (size, str) => scala.math.max(size, str.length) } + } + def visualize: String = { + val output = new StringBuffer() + for (x <- 0 until bumpDimensions._1) { + for (y <- 0 until bumpDimensions._2) { + val name = bumpLocations(x)(y).drop(1).dropRight(1) + val extra = maxIONameSize - name.length() + val leftSpace = " " * (extra / 2) + val rightSpace = " " * (extra / 2 + extra % 2) + output.append(leftSpace + name + rightSpace + "|") + } + output.append("\n") + } + output.toString() + } + + override def typeStr = "flipchip" +} + +object FlipChipMacro { + def parseJSON(json: Map[String, JsValue]): Option[FlipChipMacro] = { + val name: String = json.get("name") match { + case Some(x: JsString) => x.as[String] + case _ => return None + } + + val bumpDimensions: (Int, Int) = json.get("bump_dimensions") match { + case Some(JsArray(x)) if x.size == 2 => + val z = x.map(_.as[JsNumber].value.intValue()) + (z(0), z(1)) + case None => return None + } + val bumpLocations: Seq[Seq[String]] = json.get("bump_locations") match { + case Some(JsArray(array)) => + array.collect { case JsArray(a2) => a2.map(_.toString) } + case _ => return None + } + // Can't have dimensions and locations which don't match + if (bumpLocations.size != bumpDimensions._1) return None + if (bumpLocations.collect { case x if x.size != bumpDimensions._2 => x }.nonEmpty) return None + + Some(FlipChipMacro(name, bumpDimensions, bumpLocations)) + } +} diff --git a/tapeout/src/main/scala/mdf/macrolib/IOMacro.scala b/tapeout/src/main/scala/mdf/macrolib/IOMacro.scala new file mode 100644 index 00000000..3f8ead8c --- /dev/null +++ b/tapeout/src/main/scala/mdf/macrolib/IOMacro.scala @@ -0,0 +1,147 @@ +package mdf.macrolib + +import play.api.libs.json._ +import scala.collection.mutable.ListBuffer +import scala.language.implicitConversions + +sealed abstract class PortType { def toJSON(): JsString = JsString(toString) } +case object Digital extends PortType { override def toString: String = "digital" } +case object Analog extends PortType { override def toString: String = "analog" } +case object Power extends PortType { override def toString: String = "power" } +case object Ground extends PortType { override def toString: String = "ground" } +case object NoConnect extends PortType { override def toString: String = "NC" } + +sealed abstract class Direction { def toJSON(): JsString = JsString(toString) } +case object Input extends Direction { override def toString: String = "input" } +case object Output extends Direction { override def toString: String = "output" } +case object InOut extends Direction { override def toString: String = "inout" } + +sealed abstract class Termination { def toJSON(): JsValue } +case object CMOS extends Termination { override def toJSON(): JsString = JsString("CMOS") } +case class Resistive(ohms: Int) extends Termination { override def toJSON(): JsNumber = JsNumber(ohms) } + +sealed abstract class TerminationType { def toJSON(): JsString } +case object Single extends TerminationType { override def toJSON(): JsString = JsString("single") } +case object Differential extends TerminationType { override def toJSON(): JsString = JsString("differential") } + +// IO macro +case class IOMacro( + name: String, + tpe: PortType, + direction: Option[Direction] = None, + termination: Option[Termination] = None, + terminationType: Option[TerminationType] = None, + terminationReference: Option[String] = None, + matching: Seq[String] = Seq.empty[String], + bbname: Option[String] = None) + extends Macro { + override def toJSON(): JsObject = { + + val output = new ListBuffer[(String, JsValue)]() + output.appendAll( + Seq( + "name" -> Json.toJson(name), + "type" -> tpe.toJSON() + ) + ) + if (direction.isDefined) output.append("direction" -> direction.get.toJSON) + if (termination.isDefined) output.append("termination" -> termination.get.toJSON) + if (terminationType.isDefined) output.append("terminationType" -> terminationType.get.toJSON) + if (terminationReference.isDefined) output.append("terminationReference" -> JsString(terminationReference.get)) + if (matching.nonEmpty) output.append("match" -> JsArray(matching.map(JsString))) + if (bbname.nonEmpty) output.append("blackBox" -> JsString(bbname.get)) + + JsObject(output) + } + + override def typeStr = "iomacro" +} +object IOMacro { + def parseJSON(json: Map[String, JsValue]): Option[IOMacro] = { + val name: String = json.get("name") match { + case Some(x: JsString) => x.as[String] + case _ => return None + } + val tpe: PortType = json.get("type") match { + case Some(JsString("power")) => Power + case Some(JsString("ground")) => Ground + case Some(JsString("digital")) => Digital + case Some(JsString("analog")) => Analog + case Some(JsString("NC")) => NoConnect + case _ => return None + } + val direction: Option[Direction] = json.get("direction") match { + case Some(JsString("input")) => Some(Input) + case Some(JsString("output")) => Some(Output) + case Some(JsString("inout")) => Some(InOut) + case _ => None + } + val termination: Option[Termination] = json.get("termination") match { + case Some(JsNumber(x)) => Some(Resistive(x.toInt)) + case Some(JsString("CMOS")) => Some(CMOS) + case _ => None + } + val terminationType: Option[TerminationType] = json.get("terminationType") match { + case Some(JsString("differential")) => Some(Differential) + case Some(JsString("single")) => Some(Single) + case _ => None + } + val terminationRef: Option[String] = json.get("terminationReference") match { + case Some(JsString(x)) => Some(x) + case _ if terminationType.isDefined => return None + case _ => None + } + val matching: Seq[String] = json.get("match") match { + case Some(JsArray(array)) => array.map(_.as[JsString].value).toList + case _ => Seq.empty[String] + } + val bbname: Option[String] = json.get("blackBox") match { + case Some(JsString(module)) => Some(module) + case Some(_) => return None + case _ => None + } + Some(IOMacro(name, tpe, direction, termination, terminationType, terminationRef, matching, bbname)) + } +} + +case class IOProperties(name: String, top: String, ios: Seq[IOMacro]) extends Macro { + override def toJSON(): JsObject = { + val output = new ListBuffer[(String, JsValue)]() + output.appendAll( + Seq( + "name" -> Json.toJson(name), + "top" -> Json.toJson(top), + "type" -> Json.toJson(typeStr), + "ios" -> JsArray(ios.map(_.toJSON)) + ) + ) + JsObject(output) + } + + override def typeStr = "io_properties" + +} + +object IOProperties { + def parseJSON(json: Map[String, JsValue]): Option[IOProperties] = { + val name: String = json.get("name") match { + case Some(x: JsString) => x.as[String] + case _ => return None + } + val top: String = json.get("top") match { + case Some(x: JsString) => x.as[String] + case _ => return None + } + val ios: Seq[IOMacro] = json.get("ios") match { + case Some(x: JsArray) => + x.as[List[Map[String, JsValue]]].map { a => + val b = IOMacro.parseJSON(a); + if (b == None) { + return None + } else b.get + } + case _ => List() + } + Some(IOProperties(name, top, ios)) + } +} diff --git a/tapeout/src/main/scala/mdf/macrolib/MacroLib.scala b/tapeout/src/main/scala/mdf/macrolib/MacroLib.scala new file mode 100644 index 00000000..569c4dac --- /dev/null +++ b/tapeout/src/main/scala/mdf/macrolib/MacroLib.scala @@ -0,0 +1,19 @@ +package mdf.macrolib + +import play.api.libs.json._ +import scala.collection.mutable.ListBuffer +import scala.language.implicitConversions + +// TODO: decide if we should always silently absorb errors + +// See macro_format.yml for the format description. + +// "Base class" for macros +abstract class Macro { + def name: String + + // Type of macro is determined by subclass + def typeStr: String + + def toJSON(): JsObject +} diff --git a/tapeout/src/main/scala/mdf/macrolib/SRAM.scala b/tapeout/src/main/scala/mdf/macrolib/SRAM.scala new file mode 100644 index 00000000..ea51b049 --- /dev/null +++ b/tapeout/src/main/scala/mdf/macrolib/SRAM.scala @@ -0,0 +1,444 @@ +package mdf.macrolib + +import play.api.libs.json._ +import scala.collection.mutable.ListBuffer +import scala.language.implicitConversions + +// SRAM macro +case class SRAMMacro( + name: String, + width: Int, + depth: BigInt, + family: String, + ports: Seq[MacroPort], + vt: String = "", + mux: Int = 1, + extraPorts: Seq[MacroExtraPort] = List()) + extends Macro { + override def toJSON(): JsObject = { + val output = new ListBuffer[(String, JsValue)]() + output.appendAll( + Seq( + "type" -> JsString("sram"), + "name" -> Json.toJson(name), + "width" -> Json.toJson(width), + "depth" -> Json.toJson(depth.toString), + "mux" -> Json.toJson(mux), + "mask" -> Json.toJson(ports.exists(p => p.maskPort.isDefined)), + "ports" -> JsArray(ports.map { _.toJSON }) + ) + ) + if (family != "") { + output.appendAll(Seq("family" -> Json.toJson(family))) + } + if (vt != "") { + output.appendAll(Seq("vt" -> Json.toJson(vt))) + } + if (extraPorts.length > 0) { + output.appendAll(Seq("extra ports" -> JsArray(extraPorts.map { _.toJSON }))) + } + + JsObject(output) + } + + override def typeStr = "sram" +} +object SRAMMacro { + def parseJSON(json: Map[String, JsValue]): Option[SRAMMacro] = { + val name: String = json.get("name") match { + case Some(x: JsString) => x.as[String] + case _ => return None + } + val width: Int = json.get("width") match { + case Some(x: JsNumber) => x.value.intValue + case _ => return None + } + val depth: BigInt = json.get("depth") match { + case Some(x: JsString) => + try { BigInt(x.as[String]) } + catch { case _: Throwable => return None } + case _ => return None + } + val family: String = json.get("family") match { + case Some(x: JsString) => x.as[String] + case _ => "" // optional + } + val vt: String = json.get("vt") match { + case Some(x: JsString) => x.as[String] + case _ => "" // optional + } + val mux: Int = json.get("mux") match { + case Some(x: JsNumber) => x.value.intValue + case _ => 1 // default + } + val ports: Seq[MacroPort] = json.get("ports") match { + case Some(x: JsArray) => + x.as[List[Map[String, JsValue]]].map { a => + val b = MacroPort.parseJSON(a, width, depth); + if (b == None) { + return None + } else b.get + } + case _ => List() + } + if (ports.length == 0) { + // Can't have portless memories. + return None + } + val extraPorts: Seq[MacroExtraPort] = json.get("extra ports") match { + case Some(x: JsArray) => + x.as[List[Map[String, JsValue]]].map { a => + val b = MacroExtraPort.parseJSON(a); + if (b == None) { + return None + } else b.get + } + case _ => List() + } + Some(SRAMMacro(name, width, depth, family, ports, vt, mux, extraPorts)) + } +} + +// SRAM compiler +case class SRAMGroup( + name: Seq[String], + family: String, + vt: Seq[String], + mux: Int, + depth: Range, + width: Range, + ports: Seq[MacroPort], + extraPorts: Seq[MacroExtraPort] = List()) { + def toJSON: JsObject = { + val output = new ListBuffer[(String, JsValue)]() + output.appendAll( + Seq( + "name" -> JsArray(name.map(Json.toJson(_))), + "vt" -> JsArray(vt.map(Json.toJson(_))), + "mux" -> Json.toJson(mux), + "depth" -> JsArray(Seq(depth.start, depth.end, depth.step).map { x => Json.toJson(x) }), + "width" -> JsArray(Seq(width.start, width.end, width.step).map { x => Json.toJson(x) }), + "ports" -> JsArray(ports.map { _.toJSON }) + ) + ) + if (family != "") { + output.appendAll(Seq("family" -> Json.toJson(family))) + } + if (extraPorts.length > 0) { + output.appendAll(Seq("extra ports" -> JsArray(extraPorts.map { _.toJSON }))) + } + JsObject(output) + } +} +object SRAMGroup { + def parseJSON(json: Map[String, JsValue]): Option[SRAMGroup] = { + val family: String = json.get("family") match { + case Some(x: JsString) => x.as[String] + case _ => "" // optional + } + val name: Seq[String] = json.get("name") match { + case Some(x: JsArray) => x.as[List[JsString]].map(_.as[String]) + case _ => return None + } + val vt: Seq[String] = json.get("vt") match { + case Some(x: JsArray) => x.as[List[JsString]].map(_.as[String]) + case _ => return None + } + val mux: Int = json.get("mux") match { + case Some(x: JsNumber) => x.value.intValue + case _ => return None + } + val depth: Range = json.get("depth") match { + case Some(x: JsArray) => + val seq = x.as[List[JsNumber]].map(_.value.intValue) + Range.inclusive(seq(0), seq(1), seq(2)) + case _ => return None + } + val width: Range = json.get("width") match { + case Some(x: JsArray) => + val seq = x.as[List[JsNumber]].map(_.value.intValue) + Range.inclusive(seq(0), seq(1), seq(2)) + case _ => return None + } + val ports: Seq[MacroPort] = json.get("ports") match { + case Some(x: JsArray) => + x.as[List[Map[String, JsValue]]].map { a => + { + val b = MacroPort.parseJSON(a, None, None); + if (b == None) { + return None + } else b.get + } + } + case _ => List() + } + if (ports.length == 0) { + // Can't have portless memories. + return None + } + val extraPorts: Seq[MacroExtraPort] = json.get("extra ports") match { + case Some(x: JsArray) => + x.as[List[Map[String, JsValue]]].map { a => + { + val b = MacroExtraPort.parseJSON(a); + if (b == None) { + return None + } else b.get + } + } + case _ => List() + } + Some(SRAMGroup(name, family, vt, mux, depth, width, ports, extraPorts)) + } +} + +case class SRAMCompiler( + name: String, + groups: Seq[SRAMGroup]) + extends Macro { + override def toJSON(): JsObject = { + val output = new ListBuffer[(String, JsValue)]() + output.appendAll( + Seq( + "type" -> Json.toJson("sramcompiler"), + "name" -> Json.toJson(name), + "groups" -> JsArray(groups.map { _.toJSON }) + ) + ) + + JsObject(output) + } + + override def typeStr = "sramcompiler" +} +object SRAMCompiler { + def parseJSON(json: Map[String, JsValue]): Option[SRAMCompiler] = { + val name: String = json.get("name") match { + case Some(x: JsString) => x.as[String] + case _ => return None + } + val groups: Seq[SRAMGroup] = json.get("groups") match { + case Some(x: JsArray) => + x.as[List[Map[String, JsValue]]].map { a => + { + val b = SRAMGroup.parseJSON(a); + if (b == None) { return None } + else b.get + } + } + case _ => List() + } + if (groups.length == 0) { + // Can't have portless memories. + return None + } + Some(SRAMCompiler(name, groups)) + } +} + +// Type of extra port +sealed abstract class MacroExtraPortType +case object Constant extends MacroExtraPortType +object MacroExtraPortType { + implicit def toMacroExtraPortType(s: Any): Option[MacroExtraPortType] = { + s match { + case "constant" => Some(Constant) + case _ => None + } + } + + implicit def toString(t: MacroExtraPortType): String = { + t match { + case Constant => "constant" + case _ => "" + } + } +} + +// Extra port in SRAM +case class MacroExtraPort( + name: String, + width: Int, + portType: MacroExtraPortType, + value: BigInt) { + def toJSON(): JsObject = { + JsObject( + Seq( + "name" -> Json.toJson(name), + "width" -> Json.toJson(width), + "type" -> JsString(MacroExtraPortType.toString(portType)), + "value" -> JsNumber(BigDecimal(value)) + ) + ) + } +} +object MacroExtraPort { + def parseJSON(json: Map[String, JsValue]): Option[MacroExtraPort] = { + val name = json.get("name") match { + case Some(x: JsString) => x.value + case _ => return None + } + val width = json.get("width") match { + case Some(x: JsNumber) => x.value.intValue + case _ => return None + } + val portType: MacroExtraPortType = json.get("type") match { + case Some(x: JsString) => + MacroExtraPortType.toMacroExtraPortType(x.value) match { + case Some(t: MacroExtraPortType) => t + case _ => return None + } + case _ => return None + } + val value = json.get("value") match { + case Some(x: JsNumber) => x.value.toBigInt + case _ => return None + } + Some(MacroExtraPort(name, width, portType, value)) + } +} + +// A named port that also has polarity. +case class PolarizedPort(name: String, polarity: PortPolarity) { + def toSeqMap(prefix: String): Seq[Tuple2[String, JsValue]] = { + Seq( + prefix + " port name" -> Json.toJson(name), + prefix + " port polarity" -> JsString(polarity) + ) + } +} +object PolarizedPort { + // Parse a pair of " port name" and " port polarity" keys into a + // polarized port definition. + def parseJSON(json: Map[String, JsValue], prefix: String): Option[PolarizedPort] = { + val name = json.get(prefix + " port name") match { + case Some(x: JsString) => Some(x.value) + case _ => None + } + val polarity: Option[PortPolarity] = json.get(prefix + " port polarity") match { + case Some(x: JsString) => Some(x.value) + case _ => None + } + + (name, polarity) match { + case (Some(n: String), Some(p: PortPolarity)) => Some(PolarizedPort(n, p)) + case _ => None + } + } +} + +// A SRAM memory port +case class MacroPort( + address: PolarizedPort, + clock: Option[PolarizedPort] = None, + writeEnable: Option[PolarizedPort] = None, + readEnable: Option[PolarizedPort] = None, + chipEnable: Option[PolarizedPort] = None, + output: Option[PolarizedPort] = None, + input: Option[PolarizedPort] = None, + maskPort: Option[PolarizedPort] = None, + maskGran: Option[Int] = None, + // For internal use only; these aren't port-specific. + width: Option[Int], + depth: Option[BigInt]) { + def effectiveMaskGran = maskGran.getOrElse(width.get) + + def toJSON(): JsObject = { + val keys: Seq[Tuple2[String, Option[Any]]] = Seq( + "address" -> Some(address), + "clock" -> clock, + "write enable" -> writeEnable, + "read enable" -> readEnable, + "chip enable" -> chipEnable, + "output" -> output, + "input" -> input, + "mask" -> maskPort, + "mask granularity" -> maskGran + ) + JsObject(keys.flatMap(k => { + val (key, value) = k + value match { + case Some(x: Int) => Seq(key -> JsNumber(x)) + case Some(x: PolarizedPort) => x.toSeqMap(key) + case _ => List() + } + })) + } + + // Check that all port names are unique. + private val polarizedPorts = + List(Some(address), clock, writeEnable, readEnable, chipEnable, output, input, maskPort).flatten + assert(polarizedPorts.distinct.size == polarizedPorts.size, "All port names must be unique") +} +object MacroPort { + def parseJSON(json: Map[String, JsValue]): Option[MacroPort] = parseJSON(json, None, None) + def parseJSON(json: Map[String, JsValue], width: Int, depth: BigInt): Option[MacroPort] = + parseJSON(json, Some(width), Some(depth)) + def parseJSON(json: Map[String, JsValue], width: Option[Int], depth: Option[BigInt]): Option[MacroPort] = { + val address = PolarizedPort.parseJSON(json, "address") + if (address == None) { + return None + } + + val clock = PolarizedPort.parseJSON(json, "clock") + // TODO: validate based on family (e.g. 1rw must have a write enable, etc) + val writeEnable = PolarizedPort.parseJSON(json, "write enable") + val readEnable = PolarizedPort.parseJSON(json, "read enable") + val chipEnable = PolarizedPort.parseJSON(json, "chip enable") + + val output = PolarizedPort.parseJSON(json, "output") + val input = PolarizedPort.parseJSON(json, "input") + + val maskPort = PolarizedPort.parseJSON(json, "mask") + val maskGran: Option[Int] = json.get("mask granularity") match { + case Some(x: JsNumber) => Some(x.value.intValue) + case _ => None + } + + if (maskPort.isDefined != maskGran.isDefined) { + return None + } + + Some( + MacroPort( + width = width, + depth = depth, + address = address.get, + clock = clock, + writeEnable = writeEnable, + readEnable = readEnable, + chipEnable = chipEnable, + output = output, + input = input, + maskPort = maskPort, + maskGran = maskGran + ) + ) + } +} + +// Port polarity +trait PortPolarity +case object ActiveLow extends PortPolarity +case object ActiveHigh extends PortPolarity +case object NegativeEdge extends PortPolarity +case object PositiveEdge extends PortPolarity +object PortPolarity { + implicit def toPortPolarity(s: String): PortPolarity = (s: @unchecked) match { + case "active low" => ActiveLow + case "active high" => ActiveHigh + case "negative edge" => NegativeEdge + case "positive edge" => PositiveEdge + } + implicit def toPortPolarity(s: Option[String]): Option[PortPolarity] = + s.map(toPortPolarity) + + implicit def toString(p: PortPolarity): String = { + p match { + case ActiveLow => "active low" + case ActiveHigh => "active high" + case NegativeEdge => "negative edge" + case PositiveEdge => "positive edge" + } + } +} diff --git a/tapeout/src/main/scala/mdf/macrolib/Utils.scala b/tapeout/src/main/scala/mdf/macrolib/Utils.scala new file mode 100644 index 00000000..795cff0a --- /dev/null +++ b/tapeout/src/main/scala/mdf/macrolib/Utils.scala @@ -0,0 +1,87 @@ +package mdf.macrolib + +import play.api.libs.json._ +import scala.collection.mutable.ListBuffer +import scala.language.implicitConversions + +object Utils { + // Read a MDF file from a String. + def readMDFFromString(str: String): Option[Seq[Macro]] = { + Json.parse(str) match { + // Make sure that the document is a list. + case arr: JsArray => { + val result: List[Option[Macro]] = arr.as[List[Map[String, JsValue]]].map { obj => + // Check the type of object. + val objTypeStr: String = obj.get("type") match { + case Some(x: JsString) => x.as[String] + case _ => return None // error, no type found + } + objTypeStr match { + case "filler cell" | "metal filler cell" => FillerMacroBase.parseJSON(obj) + case "sram" => SRAMMacro.parseJSON(obj) + case "sramcompiler" => SRAMCompiler.parseJSON(obj) + case "io_properties" => IOProperties.parseJSON(obj) + case "flipchip" => FlipChipMacro.parseJSON(obj) + case _ => None // skip unknown macro types + } + } + // Remove all the Nones and convert back to Seq[Macro] + Some(result.filter { x => x != None }.map { x => x.get }) + } + case _ => None + } + } + + // Read a MDF file from a path. + def readMDFFromPath(path: Option[String]): Option[Seq[Macro]] = { + path match { + case None => None + // Read file into string and parse + case Some(p) => Utils.readMDFFromString(scala.io.Source.fromFile(p).mkString) + } + } + + // Write a MDF file to a String. + def writeMDFToString(s: Seq[Macro]): String = { + Json.prettyPrint(JsArray(s.map(_.toJSON))) + } + + // Write a MDF file from a path. + // Returns true upon success. + def writeMDFToPath(path: Option[String], s: Seq[Macro]): Boolean = { + path match { + case None => false + // Read file into string and parse + case Some(p: String) => { + import java.io._ + val pw = new PrintWriter(new File(p)) + pw.write(writeMDFToString(s)) + val error = pw.checkError + pw.close() + !error + } + } + } + + // Write a macro file to a String. + def writeMacroToString(s: Macro): String = { + Json.prettyPrint(s.toJSON) + } + + // Write a Macro file from a path. + // Returns true upon success. + def writeMacroToPath(path: Option[String], s: Macro): Boolean = { + path match { + case None => false + // Read file into string and parse + case Some(p: String) => { + import java.io._ + val pw = new PrintWriter(new File(p)) + pw.write(writeMacroToString(s)) + val error = pw.checkError + pw.close() + !error + } + } + } +} diff --git a/macros/src/test/resources/lib-BOOMTest.json b/tapeout/src/test/resources/lib-BOOMTest.json similarity index 100% rename from macros/src/test/resources/lib-BOOMTest.json rename to tapeout/src/test/resources/lib-BOOMTest.json diff --git a/macros/src/test/resources/lib-MaskPortTest.json b/tapeout/src/test/resources/lib-MaskPortTest.json similarity index 100% rename from macros/src/test/resources/lib-MaskPortTest.json rename to tapeout/src/test/resources/lib-MaskPortTest.json diff --git a/macros/src/test/resources/lib-WriteEnableTest.json b/tapeout/src/test/resources/lib-WriteEnableTest.json similarity index 100% rename from macros/src/test/resources/lib-WriteEnableTest.json rename to tapeout/src/test/resources/lib-WriteEnableTest.json diff --git a/macros/src/test/scala/barstools/macros/CostFunction.scala b/tapeout/src/test/scala/barstools/macros/CostFunction.scala similarity index 100% rename from macros/src/test/scala/barstools/macros/CostFunction.scala rename to tapeout/src/test/scala/barstools/macros/CostFunction.scala diff --git a/macros/src/test/scala/barstools/macros/Functional.scala b/tapeout/src/test/scala/barstools/macros/Functional.scala similarity index 100% rename from macros/src/test/scala/barstools/macros/Functional.scala rename to tapeout/src/test/scala/barstools/macros/Functional.scala diff --git a/macros/src/test/scala/barstools/macros/MacroCompilerSpec.scala b/tapeout/src/test/scala/barstools/macros/MacroCompilerSpec.scala similarity index 100% rename from macros/src/test/scala/barstools/macros/MacroCompilerSpec.scala rename to tapeout/src/test/scala/barstools/macros/MacroCompilerSpec.scala diff --git a/macros/src/test/scala/barstools/macros/Masks.scala b/tapeout/src/test/scala/barstools/macros/Masks.scala similarity index 100% rename from macros/src/test/scala/barstools/macros/Masks.scala rename to tapeout/src/test/scala/barstools/macros/Masks.scala diff --git a/macros/src/test/scala/barstools/macros/MultiPort.scala b/tapeout/src/test/scala/barstools/macros/MultiPort.scala similarity index 100% rename from macros/src/test/scala/barstools/macros/MultiPort.scala rename to tapeout/src/test/scala/barstools/macros/MultiPort.scala diff --git a/macros/src/test/scala/barstools/macros/SRAMCompiler.scala b/tapeout/src/test/scala/barstools/macros/SRAMCompiler.scala similarity index 100% rename from macros/src/test/scala/barstools/macros/SRAMCompiler.scala rename to tapeout/src/test/scala/barstools/macros/SRAMCompiler.scala diff --git a/macros/src/test/scala/barstools/macros/SimpleSplitDepth.scala b/tapeout/src/test/scala/barstools/macros/SimpleSplitDepth.scala similarity index 100% rename from macros/src/test/scala/barstools/macros/SimpleSplitDepth.scala rename to tapeout/src/test/scala/barstools/macros/SimpleSplitDepth.scala diff --git a/macros/src/test/scala/barstools/macros/SimpleSplitWidth.scala b/tapeout/src/test/scala/barstools/macros/SimpleSplitWidth.scala similarity index 100% rename from macros/src/test/scala/barstools/macros/SimpleSplitWidth.scala rename to tapeout/src/test/scala/barstools/macros/SimpleSplitWidth.scala diff --git a/macros/src/test/scala/barstools/macros/SpecificExamples.scala b/tapeout/src/test/scala/barstools/macros/SpecificExamples.scala similarity index 99% rename from macros/src/test/scala/barstools/macros/SpecificExamples.scala rename to tapeout/src/test/scala/barstools/macros/SpecificExamples.scala index 334e3a73..6ee7255a 100644 --- a/macros/src/test/scala/barstools/macros/SpecificExamples.scala +++ b/tapeout/src/test/scala/barstools/macros/SpecificExamples.scala @@ -27,7 +27,7 @@ class WriteEnableTest extends MacroCompilerSpec with HasSRAMGenerator { val lib = s"lib-WriteEnableTest.json" // lib. of mems to create it val v = s"WriteEnableTest.json" - override val libPrefix = "macros/src/test/resources" + override val libPrefix = "tapeout/src/test/resources" val memSRAMs = mdf.macrolib.Utils .readMDFFromString(""" @@ -97,7 +97,7 @@ class MaskPortTest extends MacroCompilerSpec with HasSRAMGenerator { val lib = s"lib-MaskPortTest.json" // lib. of mems to create it val v = s"MaskPortTest.json" - override val libPrefix = "macros/src/test/resources" + override val libPrefix = "tapeout/src/test/resources" val memSRAMs = mdf.macrolib.Utils .readMDFFromString(""" @@ -181,7 +181,7 @@ class BOOMTest extends MacroCompilerSpec with HasSRAMGenerator { val lib = s"lib-BOOMTest.json" val v = s"BOOMTest.v" - override val libPrefix = "macros/src/test/resources" + override val libPrefix = "tapeout/src/test/resources" val memSRAMs = mdf.macrolib.Utils .readMDFFromString(""" diff --git a/macros/src/test/scala/barstools/macros/SynFlops.scala b/tapeout/src/test/scala/barstools/macros/SynFlops.scala similarity index 100% rename from macros/src/test/scala/barstools/macros/SynFlops.scala rename to tapeout/src/test/scala/barstools/macros/SynFlops.scala diff --git a/tapeout/src/test/scala/barstools/tapeout/transforms/GenerateSpec.scala b/tapeout/src/test/scala/barstools/tapeout/transforms/GenerateSpec.scala index cefd9759..ec4822fe 100644 --- a/tapeout/src/test/scala/barstools/tapeout/transforms/GenerateSpec.scala +++ b/tapeout/src/test/scala/barstools/tapeout/transforms/GenerateSpec.scala @@ -62,7 +62,7 @@ class GenerateSpec extends AnyFreeSpec { FileUtils.makeDirectory(targetDir) val printWriter = new PrintWriter(new File(s"$targetDir/GenerateExampleTester.fir")) - printWriter.write((new ChiselStage()).emitFirrtl(new GenerateExampleTester)) + printWriter.write((new ChiselStage()).emitFirrtl(new GenerateExampleTester, Array("--target-dir", targetDir))) printWriter.close() val blackBoxInverterText = """ diff --git a/tapeout/src/test/scala/barstools/tapeout/transforms/ResetInverterSpec.scala b/tapeout/src/test/scala/barstools/tapeout/transforms/ResetInverterSpec.scala index 701c7845..d18053f0 100644 --- a/tapeout/src/test/scala/barstools/tapeout/transforms/ResetInverterSpec.scala +++ b/tapeout/src/test/scala/barstools/tapeout/transforms/ResetInverterSpec.scala @@ -22,7 +22,7 @@ class ExampleModuleNeedsResetInverted extends Module with ResetInverter { class ResetNSpec extends AnyFreeSpec with Matchers { "Inverting reset needs to be done throughout module in Chirrtl" in { - val chirrtl = (new ChiselStage).emitChirrtl(new ExampleModuleNeedsResetInverted) + val chirrtl = (new ChiselStage).emitChirrtl(new ExampleModuleNeedsResetInverted, Array("--target-dir", "test_run_dir/reset_n_spec")) chirrtl should include("input reset :") (chirrtl should not).include("input reset_n :") (chirrtl should not).include("node reset = not(reset_n)") @@ -32,7 +32,7 @@ class ResetNSpec extends AnyFreeSpec with Matchers { // generate low-firrtl val firrtl = (new ChiselStage) .execute( - Array("-X", "low"), + Array("-X", "low", "--target-dir", "test_run_dir/reset_inverting_spec"), Seq(ChiselGeneratorAnnotation(() => new ExampleModuleNeedsResetInverted)) ) .collect { diff --git a/tapeout/src/test/scala/mdf/macrolib/ConfReaderSpec.scala b/tapeout/src/test/scala/mdf/macrolib/ConfReaderSpec.scala new file mode 100644 index 00000000..58680cd7 --- /dev/null +++ b/tapeout/src/test/scala/mdf/macrolib/ConfReaderSpec.scala @@ -0,0 +1,101 @@ +package mdf.macrolib + +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers + +class ConfReaderSpec extends AnyFlatSpec with Matchers { + + /** Generate a read port in accordance with RenameAnnotatedMemoryPorts. */ + def generateReadPort(num: Int, width: Int, depth: Int): MacroPort = { + MacroPort( + address = PolarizedPort(s"R${num}_addr", ActiveHigh), + clock = Some(PolarizedPort(s"R${num}_clk", PositiveEdge)), + output = Some(PolarizedPort(s"R${num}_data", ActiveHigh)), + width = Some(width), + depth = Some(depth) + ) + } + + /** Generate a write port in accordance with RenameAnnotatedMemoryPorts. */ + def generateWritePort(num: Int, width: Int, depth: Int, maskGran: Option[Int] = None): MacroPort = { + MacroPort( + address = PolarizedPort(s"W${num}_addr", ActiveHigh), + clock = Some(PolarizedPort(s"W${num}_clk", PositiveEdge)), + input = Some(PolarizedPort(s"W${num}_data", ActiveHigh)), + maskPort = if (maskGran.isDefined) Some(PolarizedPort(s"W${num}_mask", ActiveHigh)) else None, + maskGran = maskGran, + width = Some(184), + depth = Some(128) + ) + } + + "ConfReader" should "read a 1rw conf line" in { + val confStr = "name Foo_Bar_mem123_ext depth 128 width 184 ports mrw mask_gran 23" + ConfReader.readSingleLine(confStr) shouldBe SRAMMacro( + name = "Foo_Bar_mem123_ext", + width = 184, + depth = 128, + family = "1rw", + ports = List( + MacroPort( + address = PolarizedPort("RW0_addr", ActiveHigh), + clock = Some(PolarizedPort("RW0_clk", PositiveEdge)), + writeEnable = Some(PolarizedPort("RW0_wmode", ActiveHigh)), + output = Some(PolarizedPort("RW0_wdata", ActiveHigh)), + input = Some(PolarizedPort("RW0_rdata", ActiveHigh)), + maskPort = Some(PolarizedPort("RW0_wmask", ActiveHigh)), + maskGran = Some(23), + width = Some(184), + depth = Some(128) + ) + ), + extraPorts = List() + ) + } + + "ConfReader" should "read a 1r1w conf line" in { + val confStr = "name Foo_Bar_mem321_ext depth 128 width 184 ports read,mwrite mask_gran 23" + ConfReader.readSingleLine(confStr) shouldBe SRAMMacro( + name = "Foo_Bar_mem321_ext", + width = 184, + depth = 128, + family = "1r1w", + ports = List( + generateReadPort(0, 184, 128), + generateWritePort(0, 184, 128, Some(23)) + ), + extraPorts = List() + ) + } + + "ConfReader" should "read a mixed 1r2w conf line" in { + val confStr = "name Foo_Bar_mem321_ext depth 128 width 184 ports read,mwrite,write mask_gran 23" + ConfReader.readSingleLine(confStr) shouldBe SRAMMacro( + name = "Foo_Bar_mem321_ext", + width = 184, + depth = 128, + family = "1r2w", + ports = List( + generateReadPort(0, 184, 128), + generateWritePort(0, 184, 128, Some(23)), + generateWritePort(1, 184, 128) + ), + extraPorts = List() + ) + } + + "ConfReader" should "read a 42r29w conf line" in { + val confStr = + "name Foo_Bar_mem321_ext depth 128 width 184 ports " + (Seq.fill(42)("read") ++ Seq.fill(29)("mwrite")) + .mkString(",") + " mask_gran 23" + ConfReader.readSingleLine(confStr) shouldBe SRAMMacro( + name = "Foo_Bar_mem321_ext", + width = 184, + depth = 128, + family = "42r29w", + ports = ((0 to 41).map((num: Int) => generateReadPort(num, 184, 128))) ++ + ((0 to 28).map((num: Int) => generateWritePort(num, 184, 128, Some(23)))), + extraPorts = List() + ) + } +} diff --git a/tapeout/src/test/scala/mdf/macrolib/FlipChipMacroSpec.scala b/tapeout/src/test/scala/mdf/macrolib/FlipChipMacroSpec.scala new file mode 100644 index 00000000..ba51e4d1 --- /dev/null +++ b/tapeout/src/test/scala/mdf/macrolib/FlipChipMacroSpec.scala @@ -0,0 +1,14 @@ +package mdf.macrolib + +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers + +class FlipChipMacroSpec extends AnyFlatSpec with Matchers { + "Parsing flipchipmacros" should "work" in { + val stream = getClass.getResourceAsStream("/bumps.json") + val mdf = Utils.readMDFFromString(scala.io.Source.fromInputStream(stream).getLines().mkString("\n")) + mdf match { + case Some(Seq(fcp: FlipChipMacro)) => println(fcp.visualize) + } + } +} diff --git a/tapeout/src/test/scala/mdf/macrolib/IOMacroSpec.scala b/tapeout/src/test/scala/mdf/macrolib/IOMacroSpec.scala new file mode 100644 index 00000000..c6ab6e10 --- /dev/null +++ b/tapeout/src/test/scala/mdf/macrolib/IOMacroSpec.scala @@ -0,0 +1,67 @@ +package mdf.macrolib + +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers + +class IOMacroSpec extends AnyFlatSpec with Matchers { + "Ground IOs" should "be detected" in { + val json = + """{ + | "name" : "GND", + | "type" : "ground" + |}""".stripMargin + val m = JSONUtils.readStringValueMap(json).get + IOMacro.parseJSON(m) shouldBe Some(IOMacro("GND", Ground)) + } + "Power IOs" should "be detected" in { + val json = + """{ + | "name" : "VDD0V8", + | "type" : "power" + |}""".stripMargin + val m = JSONUtils.readStringValueMap(json).get + IOMacro.parseJSON(m) shouldBe Some(IOMacro("VDD0V8", Power)) + } + "Digital IOs" should "be detected" in { + val json = + """{ + | "name" : "VDDC0_SEL[1:0]", + | "type" : "digital", + | "direction" : "output", + | "termination" : "CMOS" + |}""".stripMargin + val m = JSONUtils.readStringValueMap(json).get + IOMacro.parseJSON(m) shouldBe Some(IOMacro("VDDC0_SEL[1:0]", Digital, Some(Output), Some(CMOS))) + } + "Digital IOs with termination" should "be detected" in { + val json = + """{ + | "name" : "CCLK1", + | "type" : "digital", + | "direction" : "input", + | "termination" : 50, + | "terminationType" : "single", + | "terminationReference" : "GND" + |}""".stripMargin + val m = JSONUtils.readStringValueMap(json).get + IOMacro.parseJSON(m) shouldBe Some( + IOMacro("CCLK1", Digital, Some(Input), Some(Resistive(50)), Some(Single), Some("GND")) + ) + } + "Digital IOs with matching and termination" should "be detected" in { + val json = + """{ + | "name" : "REFCLK0P", + | "type" : "analog", + | "direction" : "input", + | "match" : ["REFCLK0N"], + | "termination" : 100, + | "terminationType" : "differential", + | "terminationReference" : "GND" + |}""".stripMargin + val m = JSONUtils.readStringValueMap(json).get + IOMacro.parseJSON(m) shouldBe Some( + IOMacro("REFCLK0P", Analog, Some(Input), Some(Resistive(100)), Some(Differential), Some("GND"), List("REFCLK0N")) + ) + } +} diff --git a/tapeout/src/test/scala/mdf/macrolib/IOPropertiesSpec.scala b/tapeout/src/test/scala/mdf/macrolib/IOPropertiesSpec.scala new file mode 100644 index 00000000..ffd13be5 --- /dev/null +++ b/tapeout/src/test/scala/mdf/macrolib/IOPropertiesSpec.scala @@ -0,0 +1,14 @@ +package mdf.macrolib + +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers + +class IOPropertiesSpec extends AnyFlatSpec with Matchers { + "Parsing io_properties" should "work" in { + val stream = getClass.getResourceAsStream("/io_properties.json") + val mdf = Utils.readMDFFromString(scala.io.Source.fromInputStream(stream).getLines().mkString("\n")) + mdf match { + case Some(Seq(fcp: IOProperties)) => + } + } +} diff --git a/tapeout/src/test/scala/mdf/macrolib/MacroLibOutput.scala b/tapeout/src/test/scala/mdf/macrolib/MacroLibOutput.scala new file mode 100644 index 00000000..85feaffa --- /dev/null +++ b/tapeout/src/test/scala/mdf/macrolib/MacroLibOutput.scala @@ -0,0 +1,270 @@ +package mdf.macrolib + +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers +import play.api.libs.json._ + +import java.io.File + +// Output tests (Scala -> JSON). +// TODO: unify these tests with the input tests? + +trait HasAwesomeMemData { + def getAwesomeMem() = { + SRAMMacro( + name = "awesome_mem", + width = 32, + depth = 1024, + family = "1rw", + ports = Seq( + MacroPort( + address = PolarizedPort(name = "addr", polarity = ActiveHigh), + clock = Some(PolarizedPort(name = "clk", polarity = PositiveEdge)), + writeEnable = Some(PolarizedPort(name = "write_enable", polarity = ActiveHigh)), + readEnable = Some(PolarizedPort(name = "read_enable", polarity = ActiveHigh)), + chipEnable = Some(PolarizedPort(name = "chip_enable", polarity = ActiveHigh)), + output = Some(PolarizedPort(name = "data_out", polarity = ActiveHigh)), + input = Some(PolarizedPort(name = "data_in", polarity = ActiveHigh)), + maskPort = Some(PolarizedPort(name = "mask", polarity = ActiveHigh)), + maskGran = Some(8), + width = Some(32), + depth = Some(1024) // These numbers don't matter. + ) + ), + extraPorts = List() + ) + } + + def getAwesomeMemJSON(): String = { + """ + | { + | "type": "sram", + | "name": "awesome_mem", + | "width": 32, + | "depth": "1024", + | "mux": 1, + | "mask":true, + | "family": "1rw", + | "ports": [ + | { + | "address port name": "addr", + | "address port polarity": "active high", + | "clock port name": "clk", + | "clock port polarity": "positive edge", + | "write enable port name": "write_enable", + | "write enable port polarity": "active high", + | "read enable port name": "read_enable", + | "read enable port polarity": "active high", + | "chip enable port name": "chip_enable", + | "chip enable port polarity": "active high", + | "output port name": "data_out", + | "output port polarity": "active high", + | "input port name": "data_in", + | "input port polarity": "active high", + | "mask port name": "mask", + | "mask port polarity": "active high", + | "mask granularity": 8 + | } + | ] + | } + |""".stripMargin + } +} + +// Tests for filler macros. +class FillerMacroOutput extends AnyFlatSpec with Matchers { + "Valid lvt macro" should "be generated" in { + val expected = """ + | { + | "type": "filler cell", + | "name": "MY_FILLER_CELL", + | "vt": "lvt" + | } + |""".stripMargin + FillerMacro("MY_FILLER_CELL", "lvt").toJSON shouldBe Json.parse(expected) + } + + "Valid metal macro" should "be generated" in { + val expected = """ + | { + | "type": "metal filler cell", + | "name": "METAL_FILLER_CELL", + | "vt": "lvt" + | } + |""".stripMargin + MetalFillerMacro("METAL_FILLER_CELL", "lvt").toJSON shouldBe Json.parse(expected) + } + + "Valid hvt macro" should "be generated" in { + val expected = """ + | { + | "type": "filler cell", + | "name": "HVT_CELL_PROP", + | "vt": "hvt" + | } + |""".stripMargin + FillerMacro("HVT_CELL_PROP", "hvt").toJSON shouldBe Json.parse(expected) + } +} + +class SRAMPortOutput extends AnyFlatSpec with Matchers { + "Extra port" should "be generated" in { + val m = MacroExtraPort( + name = "TIE_HIGH", + width = 8, + portType = Constant, + value = ((1 << 8) - 1) + ) + val expected = """ + | { + | "type": "constant", + | "name": "TIE_HIGH", + | "width": 8, + | "value": 255 + | } + |""".stripMargin + m.toJSON shouldBe Json.parse(expected) + } + + "Minimal write port" should "be generated" in { + val m = MacroPort( + address = PolarizedPort(name = "addr", polarity = ActiveHigh), + clock = Some(PolarizedPort(name = "clk", polarity = PositiveEdge)), + writeEnable = Some(PolarizedPort(name = "write_enable", polarity = ActiveHigh)), + input = Some(PolarizedPort(name = "data_in", polarity = ActiveHigh)), + width = Some(32), + depth = Some(1024) // These numbers don't matter. + ) + val expected = """ + | { + | "address port name": "addr", + | "address port polarity": "active high", + | "clock port name": "clk", + | "clock port polarity": "positive edge", + | "write enable port name": "write_enable", + | "write enable port polarity": "active high", + | "input port name": "data_in", + | "input port polarity": "active high" + | } + |""".stripMargin + m.toJSON shouldBe Json.parse(expected) + } + + "Minimal read port" should "be generated" in { + val m = MacroPort( + address = PolarizedPort(name = "addr", polarity = ActiveHigh), + clock = Some(PolarizedPort(name = "clk", polarity = PositiveEdge)), + output = Some(PolarizedPort(name = "data_out", polarity = ActiveHigh)), + width = Some(32), + depth = Some(1024) // These numbers don't matter. + ) + val expected = """ + | { + | "address port name": "addr", + | "address port polarity": "active high", + | "clock port name": "clk", + | "clock port polarity": "positive edge", + | "output port name": "data_out", + | "output port polarity": "active high" + | } + |""".stripMargin + m.toJSON shouldBe Json.parse(expected) + } + + "Masked read port" should "be generated" in { + val m = MacroPort( + address = PolarizedPort(name = "addr", polarity = ActiveHigh), + clock = Some(PolarizedPort(name = "clk", polarity = PositiveEdge)), + output = Some(PolarizedPort(name = "data_out", polarity = ActiveHigh)), + maskPort = Some(PolarizedPort(name = "mask", polarity = ActiveHigh)), + maskGran = Some(8), + width = Some(32), + depth = Some(1024) // These numbers don't matter. + ) + val expected = """ + | { + | "address port name": "addr", + | "address port polarity": "active high", + | "clock port name": "clk", + | "clock port polarity": "positive edge", + | "output port name": "data_out", + | "output port polarity": "active high", + | "mask port name": "mask", + | "mask port polarity": "active high", + | "mask granularity": 8 + | } + |""".stripMargin + m.toJSON shouldBe Json.parse(expected) + } + + "Everything port" should "be generated" in { + val m = MacroPort( + address = PolarizedPort(name = "addr", polarity = ActiveHigh), + clock = Some(PolarizedPort(name = "clk", polarity = PositiveEdge)), + writeEnable = Some(PolarizedPort(name = "write_enable", polarity = ActiveHigh)), + readEnable = Some(PolarizedPort(name = "read_enable", polarity = ActiveHigh)), + chipEnable = Some(PolarizedPort(name = "chip_enable", polarity = ActiveHigh)), + output = Some(PolarizedPort(name = "data_out", polarity = ActiveHigh)), + input = Some(PolarizedPort(name = "data_in", polarity = ActiveHigh)), + maskPort = Some(PolarizedPort(name = "mask", polarity = ActiveHigh)), + maskGran = Some(8), + width = Some(32), + depth = Some(1024) // These numbers don't matter. + ) + val expected = """ + | { + | "address port name": "addr", + | "address port polarity": "active high", + | "clock port name": "clk", + | "clock port polarity": "positive edge", + | "write enable port name": "write_enable", + | "write enable port polarity": "active high", + | "read enable port name": "read_enable", + | "read enable port polarity": "active high", + | "chip enable port name": "chip_enable", + | "chip enable port polarity": "active high", + | "output port name": "data_out", + | "output port polarity": "active high", + | "input port name": "data_in", + | "input port polarity": "active high", + | "mask port name": "mask", + | "mask port polarity": "active high", + | "mask granularity": 8 + | } + |""".stripMargin + m.toJSON shouldBe Json.parse(expected) + } +} + +class SRAMMacroOutput extends AnyFlatSpec with Matchers with HasAwesomeMemData { + "SRAM macro" should "be generated" in { + val m = getAwesomeMem + val expected = getAwesomeMemJSON + m.toJSON shouldBe Json.parse(expected) + } +} + +class InputOutput extends AnyFlatSpec with Matchers with HasAwesomeMemData { + "Read-write string" should "preserve data" in { + val mdf = List( + FillerMacro("MY_FILLER_CELL", "lvt"), + MetalFillerMacro("METAL_GEAR_FILLER", "hvt"), + getAwesomeMem + ) + Utils.readMDFFromString(Utils.writeMDFToString(mdf)) shouldBe Some(mdf) + } + + val testDir: String = "test_run_dir" + new File(testDir).mkdirs // Make sure the testDir exists + + "Read-write file" should "preserve data" in { + val mdf = List( + FillerMacro("MY_FILLER_CELL", "lvt"), + MetalFillerMacro("METAL_GEAR_FILLER", "hvt"), + getAwesomeMem + ) + val filename = testDir + "/" + "mdf_read_write_test.json" + Utils.writeMDFToPath(Some(filename), mdf) shouldBe true + Utils.readMDFFromPath(Some(filename)) shouldBe Some(mdf) + } +} diff --git a/tapeout/src/test/scala/mdf/macrolib/MacroLibSpec.scala b/tapeout/src/test/scala/mdf/macrolib/MacroLibSpec.scala new file mode 100644 index 00000000..fd3210bb --- /dev/null +++ b/tapeout/src/test/scala/mdf/macrolib/MacroLibSpec.scala @@ -0,0 +1,406 @@ +package mdf.macrolib + +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers +import play.api.libs.json._ + +object JSONUtils { + def readStringValueMap(str: String): Option[Map[String, JsValue]] = { + Json.parse(str) match { + case x: JsObject => Some(x.as[Map[String, JsValue]]) + case _ => None + } + } +} + +// Tests for filler macros +class FillerMacroSpec extends AnyFlatSpec with Matchers { + "Valid lvt macros" should "be detected" in { + val m = JSONUtils + .readStringValueMap(""" + | { + | "type": "filler cell", + | "name": "MY_FILLER_CELL", + | "vt": "lvt" + | } + |""".stripMargin) + .get + FillerMacroBase.parseJSON(m) shouldBe Some(FillerMacro("MY_FILLER_CELL", "lvt")) + } + + "Valid metal macro" should "be detected" in { + val m = JSONUtils + .readStringValueMap(""" + | { + | "type": "metal filler cell", + | "name": "METAL_FILLER_CELL", + | "vt": "lvt" + | } + |""".stripMargin) + .get + FillerMacroBase.parseJSON(m) shouldBe Some(MetalFillerMacro("METAL_FILLER_CELL", "lvt")) + } + + "Valid hvt macros" should "be detected" in { + val m = JSONUtils + .readStringValueMap(""" + | { + | "type": "filler cell", + | "name": "HVT_CELL_PROP", + | "vt": "hvt" + | } + |""".stripMargin) + .get + FillerMacroBase.parseJSON(m) shouldBe Some(FillerMacro("HVT_CELL_PROP", "hvt")) + } + + "Empty name macros" should "be rejected" in { + val m = JSONUtils + .readStringValueMap(""" + | { + | "type": "filler cell", + | "name": "", + | "vt": "hvt" + | } + |""".stripMargin) + .get + FillerMacroBase.parseJSON(m) shouldBe None + } + + "Empty vt macros" should "be rejected" in { + val m = JSONUtils + .readStringValueMap(""" + | { + | "type": "metal filler cell", + | "name": "DEAD_CELL", + | "vt": "" + | } + |""".stripMargin) + .get + FillerMacroBase.parseJSON(m) shouldBe None + } + + "Missing vt macros" should "be rejected" in { + val m = JSONUtils + .readStringValueMap(""" + | { + | "type": "metal filler cell", + | "name": "DEAD_CELL" + | } + |""".stripMargin) + .get + FillerMacroBase.parseJSON(m) shouldBe None + } + + "Missing name macros" should "be rejected" in { + val m = JSONUtils + .readStringValueMap(""" + | { + | "type": "filler cell", + | "vt": "" + | } + |""".stripMargin) + .get + FillerMacroBase.parseJSON(m) shouldBe None + } +} + +// Tests for SRAM type and associates. +class SRAMMacroSpec extends AnyFlatSpec with Matchers { + // Simple port which can be reused in tests + // Note: assume width=depth=simplePortConstant. + val simplePortConstant = 1024 + def simplePort( + postfix: String = "", + width: Int = simplePortConstant, + depth: Int = simplePortConstant + ): (String, MacroPort) = { + val json = s""" + { + "address port name": "A_${postfix}", + "address port polarity": "active high", + "clock port name": "CLK_${postfix}", + "clock port polarity": "positive edge", + "write enable port name": "WEN_${postfix}", + "write enable port polarity": "active high", + "read enable port name": "REN_${postfix}", + "read enable port polarity": "active high", + "chip enable port name": "CEN_${postfix}", + "chip enable port polarity": "active high", + "output port name": "OUT_${postfix}", + "output port polarity": "active high", + "input port name": "IN_${postfix}", + "input port polarity": "active high", + "mask granularity": 1, + "mask port name": "MASK_${postfix}", + "mask port polarity": "active high" + } + """ + val port = MacroPort( + address = PolarizedPort(s"A_${postfix}", ActiveHigh), + clock = Some(PolarizedPort(s"CLK_${postfix}", PositiveEdge)), + writeEnable = Some(PolarizedPort(s"WEN_${postfix}", ActiveHigh)), + readEnable = Some(PolarizedPort(s"REN_${postfix}", ActiveHigh)), + chipEnable = Some(PolarizedPort(s"CEN_${postfix}", ActiveHigh)), + output = Some(PolarizedPort(s"OUT_${postfix}", ActiveHigh)), + input = Some(PolarizedPort(s"IN_${postfix}", ActiveHigh)), + maskPort = Some(PolarizedPort(s"MASK_${postfix}", ActiveHigh)), + maskGran = Some(1), + width = Some(width), + depth = Some(depth) + ) + (json, port) + } + "Simple port" should "be valid" in { + { + val (json, port) = simplePort("Simple1") + MacroPort.parseJSON(JSONUtils.readStringValueMap(json).get, simplePortConstant, simplePortConstant) shouldBe Some( + port + ) + } + { + val (json, port) = simplePort("Simple2") + MacroPort.parseJSON(JSONUtils.readStringValueMap(json).get, simplePortConstant, simplePortConstant) shouldBe Some( + port + ) + } + { + val (json, port) = simplePort("bar") + MacroPort.parseJSON(JSONUtils.readStringValueMap(json).get, simplePortConstant, simplePortConstant) shouldBe Some( + port + ) + } + { + val (json, port) = simplePort("") + MacroPort.parseJSON(JSONUtils.readStringValueMap(json).get, simplePortConstant, simplePortConstant) shouldBe Some( + port + ) + } + } + + "Simple SRAM macro" should "be detected" in { + val (json, port) = simplePort("", 2048, 4096) + val m = JSONUtils + .readStringValueMap(s""" +{ + "type": "sram", + "name": "SRAMS_R_US", + "width": 2048, + "depth": "4096", + "family": "1rw", + "ports": [ + ${json} + ] +} + """) + .get + SRAMMacro.parseJSON(m) shouldBe Some( + SRAMMacro("SRAMS_R_US", width = 2048, depth = 4096, family = "1rw", ports = List(port), extraPorts = List()) + ) + } + + "Non-power-of-two width & depth SRAM macro" should "be detected" in { + val (json, port) = simplePort("", 1234, 8888) + val m = JSONUtils + .readStringValueMap(s""" +{ + "type": "sram", + "name": "SRAMS_R_US", + "width": 1234, + "depth": "8888", + "family": "1rw", + "ports": [ + ${json} + ] +} + """) + .get + SRAMMacro.parseJSON(m) shouldBe Some( + SRAMMacro("SRAMS_R_US", width = 1234, depth = 8888, family = "1rw", ports = List(port), extraPorts = List()) + ) + } + + "Minimal memory port" should "be detected" in { + val (json, port) = simplePort("_A", 64, 1024) + val port2 = MacroPort( + address = PolarizedPort("A_B", ActiveHigh), + clock = Some(PolarizedPort("CLK_B", PositiveEdge)), + writeEnable = Some(PolarizedPort("WEN_B", ActiveHigh)), + readEnable = None, + chipEnable = None, + output = Some(PolarizedPort("OUT_B", ActiveHigh)), + input = Some(PolarizedPort("IN_B", ActiveHigh)), + maskPort = None, + maskGran = None, + width = Some(64), + depth = Some(1024) + ) + val m = JSONUtils + .readStringValueMap(s""" +{ + "type": "sram", + "name": "SRAMS_R_US", + "width": 64, + "depth": "1024", + "family": "2rw", + "ports": [ + ${json}, + { + "address port name": "A_B", + "address port polarity": "active high", + "clock port name": "CLK_B", + "clock port polarity": "positive edge", + "write enable port name": "WEN_B", + "write enable port polarity": "active high", + "output port name": "OUT_B", + "output port polarity": "active high", + "input port name": "IN_B", + "input port polarity": "active high" + } + ] +} + """) + .get + SRAMMacro.parseJSON(m) shouldBe Some( + SRAMMacro("SRAMS_R_US", width = 64, depth = 1024, family = "2rw", ports = List(port, port2), extraPorts = List()) + ) + } + + "Extra ports" should "be detected" in { + val (json, port) = simplePort("", 2048, 4096) + val m = JSONUtils + .readStringValueMap(s""" +{ + "type": "sram", + "name": "GOT_EXTRA", + "width": 2048, + "depth": "4096", + "family": "1rw", + "ports": [ + ${json} + ], + "extra ports": [ + { + "name": "TIE_DIE", + "width": 1, + "type": "constant", + "value": 1 + }, + { + "name": "TIE_MOO", + "width": 4, + "type": "constant", + "value": 0 + } + ] +} + """) + .get + SRAMMacro.parseJSON(m) shouldBe Some( + SRAMMacro( + "GOT_EXTRA", + width = 2048, + depth = 4096, + family = "1rw", + ports = List(port), + extraPorts = List( + MacroExtraPort( + name = "TIE_DIE", + width = 1, + portType = Constant, + value = 1 + ), + MacroExtraPort( + name = "TIE_MOO", + width = 4, + portType = Constant, + value = 0 + ) + ) + ) + ) + } + + "Invalid port" should "be rejected" in { + val (json, port) = simplePort("", 2048, 4096) + val m = JSONUtils + .readStringValueMap(s""" +{ + "type": "sram", + "name": "SRAMS_R_US", + "width": 2048, + "depth": "4096", + "family": "1rw", + "ports": [ + { + "address port name": "missing_polarity", + "output port name": "missing_clock" + } + ] +} + """) + .get + SRAMMacro.parseJSON(m) shouldBe None + } + + "No ports" should "be rejected" in { + val (json, port) = simplePort("", 2048, 4096) + val m = JSONUtils + .readStringValueMap(s""" +{ + "type": "sram", + "name": "SRAMS_R_US", + "width": 2048, + "depth": "4096", + "family": "1rw" +} + """) + .get + SRAMMacro.parseJSON(m) shouldBe None + } + + "No family and ports" should "be rejected" in { + val (json, port) = simplePort("", 2048, 4096) + val m = JSONUtils + .readStringValueMap(s""" +{ + "type": "sram", + "name": "SRAMS_R_US", + "width": 2048, + "depth": "4096" +} + """) + .get + SRAMMacro.parseJSON(m) shouldBe None + } + + "String width" should "be rejected" in { + val (json, port) = simplePort("", 2048, 4096) + val m = JSONUtils + .readStringValueMap(s""" +{ + "type": "sram", + "name": "BAD_BAD_SRAM", + "width": "wide", + "depth": "4096" +} + """) + .get + SRAMMacro.parseJSON(m) shouldBe None + } + + "String depth" should "be rejected" in { + val (json, port) = simplePort("", 2048, 4096) + val m = JSONUtils + .readStringValueMap(s""" +{ + "type": "sram", + "name": "BAD_BAD_SRAM", + "width": 512, + "depth": "octopus_under_the_sea" +} + """) + .get + SRAMMacro.parseJSON(m) shouldBe None + } +}