Moved a zillion files all over the place so that everything is now

in tapeout/src in the correct directory corresponding to internal packages.
Everything compiles and tests run
TODO:
- Figure out assembly step for MacroCompiler
- Does root project matter?
This commit is contained in:
chick
2021-08-06 13:09:45 -07:00
parent cdd8af4153
commit c907a7377c
40 changed files with 1809 additions and 23 deletions

3
.gitmodules vendored
View File

@@ -1,3 +0,0 @@
[submodule "mdf"]
path = mdf
url = https://github.com/ucb-bar/plsi-mdf.git

View File

@@ -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)

View File

@@ -1 +0,0 @@
enablePlugins(sbtassembly.AssemblyPlugin)

1
mdf

Submodule mdf deleted from e588024d70

View File

@@ -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,

View File

@@ -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)

View File

@@ -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(_))
}
}

View File

@@ -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"
}

View File

@@ -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))
}
}

View File

@@ -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))
}
}

View File

@@ -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
}

View File

@@ -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 "<prefix> port name" and "<prefix> 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"
}
}
}

View File

@@ -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
}
}
}
}

View File

@@ -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("""

View File

@@ -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 = """

View File

@@ -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 {

View File

@@ -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()
)
}
}

View File

@@ -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)
}
}
}

View File

@@ -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"))
)
}
}

View File

@@ -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)) =>
}
}
}

View File

@@ -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)
}
}

View File

@@ -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
}
}