Merge remote-tracking branch 'barstools/master' into flatten-barstools

This commit is contained in:
Jerry Zhao
2024-04-19 10:59:56 -07:00
48 changed files with 11680 additions and 0 deletions

44
.github/workflows/run-ci.yml vendored Normal file
View File

@@ -0,0 +1,44 @@
name: Test
on:
pull_request:
push:
branches:
- master
jobs:
test:
name: Unit Tests
runs-on: ubuntu-latest
strategy:
matrix:
scala: [ 2.12.14 ]
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup Scala
uses: olafurpg/setup-scala@v10
- name: Cache
uses: coursier/cache-action@v5
- name: Get submodules
run: git submodule update --init
- name: Test
run: sbt test
doc:
name: Documentation and formatting
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup Scala
uses: olafurpg/setup-scala@v10
- name: Check Formatting
run: sbt scalafmtCheckAll
all_test_passed:
name: "all tests passed"
runs-on: ubuntu-latest
needs: [test, doc]
steps:
- run: echo Success

27
.scalafmt.conf Normal file
View File

@@ -0,0 +1,27 @@
version = 2.7.5
maxColumn = 120
align = most
continuationIndent.defnSite = 2
assumeStandardLibraryStripMargin = true
docstrings = ScalaDoc
lineEndings = preserve
includeCurlyBraceInSelectChains = false
danglingParentheses.defnSite = true
danglingParentheses.callSite = true
align.tokens.add = [
{
code = ":"
}
]
newlines.alwaysBeforeCurlyBraceLambdaParams = false
newlines.alwaysBeforeMultilineDef = false
newlines.implicitParamListModifierForce = [before]
verticalMultiline.atDefnSite = true
optIn.annotationNewlines = true
rewrite.rules = [SortImports, PreferCurlyFors, AvoidInfix]

View File

@@ -0,0 +1,11 @@
// See LICENSE for license details
`timescale 1ns/1ps
module AnalogConst #(CONST, WIDTH) (
output [WIDTH-1:0] io
);
assign io = CONST;
endmodule

View File

@@ -0,0 +1,18 @@
// See LICENSE for license details
package barstools.iocell.chisel
import chisel3._
import chisel3.util.{HasBlackBoxResource}
import chisel3.experimental.{Analog, IntParam}
class AnalogConst(value: Int, width: Int = 1)
extends BlackBox(Map("CONST" -> IntParam(value), "WIDTH" -> IntParam(width)))
with HasBlackBoxResource {
val io = IO(new Bundle { val io = Analog(width.W) })
addResource("/barstools/iocell/vsrc/Analog.v")
}
object AnalogConst {
def apply(value: Int, width: Int = 1) = Module(new AnalogConst(value, width)).io.io
}

View File

@@ -0,0 +1,338 @@
// See LICENSE for license details
package barstools.iocell.chisel
import chisel3._
import chisel3.util.{Cat, HasBlackBoxInline}
import chisel3.reflect.DataMirror
import chisel3.experimental.{Analog, BaseModule}
// The following four IO cell bundle types are bare-minimum functional connections
// for modeling 4 different IO cell scenarios. The intention is that the user
// would create wrapper modules that extend these interfaces with additional
// control signals. These are loosely similar to the sifive-blocks PinCtrl bundles
// (https://github.com/sifive/sifive-blocks/blob/master/src/main/scala/devices/pinctrl/PinCtrl.scala),
// but we want to avoid a dependency on an external libraries.
/** The base IO bundle for an analog signal (typically something with no digital buffers inside)
* pad: off-chip (external) connection
* core: internal connection
*/
class AnalogIOCellBundle extends Bundle {
val pad = Analog(1.W) // Pad/bump signal (off-chip)
val core = Analog(1.W) // core signal (on-chip)
}
/** The base IO bundle for a signal with runtime-controllable direction
* pad: off-chip (external) connection
* i: input to chip logic (output from IO cell)
* ie: enable signal for i
* o: output from chip logic (input to IO cell)
* oe: enable signal for o
*/
class DigitalGPIOCellBundle extends Bundle {
val pad = Analog(1.W)
val i = Output(Bool())
val ie = Input(Bool())
val o = Input(Bool())
val oe = Input(Bool())
}
/** The base IO bundle for a digital output signal
* pad: off-chip (external) connection
* o: output from chip logic (input to IO cell)
* oe: enable signal for o
*/
class DigitalOutIOCellBundle extends Bundle {
val pad = Output(Bool())
val o = Input(Bool())
val oe = Input(Bool())
}
/** The base IO bundle for a digital input signal
* pad: off-chip (external) connection
* i: input to chip logic (output from IO cell)
* ie: enable signal for i
*/
class DigitalInIOCellBundle extends Bundle {
val pad = Input(Bool())
val i = Output(Bool())
val ie = Input(Bool())
}
trait IOCell extends BaseModule {
var iocell_name: Option[String] = None
/** Set IOCell name
* @param s Proposed name for the IOCell
*
* @return An inherited IOCell with given the proposed name
*/
def suggestName(s: String): this.type = {
iocell_name = Some(s)
super.suggestName(s)
}
}
trait AnalogIOCell extends IOCell {
val io: AnalogIOCellBundle
}
trait DigitalGPIOCell extends IOCell {
val io: DigitalGPIOCellBundle
}
trait DigitalInIOCell extends IOCell {
val io: DigitalInIOCellBundle
}
trait DigitalOutIOCell extends IOCell {
val io: DigitalOutIOCellBundle
}
// The following Generic IO cell black boxes have verilog models that mimic a very simple
// implementation of an IO cell. For building a real chip, it is important to implement
// and use similar classes which wrap the foundry-specific IO cells.
abstract class GenericIOCell extends BlackBox with HasBlackBoxInline {
val impl: String
val moduleName = this.getClass.getSimpleName
setInline(s"$moduleName.v", impl);
}
class GenericAnalogIOCell extends GenericIOCell with AnalogIOCell {
val io = IO(new AnalogIOCellBundle)
lazy val impl = s"""
`timescale 1ns/1ps
module GenericAnalogIOCell(
inout pad,
inout core
);
assign core = 1'bz;
assign pad = core;
endmodule"""
}
class GenericDigitalGPIOCell extends GenericIOCell with DigitalGPIOCell {
val io = IO(new DigitalGPIOCellBundle)
lazy val impl = s"""
`timescale 1ns/1ps
module GenericDigitalGPIOCell(
inout pad,
output i,
input ie,
input o,
input oe
);
assign pad = oe ? o : 1'bz;
assign i = ie ? pad : 1'b0;
endmodule"""
}
class GenericDigitalInIOCell extends GenericIOCell with DigitalInIOCell {
val io = IO(new DigitalInIOCellBundle)
lazy val impl = s"""
`timescale 1ns/1ps
module GenericDigitalInIOCell(
input pad,
output i,
input ie
);
assign i = ie ? pad : 1'b0;
endmodule"""
}
class GenericDigitalOutIOCell extends GenericIOCell with DigitalOutIOCell {
val io = IO(new DigitalOutIOCellBundle)
lazy val impl = s"""
`timescale 1ns/1ps
module GenericDigitalOutIOCell(
output pad,
input o,
input oe
);
assign pad = oe ? o : 1'bz;
endmodule"""
}
trait IOCellTypeParams {
def analog(): AnalogIOCell
def gpio(): DigitalGPIOCell
def input(): DigitalInIOCell
def output(): DigitalOutIOCell
}
case class GenericIOCellParams() extends IOCellTypeParams {
def analog() = Module(new GenericAnalogIOCell)
def gpio() = Module(new GenericDigitalGPIOCell)
def input() = Module(new GenericDigitalInIOCell)
def output() = Module(new GenericDigitalOutIOCell)
}
object IOCell {
/** From within a RawModule or MultiIOModule context, generate new module IOs from a given
* signal and return the new IO and a Seq containing all generated IO cells.
* @param coreSignal The signal onto which to add IO cells
* @param name An optional name or name prefix to use for naming IO cells
* @param abstractResetAsAsync When set, will coerce abstract resets to
* AsyncReset, and otherwise to Bool (sync reset)
* @return A tuple of (the generated IO data node, a Seq of all generated IO cell instances)
*/
def generateIOFromSignal[T <: Data](
coreSignal: T,
name: String,
typeParams: IOCellTypeParams = GenericIOCellParams(),
abstractResetAsAsync: Boolean = false
): (T, Seq[IOCell]) = {
val padSignal = IO(DataMirror.internal.chiselTypeClone[T](coreSignal)).suggestName(name)
val resetFn = if (abstractResetAsAsync) toAsyncReset else toSyncReset
val iocells = IOCell.generateFromSignal(coreSignal, padSignal, Some(s"iocell_$name"), typeParams, resetFn)
(padSignal, iocells)
}
/** Connect two identical signals together by adding IO cells between them and return a Seq
* containing all generated IO cells.
* @param coreSignal The core-side (internal) signal onto which to connect/add IO cells
* @param padSignal The pad-side (external) signal onto which to connect IO cells
* @param name An optional name or name prefix to use for naming IO cells
* @return A Seq of all generated IO cell instances
*/
val toSyncReset: (Reset) => Bool = _.asBool
val toAsyncReset: (Reset) => AsyncReset = _.asAsyncReset
def generateFromSignal[T <: Data, R <: Reset](
coreSignal: T,
padSignal: T,
name: Option[String] = None,
typeParams: IOCellTypeParams = GenericIOCellParams(),
concretizeResetFn: (Reset) => R = toSyncReset
): Seq[IOCell] = {
def genCell[T <: Data](
castToBool: (T) => Bool,
castFromBool: (Bool) => T
)(coreSignal: T,
padSignal: T
): Seq[IOCell] = {
DataMirror.directionOf(coreSignal) match {
case ActualDirection.Input => {
val iocell = typeParams.input()
name.foreach(n => {
iocell.suggestName(n)
})
coreSignal := castFromBool(iocell.io.i)
iocell.io.ie := true.B
iocell.io.pad := castToBool(padSignal)
Seq(iocell)
}
case ActualDirection.Output => {
val iocell = typeParams.output()
name.foreach(n => {
iocell.suggestName(n)
})
iocell.io.o := castToBool(coreSignal)
iocell.io.oe := true.B
padSignal := castFromBool(iocell.io.pad)
Seq(iocell)
}
case _ => throw new Exception(s"Signal does not have a direction and cannot be matched to an IOCell")
}
}
def genCellForClock = genCell[Clock](_.asUInt.asBool, _.asClock) _
def genCellForAsyncReset = genCell[AsyncReset](_.asBool, _.asAsyncReset) _
def genCellForAbstractReset = genCell[Reset](_.asBool, concretizeResetFn) _
(coreSignal, padSignal) match {
case (coreSignal: Analog, padSignal: Analog) => {
if (coreSignal.getWidth == 0) {
Seq()
} else {
require(
coreSignal.getWidth == 1,
"Analogs wider than 1 bit are not supported because we can't bit-select Analogs (https://github.com/freechipsproject/chisel3/issues/536)"
)
val iocell = typeParams.analog()
name.foreach(n => iocell.suggestName(n))
iocell.io.core <> coreSignal
padSignal <> iocell.io.pad
Seq(iocell)
}
}
case (coreSignal: Clock, padSignal: Clock) => genCellForClock(coreSignal, padSignal)
case (coreSignal: AsyncReset, padSignal: AsyncReset) => genCellForAsyncReset(coreSignal, padSignal)
case (coreSignal: Bits, padSignal: Bits) => {
require(padSignal.getWidth == coreSignal.getWidth, "padSignal and coreSignal must be the same width")
if (padSignal.getWidth == 0) {
// This dummy assignment will prevent invalid firrtl from being emitted
DataMirror.directionOf(coreSignal) match {
case ActualDirection.Input => coreSignal := 0.U
case _ => {}
}
Seq()
} else {
DataMirror.directionOf(coreSignal) match {
case ActualDirection.Input => {
val iocells = padSignal.asBools.zipWithIndex.map { case (sig, i) =>
val iocell = typeParams.input()
// Note that we are relying on chisel deterministically naming this in the index order (which it does)
// This has the side-effect of naming index 0 with no _0 suffix, which is how chisel names other signals
// An alternative solution would be to suggestName(n + "_" + i)
name.foreach(n => {
iocell.suggestName(n)
})
iocell.io.pad := sig
iocell.io.ie := true.B
iocell
}
// Note that the reverse here is because Cat(Seq(a,b,c,d)) yields abcd, but a is index 0 of the Seq
coreSignal := Cat(iocells.map(_.io.i).reverse)
iocells
}
case ActualDirection.Output => {
val iocells = coreSignal.asBools.zipWithIndex.map { case (sig, i) =>
val iocell = typeParams.output()
// Note that we are relying on chisel deterministically naming this in the index order (which it does)
// This has the side-effect of naming index 0 with no _0 suffix, which is how chisel names other signals
// An alternative solution would be to suggestName(n + "_" + i)
name.foreach(n => {
iocell.suggestName(n)
})
iocell.io.o := sig
iocell.io.oe := true.B
iocell
}
// Note that the reverse here is because Cat(Seq(a,b,c,d)) yields abcd, but a is index 0 of the Seq
padSignal := Cat(iocells.map(_.io.pad).reverse)
iocells
}
case _ => throw new Exception("Bits signal does not have a direction and cannot be matched to IOCell(s)")
}
}
}
case (coreSignal: Reset, padSignal: Reset) => genCellForAbstractReset(coreSignal, padSignal)
case (coreSignal: Vec[_], padSignal: Vec[_]) => {
require(padSignal.size == coreSignal.size, "size of Vec for padSignal and coreSignal must be the same")
coreSignal.zip(padSignal).zipWithIndex.foldLeft(Seq.empty[IOCell]) { case (total, ((core, pad), i)) =>
val ios = IOCell.generateFromSignal(core, pad, name.map(_ + "_" + i), typeParams)
total ++ ios
}
}
case (coreSignal: Record, padSignal: Record) => {
coreSignal.elements.foldLeft(Seq.empty[IOCell]) { case (total, (eltName, core)) =>
val pad = padSignal.elements(eltName)
val ios = IOCell.generateFromSignal(core, pad, name.map(_ + "_" + eltName), typeParams)
total ++ ios
}
}
case _ => { throw new Exception("Oops, I don't know how to handle this signal.") }
}
}
}

View File

@@ -0,0 +1,205 @@
// See LICENSE for license details.
package barstools.macros
/** Trait which can calculate the cost of compiling a memory against a certain
* library memory macro using a cost function.
*/
// TODO: eventually explore compiling a single target memory using multiple
// different kinds of target memory.
trait CostMetric extends Serializable {
/** Cost function that returns the cost of compiling a memory using a certain
* macro.
*
* @param mem Memory macro to compile (target memory)
* @param lib Library memory macro to use (library memory)
* @return The cost of this compile, defined by this cost metric, or None if
* it cannot be compiled.
*/
def cost(mem: Macro, lib: Macro): Option[Double]
/** Helper function to return the map of arguments (or an empty map if there are none).
*/
def commandLineParams(): Map[String, String]
// We also want this to show up for the class itself.
def name(): String
}
// Is there a better way to do this? (static method associated to CostMetric)
trait CostMetricCompanion {
def name(): String
/** Construct this cost metric from a command line mapping. */
def construct(m: Map[String, String]): CostMetric
}
// Some default cost functions.
/** Palmer's old metric.
* TODO: figure out what is the difference between this metric and the current
* default metric and either revive or delete this metric.
*/
object OldMetric extends CostMetric with CostMetricCompanion {
override def cost(mem: Macro, lib: Macro): Option[Double] = {
/* Palmer: A quick cost function (that must be kept in sync with
* memory_cost()) that attempts to avoid compiling unnecessary
* memories. This is a lower bound on the cost of compiling a
* memory: it assumes 100% bit-cell utilization when mapping. */
// val cost = 100 * (mem.depth * mem.width) / (lib.depth * lib.width) +
// (mem.depth * mem.width)
???
}
override def commandLineParams() = Map.empty[String, String]
override def name() = "OldMetric"
override def construct(m: Map[String, String]): CostMetric = OldMetric
}
/** An external cost function.
* Calls the specified path with paths to the JSON MDF representation of the mem
* and lib macros. The external executable should print a Double.
* None will be returned if the external executable does not print a valid
* Double.
*/
class ExternalMetric(path: String) extends CostMetric {
import mdf.macrolib.Utils.writeMacroToPath
import java.io._
import scala.language.postfixOps
import sys.process._
override def cost(mem: Macro, lib: Macro): Option[Double] = {
// Create temporary files.
val memFile = File.createTempFile("_macrocompiler_mem_", ".json")
val libFile = File.createTempFile("_macrocompiler_lib_", ".json")
writeMacroToPath(Some(memFile.getAbsolutePath), mem.src)
writeMacroToPath(Some(libFile.getAbsolutePath), lib.src)
// !! executes the given command
val result: String = (s"$path ${memFile.getAbsolutePath} ${libFile.getAbsolutePath}" !!).trim
// Remove temporary files.
memFile.delete()
libFile.delete()
try {
Some(result.toDouble)
} catch {
case _: NumberFormatException => None
}
}
override def commandLineParams() = Map("path" -> path)
override def name(): String = ExternalMetric.name()
}
object ExternalMetric extends CostMetricCompanion {
override def name() = "ExternalMetric"
/** Construct this cost metric from a command line mapping. */
override def construct(m: Map[String, String]): ExternalMetric = {
val pathOption = m.get("path")
pathOption match {
case Some(path: String) => new ExternalMetric(path)
case _ => throw new IllegalArgumentException("ExternalMetric missing option 'path'")
}
}
}
/** The current default metric in barstools, re-defined by Donggyu. */
// TODO: write tests for this function to make sure it selects the right things
object DefaultMetric extends CostMetric with CostMetricCompanion {
override def cost(mem: Macro, lib: Macro): Option[Double] = {
val memMask = mem.src.ports.map(_.maskGran).find(_.isDefined).flatten
val libMask = lib.src.ports.map(_.maskGran).find(_.isDefined).flatten
val memWidth = (memMask, libMask) match {
case (None, _) => mem.src.width
case (Some(p), None) =>
(mem.src.width / p) * math.ceil(
p.toDouble / lib.src.width
) * lib.src.width //We map the mask to distinct memories
case (Some(p), Some(m)) =>
if (m <= p) (mem.src.width / p) * math.ceil(p.toDouble / m) * m //Using multiple m's to create a p (integrally)
else (mem.src.width / p) * m //Waste the extra maskbits
}
val maskPenalty = (memMask, libMask) match {
case (None, Some(_)) => 0.001
case (_, _) => 0
}
val depthCost = math.ceil(mem.src.depth.toDouble / lib.src.depth.toDouble)
val widthCost = math.ceil(memWidth / lib.src.width.toDouble)
val bitsCost = (lib.src.depth * lib.src.width).toDouble
// Fraction of wasted bits plus const per mem
val requestedBits = (mem.src.depth * mem.src.width).toDouble
val bitsWasted = depthCost * widthCost * bitsCost - requestedBits
val wastedConst = 0.05 // 0 means waste as few bits with no regard for instance count
val costPerInst = wastedConst * depthCost * widthCost
Some(1.0 * bitsWasted / requestedBits + costPerInst + maskPenalty)
}
override def commandLineParams() = Map.empty[String, String]
override def name() = "DefaultMetric"
override def construct(m: Map[String, String]): CostMetric = DefaultMetric
}
object MacroCompilerUtil {
import java.io._
import java.util.Base64
// Adapted from https://stackoverflow.com/a/134918
/** Serialize an arbitrary object to String.
* Used to pass structured values through as an annotation.
*/
def objToString(o: Serializable): String = {
val byteOutput: ByteArrayOutputStream = new ByteArrayOutputStream
val objectOutput: ObjectOutputStream = new ObjectOutputStream(byteOutput)
objectOutput.writeObject(o)
objectOutput.close()
Base64.getEncoder.encodeToString(byteOutput.toByteArray)
}
/** Deserialize an arbitrary object from String. */
def objFromString(s: String): AnyRef = {
val data = Base64.getDecoder.decode(s)
val ois: ObjectInputStream = new ObjectInputStream(new ByteArrayInputStream(data))
val o = ois.readObject
ois.close()
o
}
}
object CostMetric {
/** Define some default metric. */
val default: CostMetric = DefaultMetric
val costMetricCreators: scala.collection.mutable.Map[String, CostMetricCompanion] = scala.collection.mutable.Map()
// Register some default metrics
registerCostMetric(OldMetric)
registerCostMetric(ExternalMetric)
registerCostMetric(DefaultMetric)
/** Register a cost metric.
* @param createFuncHelper Companion object to fetch the name and construct
* the metric.
*/
def registerCostMetric(createFuncHelper: CostMetricCompanion): Unit = {
costMetricCreators.update(createFuncHelper.name(), createFuncHelper)
}
/** Select a cost metric from string. */
def getCostMetric(m: String, params: Map[String, String]): CostMetric = {
if (m == "default") {
CostMetric.default
} else if (!costMetricCreators.contains(m)) {
throw new IllegalArgumentException("Invalid cost metric " + m)
} else {
costMetricCreators(m).construct(params)
}
}
}

View File

@@ -0,0 +1,981 @@
// See LICENSE for license details.
/** Terminology note:
* mem - target memory to compile, in design (e.g. Mem() in rocket)
* lib - technology SRAM(s) to use to compile mem
*/
package barstools.macros
import barstools.macros.Utils._
import firrtl.Utils.{one, zero, BoolType}
import firrtl.annotations._
import firrtl.ir._
import firrtl.options.Dependency
import firrtl.stage.TransformManager.TransformDependency
import firrtl.stage.{FirrtlSourceAnnotation, FirrtlStage, Forms, OutputFileAnnotation, RunFirrtlTransformAnnotation}
import firrtl.{PrimOps, _}
import mdf.macrolib.{PolarizedPort, PortPolarity, SRAMCompiler, SRAMGroup, SRAMMacro}
import java.io.{File, FileWriter}
import scala.annotation.tailrec
import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer
case class MacroCompilerException(msg: String) extends Exception(msg)
// TODO The parameters could be unpacked here instead of keeping it in a serialized form
case class MacroCompilerAnnotation(content: String) extends NoTargetAnnotation {
import MacroCompilerAnnotation.Params
def params: Params = MacroCompilerUtil.objFromString(content).asInstanceOf[Params]
}
/** The MacroCompilerAnnotation to trigger the macro compiler.
* Note that this annotation does NOT actually target any modules for
* compilation. It simply holds all the settings for the memory compiler. The
* actual selection of which memories to compile is set in the Params.
*
* To use, simply annotate the entire circuit itself with this annotation and
* include [[MacroCompilerTransform]].
*/
object MacroCompilerAnnotation {
/** Macro compiler mode. */
sealed trait CompilerMode
/** Strict mode - must compile all memories or error out. */
case object Strict extends CompilerMode
/** Synflops mode - compile all memories with synflops (do not map to lib at all). */
case object Synflops extends CompilerMode
/** CompileAndSynflops mode - compile all memories and create mock versions of the target libs with synflops. */
case object CompileAndSynflops extends CompilerMode
/** FallbackSynflops - compile all memories to SRAM when possible and fall back to synflops if a memory fails. * */
case object FallbackSynflops extends CompilerMode
/** CompileAvailable - compile what is possible and do nothing with uncompiled memories. * */
case object CompileAvailable extends CompilerMode
/** The default mode for the macro compiler.
* TODO: Maybe set the default to FallbackSynflops (typical for
* vlsi_mem_gen-like scripts) once it's implemented?
*/
val Default: CompilerMode = CompileAvailable
// Options as list of (CompilerMode, command-line name, description)
val options: Seq[(CompilerMode, String, String)] = Seq(
(Default, "default", "Select the default option from below."),
(Strict, "strict", "Compile all memories to library or return an error."),
(
Synflops,
"synflops",
"Produces synthesizable flop-based memories for all memories (do not map to lib at all); likely useful for simulation purposes."
),
(
CompileAndSynflops,
"compileandsynflops",
"Compile all memories and create mock versions of the target libs with synflops; likely also useful for simulation purposes."
),
(
FallbackSynflops,
"fallbacksynflops",
"Compile all memories to library when possible and fall back to synthesizable flop-based memories when library synth is not possible."
),
(
CompileAvailable,
"compileavailable",
"Compile all memories to library when possible and do nothing in case of errors. (default)"
)
)
/** Helper function to select a compiler mode. */
def stringToCompilerMode(str: String): CompilerMode = options.collectFirst {
case (mode, cmd, _) if cmd == str => mode
} match {
case Some(x) => x
case None => throw new IllegalArgumentException("No such compiler mode " + str)
}
/** Parameters associated to this MacroCompilerAnnotation.
*
* @param mem Path to memory lib
* @param memFormat Type of memory lib (Some("conf"), Some("mdf"), or None (defaults to mdf))
* @param lib Path to library lib or None if no libraries
* @param hammerIR Path to HammerIR output or None (not generated in this case)
* @param costMetric Cost metric to use
* @param mode Compiler mode (see CompilerMode)
* @param forceCompile Set of memories to force compiling to lib regardless of the mode
* @param forceSynflops Set of memories to force compiling as flops regardless of the mode
*/
case class Params(
mem: String,
memFormat: Option[String],
lib: Option[String],
hammerIR: Option[String],
costMetric: CostMetric,
mode: CompilerMode,
useCompiler: Boolean,
forceCompile: Set[String],
forceSynflops: Set[String])
extends Serializable
/** Create a MacroCompilerAnnotation.
* @param c Top-level circuit name (see class description)
* @param p Parameters (see above).
*/
def apply(c: String, p: Params): MacroCompilerAnnotation =
MacroCompilerAnnotation(MacroCompilerUtil.objToString(p))
}
class MacroCompilerPass(
mems: Option[Seq[Macro]],
libs: Option[Seq[Macro]],
compilers: Option[SRAMCompiler],
hammerIR: Option[String],
costMetric: CostMetric = CostMetric.default,
mode: MacroCompilerAnnotation.CompilerMode = MacroCompilerAnnotation.Default)
extends firrtl.passes.Pass {
// Helper function to check the legality of bitPairs.
// e.g. ((0,21), (22,43)) is legal
// ((0,21), (22,21)) is illegal and will throw an assert
private def checkBitPairs(bitPairs: Seq[(BigInt, BigInt)]): Unit = {
bitPairs.foldLeft(BigInt(-1))((lastBit, nextPair) => {
assert(lastBit + 1 == nextPair._1, s"Pair's first bit ${nextPair._1} does not follow last bit $lastBit")
assert(nextPair._2 >= nextPair._1, s"Pair $nextPair in bitPairs $bitPairs is illegal")
nextPair._2
})
}
/** Calculate bit pairs.
* This is a list of submemories by width.
* The tuples are (lsb, msb) inclusive.
* Example: (0, 7) and (8, 15) might be a split for a width=16 memory into two width=8 target memories.
* Another example: (0, 3), (4, 7), (8, 11) may be a split for a width-12 memory into 3 width-4 target memories.
*
* @param mem Memory to compile
* @param lib Lib to compile with
* @return Bit pairs or empty list if there was an error.
*/
private def calculateBitPairs(mem: Macro, lib: Macro): Seq[(BigInt, BigInt)] = {
val pairedPorts = mem.sortedPorts.zip(lib.sortedPorts)
val bitPairs = ArrayBuffer[(BigInt, BigInt)]()
var currentLSB: BigInt = 0
// Process every bit in the mem width.
for (memBit <- 0 until mem.src.width) {
val bitsInCurrentMem = memBit - currentLSB
// We'll need to find a bitPair that works for *all* the ports of the memory.
// e.g. unmasked read port and masked write port.
// For each port, store a tentative candidate for the split.
// Afterwards, figure out which one to use.
val bitPairCandidates = ArrayBuffer[(BigInt, BigInt)]()
for ((memPort, libPort) <- pairedPorts) {
// Sanity check to make sure we only split once per bit, once per port.
var alreadySplit: Boolean = false
// Helper function to check if it's time to split memories.
// @param effectiveLibWidth Split memory when we have this many bits.
def splitMemory(effectiveLibWidth: Int): Unit = {
assert(!alreadySplit)
if (bitsInCurrentMem == effectiveLibWidth) {
bitPairCandidates += ((currentLSB, memBit - 1))
alreadySplit = true
}
}
// Make sure we don't have a maskGran larger than the width of the memory.
assert(memPort.src.effectiveMaskGran <= memPort.src.width.get)
assert(libPort.src.effectiveMaskGran <= libPort.src.width.get)
val libWidth = libPort.src.width.get
// Don't consider cases of maskGran == width as "masked" since those masks
// effectively function as write-enable bits.
val memMask = if (memPort.src.effectiveMaskGran == memPort.src.width.get) None else memPort.src.maskGran
val libMask = if (libPort.src.effectiveMaskGran == libPort.src.width.get) None else libPort.src.maskGran
(memMask, libMask) match {
// Neither lib nor mem is masked.
// No problems here.
case (None, None) => splitMemory(libWidth)
// Only the lib is masked.
// Not an issue; we can just make all the bits in the lib mask enabled.
case (None, Some(_)) => splitMemory(libWidth)
// Only the mem is masked.
case (Some(p), None) =>
if (p % libPort.src.width.get == 0) {
// If the mem mask is a multiple of the lib width, then we're good.
// Just roll over every lib width as usual.
// e.g. lib width=4, mem maskGran={4, 8, 12, 16, ...}
splitMemory(libWidth)
} else if (libPort.src.width.get % p == 0) {
// Lib width is a multiple of the mem mask.
// Consider the case where mem mask = 4 but lib width = 8, unmasked.
// We can still compile, but will need to waste the extra bits.
splitMemory(memMask.get)
} else {
// No neat multiples.
// We might still be able to compile extremely inefficiently.
if (p < libPort.src.width.get) {
// Compile using mem mask as the effective width. (note that lib is not masked)
// e.g. mem mask = 3, lib width = 8
splitMemory(memMask.get)
} else {
// e.g. mem mask = 13, lib width = 8
System.err.println(
s"Unmasked target memory: unaligned mem maskGran $p with lib (${lib.src.name}) width ${libPort.src.width.get} not supported"
)
return Seq()
}
}
// Both lib and mem are masked.
case (Some(m), Some(l)) =>
if (m == l) {
// Lib maskGran == mem maskGran, no problems
splitMemory(libWidth)
} else if (m > l) {
// Mem maskGran > lib maskGran
if (m % l == 0) {
// Mem maskGran is a multiple of lib maskGran, carry on as normal.
splitMemory(libWidth)
} else {
System.err.println(s"Mem maskGran $m is not a multiple of lib maskGran $l: currently not supported")
return Seq()
}
} else { // m < l
// Lib maskGran > mem maskGran.
if (l % m == 0) {
// Lib maskGran is a multiple of mem maskGran.
// e.g. lib maskGran = 8, mem maskGran = 4.
// In this case we can only compile very wastefully (by treating
// lib as a mem maskGran width memory) :(
splitMemory(memMask.get)
// TODO: there's an optimization that could allow us to pack more
// bits in and be more efficient.
// e.g. say if mem maskGran = 4, lib maskGran = 8, libWidth = 32
// We could use 16 of bit (bits 0-3, 8-11, 16-19, 24-27) instead
// of treating it as simply a width 4 (!!!) memory.
// This would require a major refactor though.
} else {
System.err.println(s"Lib maskGran $m is not a multiple of mem maskGran $l: currently not supported")
return Seq()
}
}
}
}
// Choose an actual bit pair to add.
// We'll have to choose the smallest one (e.g. unmasked read port might be more tolerant of a bigger split than the masked write port).
if (bitPairCandidates.isEmpty) {
// No pair needed to split, just continue
} else {
val bestPair = bitPairCandidates.reduceLeft((leftPair, rightPair) => {
if (leftPair._2 - leftPair._1 + 1 > rightPair._2 - rightPair._1 + 1) leftPair else rightPair
})
bitPairs += bestPair
currentLSB = bestPair._2 + BigInt(1) // advance the LSB pointer
}
}
// Add in the last chunk if there are any leftovers
bitPairs += ((currentLSB, mem.src.width - 1))
bitPairs
}.toSeq
def compile(mem: Macro, lib: Macro): Option[(Module, Macro)] = {
assert(
mem.sortedPorts.lengthCompare(lib.sortedPorts.length) == 0,
"mem and lib should have an equal number of ports"
)
val pairedPorts = mem.sortedPorts.zip(lib.sortedPorts)
// Width mapping. See calculateBitPairs.
val bitPairs: Seq[(BigInt, BigInt)] = calculateBitPairs(mem, lib)
if (bitPairs.isEmpty) {
System.err.println("Error occurred during bitPairs calculations (bitPairs is empty).")
return None
}
// Check bit pairs.
checkBitPairs(bitPairs)
// Depth mapping
val stmts = ArrayBuffer[Statement]()
val outputs = mutable.HashMap[String, ArrayBuffer[(Expression, Expression)]]()
val selects = mutable.HashMap[String, Expression]()
val selectRegs = mutable.HashMap[String, Expression]()
/* Palmer: If we've got a parallel memory then we've got to take the
* address bits into account. */
if (mem.src.depth > lib.src.depth) {
mem.src.ports.foreach { port =>
val high = MacroCompilerMath.ceilLog2(mem.src.depth)
val low = MacroCompilerMath.ceilLog2(lib.src.depth)
val ref = WRef(port.address.name)
val nodeName = s"${ref.name}_sel"
val tpe = UIntType(IntWidth(high - low))
selects(ref.name) = WRef(nodeName, tpe)
stmts += DefNode(NoInfo, nodeName, bits(ref, high - 1, low))
// Donggyu: output selection should be piped
if (port.output.isDefined) {
val regName = s"${ref.name}_sel_reg"
val enable = (port.chipEnable, port.readEnable) match {
case (Some(ce), Some(re)) =>
and(WRef(ce.name, BoolType), WRef(re.name, BoolType))
case (Some(ce), None) => WRef(ce.name, BoolType)
case (None, Some(re)) => WRef(re.name, BoolType)
case (None, None) => one
}
selectRegs(ref.name) = WRef(regName, tpe)
stmts += DefRegister(NoInfo, regName, tpe, WRef(port.clock.get.name), zero, WRef(regName))
stmts += Connect(NoInfo, WRef(regName), Mux(enable, WRef(nodeName), WRef(regName), tpe))
}
}
}
for ((_, i) <- BigInt(0).until(mem.src.depth, lib.src.depth).zipWithIndex) {
for (j <- bitPairs.indices) {
val name = s"mem_${i}_$j"
// Create the instance.
stmts += WDefInstance(NoInfo, name, lib.src.name, lib.tpe)
// Connect extra ports of the lib.
stmts ++= lib.extraPorts.map { case (portName, portValue) =>
Connect(NoInfo, WSubField(WRef(name), portName), portValue)
}
}
for ((memPort, libPort) <- pairedPorts) {
val addrMatch = selects.get(memPort.src.address.name) match {
case None => one
case Some(addr) =>
val index = UIntLiteral(i, IntWidth(bitWidth(addr.tpe)))
DoPrim(PrimOps.Eq, Seq(addr, index), Nil, index.tpe)
}
val addrMatchReg = selectRegs.get(memPort.src.address.name) match {
case None => one
case Some(reg) =>
val index = UIntLiteral(i, IntWidth(bitWidth(reg.tpe)))
DoPrim(PrimOps.Eq, Seq(reg, index), Nil, index.tpe)
}
def andAddrMatch(e: Expression) = {
and(e, addrMatch)
}
val cats = ArrayBuffer[Expression]()
for (((low, high), j) <- bitPairs.zipWithIndex) {
val inst = WRef(s"mem_${i}_$j", lib.tpe)
def connectPorts2(mem: Expression, lib: String, polarity: Option[PortPolarity]): Statement =
Connect(NoInfo, WSubField(inst, lib), portToExpression(mem, polarity))
def connectPorts(mem: Expression, lib: String, polarity: PortPolarity): Statement =
connectPorts2(mem, lib, Some(polarity))
// Clock port mapping
/* Palmer: FIXME: I don't handle memories with read/write clocks yet. */
/* Colin not all libPorts have clocks but all memPorts do*/
libPort.src.clock.foreach { cPort =>
stmts += connectPorts(WRef(memPort.src.clock.get.name), cPort.name, cPort.polarity)
}
// Adress port mapping
/* Palmer: The address port to a memory is just the low-order bits of
* the top address. */
stmts += connectPorts(WRef(memPort.src.address.name), libPort.src.address.name, libPort.src.address.polarity)
// Output port mapping
(memPort.src.output, libPort.src.output) match {
case (Some(PolarizedPort(mem, _)), Some(PolarizedPort(lib, lib_polarity))) =>
/* Palmer: In order to produce the output of a memory we need to cat
* together a bunch of narrower memories, which can only be
* done after generating all the memories. This saves up the
* output statements for later. */
val name = s"${mem}_${i}_$j" // This name is the output from the instance (mem vs ${mem}).
val exp = portToExpression(bits(WSubField(inst, lib), high - low, 0), Some(lib_polarity))
stmts += DefNode(NoInfo, name, exp)
cats += WRef(name)
case (None, Some(_)) =>
/* Palmer: If the inner memory has an output port but the outer
* one doesn't then it's safe to just leave the outer
* port floating. */
case (None, None) =>
/* Palmer: If there's no output ports at all (ie, read-only
* port on the memory) then just don't worry about it,
* there's nothing to do. */
case (Some(PolarizedPort(mem, _)), None) =>
System.err.println("WARNING: Unable to match output ports on memory")
System.err.println(s" outer output port: $mem")
return None
}
// Input port mapping
(memPort.src.input, libPort.src.input) match {
case (Some(PolarizedPort(mem, _)), Some(PolarizedPort(lib, lib_polarity))) =>
/* Palmer: The input port to a memory just needs to happen in parallel,
* this does a part select to narrow the memory down. */
stmts += connectPorts(bits(WRef(mem), high, low), lib, lib_polarity)
case (None, Some(lib)) =>
/* Palmer: If the inner memory has an input port but the other
* one doesn't then it's safe to just leave the inner
* port floating. This should be handled by the
* default value of the write enable, so nothing should
* every make it into the memory. */
//Firrtl cares about dangling inputs now tie it off
stmts += IsInvalid(NoInfo, WSubField(inst, lib.name))
case (None, None) =>
/* Palmer: If there's no input ports at all (ie, read-only
* port on the memory) then just don't worry about it,
* there's nothing to do. */
case (Some(PolarizedPort(mem, _)), None) =>
System.err.println("WARNING: Unable to match input ports on memory")
System.err.println(s" outer input port: $mem")
return None
}
// Mask port mapping
val memMask = memPort.src.maskPort match {
case Some(PolarizedPort(mem, _)) =>
/* Palmer: The bits from the outer memory's write mask that will be
* used as the write mask for this inner memory. */
if (libPort.src.effectiveMaskGran == libPort.src.width.get) {
bits(WRef(mem), low / memPort.src.effectiveMaskGran)
} else {
require(isPowerOfTwo(libPort.src.effectiveMaskGran), "only powers of two masks supported for now")
// How much of this lib's width we are effectively using.
// If we have a mem maskGran less than the lib's maskGran, we'll have to take the smaller maskGran.
// Example: if we have a lib whose maskGran is 8 but our mem's maskGran is 4.
// The other case is if we're using a larger lib than mem.
val usingLessThanLibMaskGran = memPort.src.maskGran.get < libPort.src.effectiveMaskGran
val effectiveLibWidth =
if (usingLessThanLibMaskGran)
memPort.src.maskGran.get
else
libPort.src.width.get
cat(
(0 until libPort.src.width.get by libPort.src.effectiveMaskGran)
.map(i => {
if (usingLessThanLibMaskGran && i >= effectiveLibWidth) {
// If the memMaskGran is smaller than the lib's gran, then
// zero out the upper bits.
zero
} else {
if ((low + i) >= memPort.src.width.get) {
// If our bit is larger than the whole width of the mem, just zero out the upper bits.
zero
} else {
// Pick the appropriate bit from the mem mask.
bits(WRef(mem), (low + i) / memPort.src.effectiveMaskGran)
}
}
})
.reverse
)
}
case None =>
/* If there is a lib mask port but no mem mask port, just turn on
* all bits of the lib mask port. */
if (libPort.src.maskPort.isDefined) {
val width = libPort.src.width.get / libPort.src.effectiveMaskGran
val value = (BigInt(1) << width) - 1
UIntLiteral(value, IntWidth(width))
} else {
// No mask ports on either side.
// We treat a "mask" of a single bit to be equivalent to a write
// enable (as used below).
one
}
}
// Write enable port mapping
val memWriteEnable = memPort.src.writeEnable match {
case Some(PolarizedPort(mem, _)) =>
/* Palmer: The outer memory's write enable port, or a constant 1 if
* there isn't a write enable port. */
WRef(mem)
case None =>
/* Palmer: If there is no input port on the source memory port
* then we don't ever want to turn on this write
* enable. Otherwise, we just _always_ turn on the
* write enable port on the inner memory. */
if (memPort.src.input.isEmpty) zero else one
}
// Chip enable port mapping
val memChipEnable = memPort.src.chipEnable match {
case Some(PolarizedPort(mem, _)) => WRef(mem)
case None => one
}
// Read enable port mapping
/* Palmer: It's safe to ignore read enables, but we pass them through
* to the vendor memory if there's a port on there that
* implements the read enables. */
(memPort.src.readEnable, libPort.src.readEnable) match {
case (_, None) =>
case (Some(PolarizedPort(mem, _)), Some(PolarizedPort(lib, lib_polarity))) =>
stmts += connectPorts(andAddrMatch(WRef(mem)), lib, lib_polarity)
case (None, Some(PolarizedPort(lib, lib_polarity))) =>
stmts += connectPorts(andAddrMatch(and(not(memWriteEnable), memChipEnable)), lib, lib_polarity)
}
/* Palmer: This is actually the memory compiler: it figures out how to
* implement the outer memory's collection of ports using what
* the inner memory has availiable. */
((libPort.src.maskPort, libPort.src.writeEnable, libPort.src.chipEnable): @unchecked) match {
case (
Some(PolarizedPort(mask, mask_polarity)),
Some(PolarizedPort(we, we_polarity)),
Some(PolarizedPort(en, en_polarity))
) =>
/* Palmer: This is the simple option: every port exists. */
stmts += connectPorts(memMask, mask, mask_polarity)
stmts += connectPorts(andAddrMatch(memWriteEnable), we, we_polarity)
stmts += connectPorts(andAddrMatch(memChipEnable), en, en_polarity)
case (Some(PolarizedPort(mask, mask_polarity)), Some(PolarizedPort(we, we_polarity)), None) =>
/* Palmer: If we don't have a chip enable but do have mask ports. */
stmts += connectPorts(memMask, mask, mask_polarity)
stmts += connectPorts(andAddrMatch(and(memWriteEnable, memChipEnable)), we, we_polarity)
case (None, Some(PolarizedPort(we, we_polarity)), chipEnable) =>
if (bitWidth(memMask.tpe) == 1) {
/* Palmer: If we're expected to provide mask ports without a
* memory that actually has them then we can use the
* write enable port instead of the mask port. */
chipEnable match {
case Some(PolarizedPort(en, en_polarity)) =>
stmts += connectPorts(andAddrMatch(and(memWriteEnable, memMask)), we, we_polarity)
stmts += connectPorts(andAddrMatch(memChipEnable), en, en_polarity)
case _ =>
stmts += connectPorts(
andAddrMatch(and(and(memWriteEnable, memChipEnable), memMask)),
we,
we_polarity
)
}
} else {
System.err.println("cannot emulate multi-bit mask ports with write enable")
return None
}
case (None, None, None) =>
// No write ports to match up (this may be a read-only port).
// This isn't necessarily an error condition.
}
}
// Cat macro outputs for selection
memPort.src.output match {
case Some(PolarizedPort(mem, _)) if cats.nonEmpty =>
val name = s"${mem}_$i"
stmts += DefNode(NoInfo, name, cat(cats.toSeq.reverse))
outputs.getOrElseUpdate(mem, ArrayBuffer[(Expression, Expression)]()) +=
(addrMatchReg -> WRef(name))
case _ =>
}
}
}
// Connect mem outputs
val zeroOutputValue: Expression = UIntLiteral(0, IntWidth(mem.src.width))
mem.src.ports.foreach { port =>
port.output match {
case Some(PolarizedPort(mem, _)) =>
outputs.get(mem) match {
case Some(select) =>
val output = select.foldRight(zeroOutputValue) { case ((cond, tval), fval) =>
Mux(cond, tval, fval, fval.tpe)
}
stmts += Connect(NoInfo, WRef(mem), output)
case None =>
}
case None =>
}
}
Some((mem.module(Block(stmts.toSeq)), lib))
}
def run(c: Circuit): Circuit = {
var firstLib = true
val modules = (mems, libs) match {
case (Some(mems), Some(libs)) =>
// Try to compile each of the memories in mems.
// The 'state' is c.modules, which is a list of all the firrtl modules
// in the 'circuit'.
mems.foldLeft(c.modules) { (modules, mem) =>
val sram = mem.src
def groupMatchesMask(group: SRAMGroup, mem: SRAMMacro): Boolean = {
val memMask = mem.ports.map(_.maskGran).find(_.isDefined).flatten
val libMask = group.ports.map(_.maskGran).find(_.isDefined).flatten
(memMask, libMask) match {
case (None, _) => true
case (Some(_), None) => false
case (Some(m), Some(l)) => l <= m //Ignore memories that don't have nice mask
}
}
// Add compiler memories that might map well to libs
val compLibs = compilers match {
case Some(SRAMCompiler(_, groups)) =>
groups
.filter(g => g.family == sram.family && groupMatchesMask(g, sram))
.map(g => {
for {
w <- g.width
d <- g.depth if (sram.width % w == 0) && (sram.depth % d == 0)
} yield Seq(new Macro(buildSRAMMacro(g, d, w, g.vt.head)))
})
case None => Seq()
}
val fullLibs = libs ++ compLibs.flatten.flatten
// Try to compile mem against each lib in libs, keeping track of the
// best compiled version, external lib used, and cost.
val (best, _) = fullLibs.foldLeft(None: Option[(Module, Macro)], Double.MaxValue) {
case ((best, cost), lib) if mem.src.ports.size != lib.src.ports.size =>
/* Palmer: FIXME: This just assumes the Chisel and vendor ports are in the same
* order, but I'm starting with what actually gets generated. */
System.err.println(s"INFO: unable to compile ${mem.src.name} using ${lib.src.name} port count must match")
(best, cost)
case ((best, cost), lib) =>
// Run the cost function to evaluate this potential compile.
costMetric.cost(mem, lib) match {
case Some(newCost) =>
//System.err.println(s"Cost of ${lib.src.name} for ${mem.src.name}: ${newCost}")
// Try compiling
compile(mem, lib) match {
// If it was successful and the new cost is lower
case Some(p) if newCost < cost => (Some(p), newCost)
case _ => (best, cost)
}
case _ => (best, cost) // Cost function rejected this combination.
}
}
// If we were able to compile anything, then replace the original module
// in the modules list with a compiled version, as well as the extmodule
// stub for the lib.
best match {
case None =>
if (mode == MacroCompilerAnnotation.Strict)
throw MacroCompilerException(
s"Target memory ${mem.src.name} could not be compiled and strict mode is activated - aborting."
)
else
modules
case Some((mod, bb)) =>
hammerIR match {
case Some(f) =>
val hammerIRWriter = new FileWriter(new File(f), !firstLib)
if (firstLib) hammerIRWriter.write("[\n")
hammerIRWriter.write(bb.src.toJSON().toString())
hammerIRWriter.write("\n,\n")
hammerIRWriter.close()
firstLib = false
case None =>
}
modules.filterNot(m => m.name == mod.name || m.name == bb.blackbox.name) ++ Seq(mod, bb.blackbox)
}
}
case _ => c.modules
}
c.copy(modules = modules)
}
}
class MacroCompilerTransform extends Transform with DependencyAPIMigration {
override def prerequisites: Seq[TransformDependency] = Forms.LowForm
override def optionalPrerequisites: Seq[TransformDependency] = Forms.LowFormOptimized
override def optionalPrerequisiteOf: Seq[Dependency[Emitter]] = Forms.LowEmitters
override def invalidates(a: Transform) = false
def execute(state: CircuitState): CircuitState = state.annotations.collect { case a: MacroCompilerAnnotation =>
a
} match {
case Seq(anno: MacroCompilerAnnotation) =>
val MacroCompilerAnnotation.Params(
memFile,
memFileFormat,
libFile,
hammerIR,
costMetric,
mode,
useCompiler,
forceCompile,
forceSynflops
) = anno.params
if (mode == MacroCompilerAnnotation.FallbackSynflops) {
throw new UnsupportedOperationException("Not implemented yet")
}
// Check that we don't have any modules both forced to compile and synflops.
assert(forceCompile.intersect(forceSynflops).isEmpty, "Cannot have modules both forced to compile and synflops")
// Read, eliminate None, get only SRAM, make firrtl macro
val mems: Option[Seq[Macro]] = (memFileFormat match {
case Some("conf") => readConfFromPath(Some(memFile))
case _ => mdf.macrolib.Utils.readMDFFromPath(Some(memFile))
}) match {
case Some(x: Seq[mdf.macrolib.Macro]) =>
Some(filterForSRAM(Some(x)).getOrElse(List()).map { new Macro(_) })
case _ => None
}
val libs: Option[Seq[Macro]] = mdf.macrolib.Utils.readMDFFromPath(libFile) match {
case Some(x: Seq[mdf.macrolib.Macro]) =>
Some(filterForSRAM(Some(x)).getOrElse(List()).map { new Macro(_) })
case _ => None
}
val compilers: Option[mdf.macrolib.SRAMCompiler] = mdf.macrolib.Utils.readMDFFromPath(libFile) match {
case Some(x: Seq[mdf.macrolib.Macro]) =>
if (useCompiler) {
findSRAMCompiler(Some(x))
} else None
case _ => None
}
// Helper function to turn a set of mem names into a Seq[Macro].
def setToSeqMacro(names: Set[String]): Seq[Macro] = {
names.toSeq.map(memName => mems.get.collectFirst { case m if m.src.name == memName => m }.get)
}
// Build lists of memories for compilation and synflops.
val memCompile = mems.map { actualMems =>
val memsAdjustedForMode = if (mode == MacroCompilerAnnotation.Synflops) Seq.empty else actualMems
memsAdjustedForMode.filterNot(m => forceSynflops.contains(m.src.name)) ++ setToSeqMacro(forceCompile)
}
val memSynflops: Seq[Macro] = mems.map { actualMems =>
//
val memsAdjustedForMode = if (mode == MacroCompilerAnnotation.Synflops) actualMems else Seq.empty
memsAdjustedForMode.filterNot(m => forceCompile.contains(m.src.name)) ++ setToSeqMacro(forceSynflops)
}.getOrElse(Seq.empty)
val transforms = Seq(
new MacroCompilerPass(memCompile, libs, compilers, hammerIR, costMetric, mode),
new SynFlopsPass(
true,
memSynflops ++ (if (mode == MacroCompilerAnnotation.CompileAndSynflops) {
libs.get
} else {
Seq.empty
})
)
)
transforms.foldLeft(state)((s, xform) => xform.runTransform(s))
case _ => state
}
}
class MacroCompilerOptimizations extends SeqTransform with DependencyAPIMigration {
override def prerequisites: Seq[TransformDependency] = Forms.LowForm
override def optionalPrerequisites: Seq[TransformDependency] = Forms.LowFormOptimized
override def optionalPrerequisiteOf: Seq[Dependency[Emitter]] = Forms.LowEmitters
override def invalidates(a: Transform) = false
def transforms: Seq[Transform] = Seq(
passes.RemoveValidIf,
new firrtl.transforms.ConstantPropagation,
passes.memlib.VerilogMemDelays,
new firrtl.transforms.ConstantPropagation,
passes.SplitExpressions,
passes.CommonSubexpressionElimination
)
}
object MacroCompiler extends App {
sealed trait MacroParam
case object Macros extends MacroParam
case object MacrosFormat extends MacroParam
case object Library extends MacroParam
case object Verilog extends MacroParam
case object Firrtl extends MacroParam
case object HammerIR extends MacroParam
case object CostFunc extends MacroParam
case object Mode extends MacroParam
case object UseCompiler extends MacroParam
type MacroParamMap = Map[MacroParam, String]
type CostParamMap = Map[String, String]
type ForcedMemories = (Set[String], Set[String])
val modeOptions: Seq[String] = MacroCompilerAnnotation.options.map { case (_, cmd, description) =>
s" $cmd: $description"
}
val usage: String = (Seq(
"Options:",
" -n, --macro-conf: The set of macros to compile in firrtl-generated conf format (exclusive with -m)",
" -m, --macro-mdf: The set of macros to compile in MDF JSON format (exclusive with -n)",
" -l, --library: The set of macros that have blackbox instances",
" -u, --use-compiler: Flag, whether to use the memory compiler defined in library",
" -v, --verilog: Verilog output",
" -f, --firrtl: FIRRTL output (optional)",
" -hir, --hammer-ir: Hammer-IR output currently only needed for IP compilers",
" -c, --cost-func: Cost function to use. Optional (default: \"default\")",
" -cp, --cost-param: Cost function parameter. (Optional depending on the cost function.). e.g. -c ExternalMetric -cp path /path/to/my/cost/script",
" --force-compile [mem]: Force the given memory to be compiled to target libs regardless of the mode",
" --force-synflops [mem]: Force the given memory to be compiled via synflops regardless of the mode",
" --mode:"
) ++ modeOptions).mkString("\n")
@tailrec
def parseArgs(
map: MacroParamMap,
costMap: CostParamMap,
forcedMemories: ForcedMemories,
args: List[String]
): (MacroParamMap, CostParamMap, ForcedMemories) =
args match {
case Nil => (map, costMap, forcedMemories)
case ("-n" | "--macro-conf") :: value :: tail =>
parseArgs(map + (Macros -> value) + (MacrosFormat -> "conf"), costMap, forcedMemories, tail)
case ("-m" | "--macro-mdf") :: value :: tail =>
parseArgs(map + (Macros -> value) + (MacrosFormat -> "mdf"), costMap, forcedMemories, tail)
case ("-l" | "--library") :: value :: tail =>
parseArgs(map + (Library -> value), costMap, forcedMemories, tail)
case ("-u" | "--use-compiler") :: tail =>
parseArgs(map + (UseCompiler -> ""), costMap, forcedMemories, tail)
case ("-v" | "--verilog") :: value :: tail =>
parseArgs(map + (Verilog -> value), costMap, forcedMemories, tail)
case ("-f" | "--firrtl") :: value :: tail =>
parseArgs(map + (Firrtl -> value), costMap, forcedMemories, tail)
case ("-hir" | "--hammer-ir") :: value :: tail =>
parseArgs(map + (HammerIR -> value), costMap, forcedMemories, tail)
case ("-c" | "--cost-func") :: value :: tail =>
parseArgs(map + (CostFunc -> value), costMap, forcedMemories, tail)
case ("-cp" | "--cost-param") :: value1 :: value2 :: tail =>
parseArgs(map, costMap + (value1 -> value2), forcedMemories, tail)
case "--force-compile" :: value :: tail =>
parseArgs(map, costMap, forcedMemories.copy(_1 = forcedMemories._1 + value), tail)
case "--force-synflops" :: value :: tail =>
parseArgs(map, costMap, forcedMemories.copy(_2 = forcedMemories._2 + value), tail)
case "--mode" :: value :: tail =>
parseArgs(map + (Mode -> value), costMap, forcedMemories, tail)
case arg :: _ =>
println(s"Unknown field $arg\n")
println(usage)
sys.exit(1)
}
def run(args: List[String]): Unit = {
val (params, costParams, forcedMemories) =
parseArgs(Map[MacroParam, String](), Map[String, String](), (Set.empty, Set.empty), args)
try {
val macros = params.get(MacrosFormat) match {
case Some("conf") =>
filterForSRAM(readConfFromPath(params.get(Macros))).get.map(x => new Macro(x).blackbox)
case _ =>
filterForSRAM(mdf.macrolib.Utils.readMDFFromPath(params.get(Macros))).get
.map(x => new Macro(x).blackbox)
}
if (macros.nonEmpty) {
// Note: the last macro in the input list is (seemingly arbitrarily)
// determined as the firrtl "top-level module".
val circuit = Circuit(NoInfo, macros, macros.last.name)
val annotations = AnnotationSeq(
Seq(
MacroCompilerAnnotation(
circuit.main,
MacroCompilerAnnotation.Params(
params(Macros),
params.get(MacrosFormat),
params.get(Library),
params.get(HammerIR),
CostMetric.getCostMetric(params.getOrElse(CostFunc, "default"), costParams),
MacroCompilerAnnotation.stringToCompilerMode(params.getOrElse(Mode, "default")),
params.contains(UseCompiler),
forceCompile = forcedMemories._1,
forceSynflops = forcedMemories._2
)
)
)
)
// The actual MacroCompilerTransform basically just generates an input circuit
val macroCompilerInput = CircuitState(circuit, annotations)
val macroCompiled = (new MacroCompilerTransform).execute(macroCompilerInput)
// Run FIRRTL compiler
// For each generated module, have to create a new circuit with that module
// as top, and all other modules as ExtModules. This guarantees all modules
// are elaborated
val verilog = macroCompiled.circuit.modules
.map(_.name)
.map { macroName =>
val (mainMod, otherMods) = macroCompiled.circuit.modules.partition(_.name == macroName)
val extMods = otherMods.map(m => ExtModule(NoInfo, m.name, m.ports, m.name, Nil))
val circuit = Circuit(NoInfo, mainMod ++ extMods, macroName)
(new FirrtlStage)
.execute(
Array.empty,
Seq(
OutputFileAnnotation(params.get(Verilog).get),
RunFirrtlTransformAnnotation(new VerilogEmitter),
EmitCircuitAnnotation(classOf[VerilogEmitter]),
FirrtlSourceAnnotation(circuit.serialize)
)
)
.collect { case c: EmittedVerilogCircuitAnnotation => c }
.head
.value
.value
}
.mkString("\n")
val verilogWriter = new FileWriter(new File(params.get(Verilog).get))
verilogWriter.write(verilog)
verilogWriter.close()
params.get(HammerIR) match {
case Some(hammerIRFile: String) =>
val lines = FileUtils.getLines(hammerIRFile).toList
val hammerIRWriter = new FileWriter(new File(hammerIRFile))
// JSON means we need to destroy the last comma :(
lines.dropRight(1).foreach(l => hammerIRWriter.write(l + "\n"))
hammerIRWriter.write("]\n")
hammerIRWriter.close()
case None =>
}
} else {
// Warn user
System.err.println("WARNING: Empty *.mems.conf file. No memories generated.")
// Emit empty verilog file if no macros found
params.get(Verilog) match {
case Some(verilogFile: String) =>
// Create an empty verilog file
val verilogWriter = new FileWriter(new File(verilogFile))
verilogWriter.close()
case None =>
}
params.get(HammerIR) match {
case Some(hammerIRFile: String) =>
// Create an empty HammerIR file
val hammerIRWriter = new FileWriter(new File(hammerIRFile))
hammerIRWriter.write("[]\n")
hammerIRWriter.close()
case None =>
}
}
} catch {
case e: java.util.NoSuchElementException =>
if (args.isEmpty) {
println("Command line arguments must be specified")
} else {
e.printStackTrace()
}
e.printStackTrace()
sys.exit(1)
case e: MacroCompilerException =>
println(usage)
e.printStackTrace()
sys.exit(1)
case e: Throwable =>
throw e
}
}
run(args.toList)
}

View File

@@ -0,0 +1,152 @@
// See LICENSE for license details.
package barstools.macros
import barstools.macros.Utils._
import firrtl.Utils.{one, zero}
import firrtl._
import firrtl.ir._
import firrtl.passes.MemPortUtils.memPortField
import scala.collection.mutable
class SynFlopsPass(synflops: Boolean, libs: Seq[Macro]) extends firrtl.passes.Pass {
val extraMods: mutable.ArrayBuffer[Module] = scala.collection.mutable.ArrayBuffer.empty[Module]
lazy val libMods: Map[String, Module] = libs.map { lib =>
lib.src.name -> {
val (dataType, dataWidth) = lib.src.ports.foldLeft(None: Option[BigInt])((res, port) =>
(res, port.maskPort) match {
case (_, None) =>
res
case (None, Some(_)) =>
Some(port.effectiveMaskGran)
case (Some(x), Some(_)) =>
assert(x == port.effectiveMaskGran)
res
}
) match {
case None => (UIntType(IntWidth(lib.src.width)), lib.src.width)
case Some(gran) => (UIntType(IntWidth(gran)), gran.intValue)
}
val maxDepth = firrtl.Utils.min(lib.src.depth, 1 << 26)
// Change macro to be mapped onto to look like the below mem
// by changing its depth, and width
val lib_macro = new Macro(
lib.src.copy(
name = "split_" + lib.src.name,
depth = maxDepth,
width = dataWidth,
ports = lib.src.ports.map(p =>
p.copy(
width = p.width.map(_ => dataWidth),
depth = p.depth.map(_ => maxDepth),
maskGran = p.maskGran.map(_ => dataWidth)
)
)
)
)
val mod_macro = new MacroCompilerPass(None, None, None, None).compile(lib, lib_macro)
val (real_mod, real_macro) = mod_macro.get
val mem = DefMemory(
NoInfo,
"ram",
dataType,
maxDepth,
1, // writeLatency
1, // readLatency. This is possible because of VerilogMemDelays
real_macro.readers.indices.map(i => s"R_$i"),
real_macro.writers.indices.map(i => s"W_$i"),
real_macro.readwriters.indices.map(i => s"RW_$i")
)
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 {
case (Some(en_port), Some(re_port)) =>
and(portToExpression(en_port), portToExpression(re_port))
case (Some(en_port), None) => portToExpression(en_port)
case (None, Some(re_port)) => portToExpression(re_port)
case (None, None) => one
}
val data = memPortField(mem, s"R_$i", "data")
val read = data
Seq(
Connect(NoInfo, memPortField(mem, s"R_$i", "clk"), clock),
Connect(NoInfo, memPortField(mem, s"R_$i", "addr"), address),
Connect(NoInfo, memPortField(mem, s"R_$i", "en"), enable),
Connect(NoInfo, WRef(r.src.output.get.name), read)
)
}
val writeConnects = real_macro.writers.zipWithIndex.flatMap { case (w, i) =>
val clock = portToExpression(w.src.clock.get)
val address = portToExpression(w.src.address)
val enable = (w.src.chipEnable, w.src.writeEnable) match {
case (Some(en), Some(we)) =>
and(portToExpression(en), portToExpression(we))
case (Some(en), None) => portToExpression(en)
case (None, Some(we)) => portToExpression(we)
case (None, None) => zero // is it possible?
}
val mask = w.src.maskPort match {
case Some(m) => portToExpression(m)
case None => one
}
val data = memPortField(mem, s"W_$i", "data")
val write = portToExpression(w.src.input.get)
Seq(
Connect(NoInfo, memPortField(mem, s"W_$i", "clk"), clock),
Connect(NoInfo, memPortField(mem, s"W_$i", "addr"), address),
Connect(NoInfo, memPortField(mem, s"W_$i", "en"), enable),
Connect(NoInfo, memPortField(mem, s"W_$i", "mask"), mask),
Connect(NoInfo, data, write)
)
}
val readwriteConnects = real_macro.readwriters.zipWithIndex.flatMap { case (rw, i) =>
val clock = portToExpression(rw.src.clock.get)
val address = portToExpression(rw.src.address)
val wmode = rw.src.writeEnable match {
case Some(we) => portToExpression(we)
case None => zero // is it possible?
}
val wmask = rw.src.maskPort match {
case Some(wm) => portToExpression(wm)
case None => one
}
val enable = (rw.src.chipEnable, rw.src.readEnable) match {
case (Some(en), Some(re)) =>
and(portToExpression(en), or(portToExpression(re), wmode))
case (Some(en), None) => portToExpression(en)
case (None, Some(re)) => or(portToExpression(re), wmode)
case (None, None) => one
}
val wdata = memPortField(mem, s"RW_$i", "wdata")
val rdata = memPortField(mem, s"RW_$i", "rdata")
val write = portToExpression(rw.src.input.get)
val read = rdata
Seq(
Connect(NoInfo, memPortField(mem, s"RW_$i", "clk"), clock),
Connect(NoInfo, memPortField(mem, s"RW_$i", "addr"), address),
Connect(NoInfo, memPortField(mem, s"RW_$i", "en"), enable),
Connect(NoInfo, memPortField(mem, s"RW_$i", "wmode"), wmode),
Connect(NoInfo, memPortField(mem, s"RW_$i", "wmask"), wmask),
Connect(NoInfo, WRef(rw.src.output.get.name), read),
Connect(NoInfo, wdata, write)
)
}
extraMods.append(real_macro.module(Block(mem +: (readConnects ++ writeConnects ++ readwriteConnects))))
real_mod
}
}.toMap
def run(c: Circuit): Circuit = {
if (!synflops) c
else c.copy(modules = c.modules.map(m => libMods.getOrElse(m.name, m)) ++ extraMods)
}
}

View File

@@ -0,0 +1,262 @@
// See LICENSE for license details.
package barstools.macros
import firrtl.Utils.BoolType
import firrtl.ir._
import firrtl.passes.memlib._
import firrtl.{PrimOps, _}
import mdf.macrolib.{Input => _, Output => _, _}
import scala.language.implicitConversions
object MacroCompilerMath {
def ceilLog2(x: BigInt): Int = (x - 1).bitLength
}
class FirrtlMacroPort(port: MacroPort) {
val src: MacroPort = port
val isReader: Boolean = port.output.nonEmpty && port.input.isEmpty
val isWriter: Boolean = port.input.nonEmpty && port.output.isEmpty
val isReadWriter: Boolean = port.input.nonEmpty && port.output.nonEmpty
val addrType: UIntType = UIntType(IntWidth(MacroCompilerMath.ceilLog2(port.depth.get).max(1)))
val dataType: UIntType = UIntType(IntWidth(port.width.get))
val maskType: UIntType = UIntType(IntWidth(port.width.get / port.effectiveMaskGran))
// Bundle representing this macro port.
val tpe: BundleType = BundleType(
Seq(Field(port.address.name, Flip, addrType)) ++
port.clock.map(p => Field(p.name, Flip, ClockType)) ++
port.input.map(p => Field(p.name, Flip, dataType)) ++
port.output.map(p => Field(p.name, Default, dataType)) ++
port.chipEnable.map(p => Field(p.name, Flip, BoolType)) ++
port.readEnable.map(p => Field(p.name, Flip, BoolType)) ++
port.writeEnable.map(p => Field(p.name, Flip, BoolType)) ++
port.maskPort.map(p => Field(p.name, Flip, maskType))
)
val ports: Seq[Port] = tpe.fields.map(f =>
Port(
NoInfo,
f.name,
f.flip match {
case Default => Output
case Flip => Input
},
f.tpe
)
)
}
// Reads an SRAMMacro and generates firrtl blackboxes.
class Macro(srcMacro: SRAMMacro) {
val src: SRAMMacro = srcMacro
val firrtlPorts: Seq[FirrtlMacroPort] = srcMacro.ports.map { new FirrtlMacroPort(_) }
val writers: Seq[FirrtlMacroPort] = firrtlPorts.filter(p => p.isWriter)
val readers: Seq[FirrtlMacroPort] = firrtlPorts.filter(p => p.isReader)
val readwriters: Seq[FirrtlMacroPort] = firrtlPorts.filter(p => p.isReadWriter)
val sortedPorts: Seq[FirrtlMacroPort] = writers ++ readers ++ readwriters
val extraPorts: Seq[(String, UIntLiteral)] = srcMacro.extraPorts.map { p =>
assert(p.portType == Constant) // TODO: release it?
val name = p.name
val width = BigInt(p.width.toLong)
val value = BigInt(p.value.toLong)
name -> UIntLiteral(value, IntWidth(width))
}
// Bundle representing this memory blackbox
val tpe: BundleType = BundleType(firrtlPorts.flatMap(_.tpe.fields))
private val modPorts = firrtlPorts.flatMap(_.ports) ++
extraPorts.map { case (name, value) => Port(NoInfo, name, Input, value.tpe) }
val blackbox: ExtModule = ExtModule(NoInfo, srcMacro.name, modPorts, srcMacro.name, Nil)
def module(body: Statement): Module = Module(NoInfo, srcMacro.name, modPorts, body)
}
object Utils {
def filterForSRAM(s: Option[Seq[mdf.macrolib.Macro]]): Option[Seq[mdf.macrolib.SRAMMacro]] = {
s match {
case Some(l: Seq[mdf.macrolib.Macro]) =>
Some(l.filter { _.isInstanceOf[mdf.macrolib.SRAMMacro] }.map { m => m.asInstanceOf[mdf.macrolib.SRAMMacro] })
case _ => None
}
}
// This utility reads a conf in and returns MDF like mdf.macrolib.Utils.readMDFFromPath
def readConfFromPath(path: Option[String]): Option[Seq[mdf.macrolib.Macro]] = {
path.map(p => Utils.readConfFromString(FileUtils.getText(p)))
}
def readConfFromString(str: String): Seq[mdf.macrolib.Macro] = {
MemConf.fromString(str).map { m: MemConf =>
val ports = m.ports.map { case (port, num) => Seq.fill(num)(port) }.reduce(_ ++ _)
SRAMMacro(
m.name,
m.width,
m.depth,
Utils.portSpecToFamily(ports),
Utils.portSpecToMacroPort(m.width, m.depth, m.maskGranularity, ports)
)
}
}
def portSpecToFamily(ports: Seq[MemPort]): String = {
val numR = ports.count { case ReadPort => true; case _ => false }
val numW = ports.count { case WritePort | MaskedWritePort => true; case _ => false }
val numRW = ports.count { case ReadWritePort | MaskedReadWritePort => true; case _ => false }
val numRStr = if (numR > 0) s"${numR}r" else ""
val numWStr = if (numW > 0) s"${numW}w" else ""
val numRWStr = if (numRW > 0) s"${numRW}rw" else ""
numRStr + numWStr + numRWStr
}
// This translates between two represenations of ports
def portSpecToMacroPort(width: Int, depth: BigInt, maskGran: Option[Int], ports: Seq[MemPort]): Seq[MacroPort] = {
var numR = 0
var numW = 0
var numRW = 0
ports.map {
case ReadPort =>
val portName = s"R$numR"
numR += 1
MacroPort(
width = Some(width),
depth = Some(depth),
address = PolarizedPort(s"${portName}_addr", ActiveHigh),
clock = Some(PolarizedPort(s"${portName}_clk", PositiveEdge)),
readEnable = Some(PolarizedPort(s"${portName}_en", ActiveHigh)),
output = Some(PolarizedPort(s"${portName}_data", ActiveHigh))
)
case WritePort =>
val portName = s"W$numW"
numW += 1
MacroPort(
width = Some(width),
depth = Some(depth),
address = PolarizedPort(s"${portName}_addr", ActiveHigh),
clock = Some(PolarizedPort(s"${portName}_clk", PositiveEdge)),
writeEnable = Some(PolarizedPort(s"${portName}_en", ActiveHigh)),
input = Some(PolarizedPort(s"${portName}_data", ActiveHigh))
)
case MaskedWritePort =>
val portName = s"W$numW"
numW += 1
MacroPort(
width = Some(width),
depth = Some(depth),
address = PolarizedPort(s"${portName}_addr", ActiveHigh),
clock = Some(PolarizedPort(s"${portName}_clk", PositiveEdge)),
writeEnable = Some(PolarizedPort(s"${portName}_en", ActiveHigh)),
maskPort = Some(PolarizedPort(s"${portName}_mask", ActiveHigh)),
maskGran = maskGran,
input = Some(PolarizedPort(s"${portName}_data", ActiveHigh))
)
case ReadWritePort =>
val portName = s"RW$numRW"
numRW += 1
MacroPort(
width = Some(width),
depth = Some(depth),
address = PolarizedPort(s"${portName}_addr", ActiveHigh),
clock = Some(PolarizedPort(s"${portName}_clk", PositiveEdge)),
chipEnable = Some(PolarizedPort(s"${portName}_en", ActiveHigh)),
writeEnable = Some(PolarizedPort(s"${portName}_wmode", ActiveHigh)),
input = Some(PolarizedPort(s"${portName}_wdata", ActiveHigh)),
output = Some(PolarizedPort(s"${portName}_rdata", ActiveHigh))
)
case MaskedReadWritePort =>
val portName = s"RW$numRW"
numRW += 1
MacroPort(
width = Some(width),
depth = Some(depth),
address = PolarizedPort(s"${portName}_addr", ActiveHigh),
clock = Some(PolarizedPort(s"${portName}_clk", PositiveEdge)),
chipEnable = Some(PolarizedPort(s"${portName}_en", ActiveHigh)),
writeEnable = Some(PolarizedPort(s"${portName}_wmode", ActiveHigh)),
maskPort = Some(PolarizedPort(s"${portName}_wmask", ActiveHigh)),
maskGran = maskGran,
input = Some(PolarizedPort(s"${portName}_wdata", ActiveHigh)),
output = Some(PolarizedPort(s"${portName}_rdata", ActiveHigh))
)
}
}
def findSRAMCompiler(s: Option[Seq[mdf.macrolib.Macro]]): Option[mdf.macrolib.SRAMCompiler] = {
s match {
case Some(l: Seq[mdf.macrolib.Macro]) =>
l.collectFirst { case x: mdf.macrolib.SRAMCompiler =>
x
}
case _ => None
}
}
def buildSRAMMacros(s: mdf.macrolib.SRAMCompiler): Seq[mdf.macrolib.SRAMMacro] = {
for {
g <- s.groups
d <- g.depth
w <- g.width
vt <- g.vt
} yield mdf.macrolib.SRAMMacro(
makeName(g, d, w, vt),
w,
d,
g.family,
g.ports.map(_.copy(width = Some(w), depth = Some(d))),
vt,
g.mux,
g.extraPorts
)
}
def buildSRAMMacro(g: mdf.macrolib.SRAMGroup, d: Int, w: Int, vt: String): mdf.macrolib.SRAMMacro = {
mdf.macrolib.SRAMMacro(
makeName(g, d, w, vt),
w,
d,
g.family,
g.ports.map(_.copy(width = Some(w), depth = Some(d))),
vt,
g.mux,
g.extraPorts
)
}
def makeName(g: mdf.macrolib.SRAMGroup, depth: Int, width: Int, vt: String): String = {
g.name.foldLeft("") { (builder, next) =>
next match {
case "depth" | "DEPTH" => builder + depth
case "width" | "WIDTH" => builder + width
case "vt" => builder + vt.toLowerCase
case "VT" => builder + vt.toUpperCase
case "family" => builder + g.family.toLowerCase
case "FAMILY" => builder + g.family.toUpperCase
case "mux" | "MUX" => builder + g.mux
case other => builder + other
}
}
}
def and(e1: Expression, e2: Expression): DoPrim =
DoPrim(PrimOps.And, Seq(e1, e2), Nil, e1.tpe)
def or(e1: Expression, e2: Expression): DoPrim =
DoPrim(PrimOps.Or, Seq(e1, e2), Nil, e1.tpe)
def bits(e: Expression, high: BigInt, low: BigInt): Expression =
DoPrim(PrimOps.Bits, Seq(e), Seq(high, low), UIntType(IntWidth(high - low + 1)))
def bits(e: Expression, idx: BigInt): Expression = bits(e, idx, idx)
def cat(es: Seq[Expression]): Expression =
if (es.size == 1) es.head
else DoPrim(PrimOps.Cat, Seq(es.head, cat(es.tail)), Nil, UnknownType)
def not(e: Expression): DoPrim =
DoPrim(PrimOps.Not, Seq(e), Nil, e.tpe)
// Convert a port to a FIRRTL expression, handling polarity along the way.
def portToExpression(pp: PolarizedPort): Expression =
portToExpression(WRef(pp.name), Some(pp.polarity))
def portToExpression(exp: Expression, polarity: Option[PortPolarity]): Expression =
polarity match {
case Some(ActiveLow) | Some(NegativeEdge) => not(exp)
case _ => exp
}
// Check if a number is a power of two
def isPowerOfTwo(x: Int): Boolean = (x & (x - 1)) == 0
}

View File

@@ -0,0 +1,26 @@
// See LICENSE for license details.
package barstools.tapeout.transforms
import firrtl.Mappers._
import firrtl._
import firrtl.annotations.{CircuitTarget, ModuleTarget, SingleTargetAnnotation}
import firrtl.ir._
import firrtl.stage.Forms
import firrtl.stage.TransformManager.TransformDependency
import firrtl.options.{Dependency}
class ExtraLowTransforms extends Transform with DependencyAPIMigration {
// this PropagatePresetAnnotations is needed to run the RemoveValidIf pass (that is removed from CIRCT).
// additionally, since that pass isn't explicitly a prereq of the LowFormEmitter it
// needs to wrapped in this xform
override def prerequisites: Seq[TransformDependency] = Forms.LowForm :+
Dependency[firrtl.transforms.PropagatePresetAnnotations]
override def optionalPrerequisites: Seq[TransformDependency] = Forms.LowFormOptimized
override def optionalPrerequisiteOf: Seq[TransformDependency] = Forms.LowEmitters
override def invalidates(a: Transform): Boolean = false
def execute(state: CircuitState): CircuitState = {
state
}
}

View File

@@ -0,0 +1,51 @@
package barstools.tapeout.transforms
import barstools.tapeout.transforms.stage._
import firrtl._
import firrtl.annotations._
import firrtl.ir._
import firrtl.options.{Dependency, InputAnnotationFileAnnotation, StageMain}
import firrtl.stage.{FirrtlCircuitAnnotation, FirrtlStage, RunFirrtlTransformAnnotation}
import logger.LazyLogging
private class GenerateModelStageMain(annotations: AnnotationSeq) extends LazyLogging {
val outAnno: Option[String] = annotations.collectFirst { case OutAnnoAnnotation(s) => s }
val annoFiles: List[String] = annotations.flatMap {
case InputAnnotationFileAnnotation(f) => Some(f)
case _ => None
}.toList
// Dump firrtl and annotation files
// Use global param outAnno
protected def dumpAnnos(
annotations: AnnotationSeq
): Unit = {
outAnno.foreach { annoPath =>
val outputFile = new java.io.PrintWriter(annoPath)
outputFile.write(JsonProtocol.serialize(annotations.filter(_ match {
case _: DeletedAnnotation => false
case _: EmittedComponent => false
case _: EmittedAnnotation[_] => false
case _: FirrtlCircuitAnnotation => false
case _: OutAnnoAnnotation => false
case _ => true
})))
outputFile.close()
}
}
def executeStageMain(): Unit = {
val annos = new FirrtlStage().execute(Array.empty, annotations)
annos.collectFirst { case FirrtlCircuitAnnotation(circuit) => circuit } match {
case Some(circuit) =>
dumpAnnos(annos)
case _ =>
throw new Exception(s"executeStageMain failed while executing FIRRTL!\n")
}
}
}
// main run class
object GenerateModelStageMain extends StageMain(new TapeoutStage())

View File

@@ -0,0 +1,48 @@
// See LICENSE for license details.
package barstools.tapeout.transforms.retime
import chisel3.experimental.RunFirrtlTransform
import firrtl.annotations._
import firrtl.stage.Forms
import firrtl.stage.TransformManager.TransformDependency
import firrtl.{CircuitState, DependencyAPIMigration, Transform}
case class RetimeAnnotation(target: Named) extends SingleTargetAnnotation[Named] {
override def duplicate(n: Named): Annotation = RetimeAnnotation(n)
}
class RetimeTransform extends Transform with DependencyAPIMigration {
override def prerequisites: Seq[TransformDependency] = Forms.LowForm
override def optionalPrerequisites: Seq[TransformDependency] = Forms.LowFormOptimized
override def optionalPrerequisiteOf: Seq[TransformDependency] = Forms.LowEmitters
override def invalidates(a: Transform): Boolean = false
override def execute(state: CircuitState): CircuitState = {
state.annotations.filter(_.isInstanceOf[RetimeAnnotation]) match {
case Nil => state
case seq =>
seq.foreach {
case RetimeAnnotation(ModuleName(module, CircuitName(_))) =>
logger.info(s"Retiming module $module")
case RetimeAnnotation(ComponentName(name, ModuleName(module, CircuitName(_)))) =>
logger.info(s"Retiming instance $module.$name")
case _ =>
throw new Exception(s"There should be RetimeAnnotations, got ${seq.mkString(" -- ")}")
}
state
}
}
}
trait RetimeLib {
self: chisel3.Module =>
def retime[T <: chisel3.Module](module: T): Unit = {
chisel3.experimental.annotate(new chisel3.experimental.ChiselAnnotation with RunFirrtlTransform {
def transformClass: Class[_ <: Transform] = classOf[RetimeTransform]
def toFirrtl: Annotation = RetimeAnnotation(module.toNamed)
})
}
}

View File

@@ -0,0 +1,50 @@
// See LICENSE for license details.
package barstools.tapeout.transforms.stage
import barstools.tapeout.transforms.GenerateModelStageMain
import chisel3.stage.ChiselCli
import firrtl.stage.{RunFirrtlTransformAnnotation}
import firrtl.AnnotationSeq
import firrtl.annotations.{Annotation, NoTargetAnnotation}
import firrtl.options.{HasShellOptions, Shell, ShellOption, Stage, Unserializable}
import firrtl.stage.FirrtlCli
import logger.Logger
sealed trait TapeoutOption extends Unserializable {
this: Annotation =>
}
case class OutAnnoAnnotation(outAnno: String) extends NoTargetAnnotation with TapeoutOption
object OutAnnoAnnotation extends HasShellOptions {
val options: Seq[ShellOption[_]] = Seq(
new ShellOption[String](
longOption = "out-anno-file",
shortOption = Some("oaf"),
toAnnotationSeq = (s: String) => Seq(OutAnnoAnnotation(s)),
helpText = "out-anno-file"
)
)
}
trait TapeoutCli {
this: Shell =>
parser.note("Tapeout specific options")
Seq(
OutAnnoAnnotation
).foreach(_.addOptions(parser))
}
class TapeoutStage() extends Stage {
override val shell: Shell = new Shell(applicationName = "tapeout") with TapeoutCli with ChiselCli with FirrtlCli
override def run(annotations: AnnotationSeq): AnnotationSeq = {
Logger.makeScope(annotations) {
val stageMain = new GenerateModelStageMain(annotations)
stageMain.executeStageMain()
}
annotations
}
}

View File

@@ -0,0 +1,79 @@
// See LICENSE for license details.
package barstools.tapeout.transforms.utils
import chisel3.experimental.{annotate, ChiselAnnotation}
import firrtl._
import firrtl.annotations._
import firrtl.stage.Forms
import firrtl.stage.TransformManager.TransformDependency
import firrtl.transforms.BlackBoxTargetDirAnno
object WriteConfig {
def apply(dir: String, file: String, contents: String): Unit = {
val writer = new java.io.PrintWriter(new java.io.File(s"$dir/$file"))
writer.write(contents)
writer.close()
}
}
object GetTargetDir {
def apply(state: CircuitState): String = {
val annos = state.annotations
val destDir = annos.map {
case BlackBoxTargetDirAnno(s) => Some(s)
case _ => None
}.flatten
val loc = {
if (destDir.isEmpty) "."
else destDir.head
}
val targetDir = new java.io.File(loc)
if (!targetDir.exists()) FileUtils.makeDirectory(targetDir.getAbsolutePath)
loc
}
}
trait HasSetTechnologyLocation {
self: chisel3.Module =>
def setTechnologyLocation(dir: String) {
annotate(new ChiselAnnotation {
override def toFirrtl: Annotation = {
TechnologyLocationAnnotation(dir)
}
})
}
}
case class TechnologyLocationAnnotation(dir: String) extends SingleTargetAnnotation[CircuitName] {
val target: CircuitName = CircuitName("All")
override def duplicate(n: CircuitName): Annotation = TechnologyLocationAnnotation(dir)
}
class TechnologyLocation extends Transform with DependencyAPIMigration {
override def prerequisites: Seq[TransformDependency] = Forms.LowForm
override def optionalPrerequisites: Seq[TransformDependency] = Forms.LowFormOptimized
override def optionalPrerequisiteOf: Seq[TransformDependency] = Forms.LowEmitters
def execute(state: CircuitState): CircuitState = {
throw new Exception("Technology Location transform execution doesn't work!")
}
def get(state: CircuitState): String = {
val annos = state.annotations
val dir = annos.flatMap {
case TechnologyLocationAnnotation(dir) => Some(dir)
case _ => None
}
dir.length match {
case 0 => ""
case 1 =>
val targetDir = new java.io.File(dir.head)
if (!targetDir.exists()) throw new Exception(s"Technology yaml directory $targetDir doesn't exist!")
dir.head
case _ => throw new Exception("Only 1 tech directory annotation allowed!")
}
}
}

View File

@@ -0,0 +1,5 @@
package barstools.tapeout.transforms.utils
object LowerName {
def apply(s: String): String = s.replace(".", "_").replace("[", "_").replace("]", "")
}

View File

@@ -0,0 +1,27 @@
package barstools.tapeout.transforms.utils
import chisel3._
import scala.collection.immutable.ListMap
class CustomBundle[T <: Data](elts: (String, T)*) extends Record {
val elements = ListMap(elts.map { case (field, elt) => field -> chiselTypeOf(elt) }: _*)
def apply(elt: String): T = elements(elt)
def apply(elt: Int): T = elements(elt.toString)
}
class CustomIndexedBundle[T <: Data](elts: (Int, T)*) extends Record {
// Must be String, Data
val elements = ListMap(elts.map { case (field, elt) => field.toString -> chiselTypeOf(elt) }: _*)
// TODO: Make an equivalent to the below work publicly (or only on subclasses?)
def indexedElements = ListMap(elts.map { case (field, elt) => field -> chiselTypeOf(elt) }: _*)
def apply(elt: Int): T = elements(elt.toString)
}
object CustomIndexedBundle {
def apply[T <: Data](gen: T, idxs: Seq[Int]) = new CustomIndexedBundle(idxs.map(_ -> gen): _*)
// Allows Vecs of elements of different types/widths
def apply[T <: Data](gen: Seq[T]) = new CustomIndexedBundle(gen.zipWithIndex.map { case (elt, field) =>
field -> elt
}: _*)
}

View File

@@ -0,0 +1,23 @@
package barstools.tapeout.transforms.utils
import firrtl.FileUtils
import net.jcazevedo.moultingyaml._
import java.io.File
class YamlFileReader(resource: String) {
def parse[A](file: String = "")(implicit reader: YamlReader[A]): Seq[A] = {
// If the user doesn't provide a Yaml file name, use defaults
val yamlString = file match {
case f if f.isEmpty =>
// Use example config if no file is provided
val stream = FileUtils.getTextResource(resource)
stream
case f if new File(f).exists =>
FileUtils.getText(f)
case _ =>
throw new Exception("No valid Yaml file found!")
}
yamlString.parseYamls.map(x => reader.read(x))
}
}

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).toSeq }.toSeq
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,96 @@
package mdf.macrolib
import play.api.libs.json._
import java.io.FileNotFoundException
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) =>
try {
Utils.readMDFFromString(scala.io.Source.fromFile(p).mkString)
} catch {
case f: FileNotFoundException =>
println(s"FILE NOT FOUND $p in dir ${os.pwd}")
throw f
}
}
}
// 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

@@ -0,0 +1,231 @@
module ExampleTopModuleWithBB_PadFrame(
output clock_Int,
output reset_Int,
output [14:0] io_a_Int,
output [14:0] io_b_Int,
output [13:0] io_c_Int,
input [15:0] io_x_Int,
input [15:0] io_y_Int,
input [15:0] io_z_Int,
input [4:0] io_v_0_Int,
input [4:0] io_v_1_Int,
input [4:0] io_v_2_Int,
input clock_Ext,
input reset_Ext,
input [14:0] io_a_Ext,
input [14:0] io_b_Ext,
input [13:0] io_c_Ext,
output [15:0] io_x_Ext,
output [15:0] io_y_Ext,
output [15:0] io_z_Ext,
inout [2:0] io_analog1_Ext,
inout [2:0] io_analog2_Ext,
output [4:0] io_v_0_Ext,
output [4:0] io_v_1_Ext,
output [4:0] io_v_2_Ext
);
wire pad_digital_from_tristate_foundry_vertical_input_array_reset_in;
wire pad_digital_from_tristate_foundry_vertical_input_array_reset_out;
wire [14:0] pad_digital_from_tristate_foundry_horizontal_input_array_io_a_in;
wire [14:0] pad_digital_from_tristate_foundry_horizontal_input_array_io_a_out;
wire [14:0] pad_digital_from_tristate_foundry_horizontal_input_array_io_b_in;
wire [14:0] pad_digital_from_tristate_foundry_horizontal_input_array_io_b_out;
wire [13:0] pad_digital_from_tristate_foundry_horizontal_input_array_io_c_in;
wire [13:0] pad_digital_from_tristate_foundry_horizontal_input_array_io_c_out;
wire [15:0] pad_digital_from_tristate_foundry_horizontal_output_array_io_x_in;
wire [15:0] pad_digital_from_tristate_foundry_horizontal_output_array_io_x_out;
wire [15:0] pad_digital_from_tristate_foundry_vertical_output_array_io_z_in;
wire [15:0] pad_digital_from_tristate_foundry_vertical_output_array_io_z_out;
wire [4:0] pad_digital_from_tristate_foundry_horizontal_output_array_io_v_0_in;
wire [4:0] pad_digital_from_tristate_foundry_horizontal_output_array_io_v_0_out;
wire [4:0] pad_digital_from_tristate_foundry_horizontal_output_array_io_v_1_in;
wire [4:0] pad_digital_from_tristate_foundry_horizontal_output_array_io_v_1_out;
wire [4:0] pad_digital_from_tristate_foundry_horizontal_output_array_io_v_2_in;
wire [4:0] pad_digital_from_tristate_foundry_horizontal_output_array_io_v_2_out;
pad_digital_from_tristate_foundry_vertical_input_array #(.WIDTH(1)) pad_digital_from_tristate_foundry_vertical_input_array_reset (
.in(pad_digital_from_tristate_foundry_vertical_input_array_reset_in),
.out(pad_digital_from_tristate_foundry_vertical_input_array_reset_out)
);
pad_digital_from_tristate_foundry_horizontal_input_array #(.WIDTH(15)) pad_digital_from_tristate_foundry_horizontal_input_array_io_a (
.in(pad_digital_from_tristate_foundry_horizontal_input_array_io_a_in),
.out(pad_digital_from_tristate_foundry_horizontal_input_array_io_a_out)
);
pad_digital_from_tristate_foundry_horizontal_input_array #(.WIDTH(15)) pad_digital_from_tristate_foundry_horizontal_input_array_io_b (
.in(pad_digital_from_tristate_foundry_horizontal_input_array_io_b_in),
.out(pad_digital_from_tristate_foundry_horizontal_input_array_io_b_out)
);
pad_digital_from_tristate_foundry_horizontal_input_array #(.WIDTH(14)) pad_digital_from_tristate_foundry_horizontal_input_array_io_c (
.in(pad_digital_from_tristate_foundry_horizontal_input_array_io_c_in),
.out(pad_digital_from_tristate_foundry_horizontal_input_array_io_c_out)
);
pad_digital_from_tristate_foundry_horizontal_output_array #(.WIDTH(16)) pad_digital_from_tristate_foundry_horizontal_output_array_io_x (
.in(pad_digital_from_tristate_foundry_horizontal_output_array_io_x_in),
.out(pad_digital_from_tristate_foundry_horizontal_output_array_io_x_out)
);
pad_digital_from_tristate_foundry_vertical_output_array #(.WIDTH(16)) pad_digital_from_tristate_foundry_vertical_output_array_io_z (
.in(pad_digital_from_tristate_foundry_vertical_output_array_io_z_in),
.out(pad_digital_from_tristate_foundry_vertical_output_array_io_z_out)
);
pad_analog_fast_custom_horizontal_array #(.WIDTH(3)) pad_analog_fast_custom_horizontal_array_io_analog1 (
.io(io_analog1_Ext)
);
pad_analog_slow_foundry_vertical_array #(.WIDTH(3)) pad_analog_slow_foundry_vertical_array_io_analog2 (
.io(io_analog2_Ext)
);
pad_digital_from_tristate_foundry_horizontal_output_array #(.WIDTH(5)) pad_digital_from_tristate_foundry_horizontal_output_array_io_v_0 (
.in(pad_digital_from_tristate_foundry_horizontal_output_array_io_v_0_in),
.out(pad_digital_from_tristate_foundry_horizontal_output_array_io_v_0_out)
);
pad_digital_from_tristate_foundry_horizontal_output_array #(.WIDTH(5)) pad_digital_from_tristate_foundry_horizontal_output_array_io_v_1 (
.in(pad_digital_from_tristate_foundry_horizontal_output_array_io_v_1_in),
.out(pad_digital_from_tristate_foundry_horizontal_output_array_io_v_1_out)
);
pad_digital_from_tristate_foundry_horizontal_output_array #(.WIDTH(5)) pad_digital_from_tristate_foundry_horizontal_output_array_io_v_2 (
.in(pad_digital_from_tristate_foundry_horizontal_output_array_io_v_2_in),
.out(pad_digital_from_tristate_foundry_horizontal_output_array_io_v_2_out)
);
pad_supply_vdd_horizontal pad_supply_vdd_horizontal_left_0 (
);
pad_supply_vdd_horizontal pad_supply_vdd_horizontal_left_1 (
);
pad_supply_vdd_horizontal pad_supply_vdd_horizontal_left_2 (
);
pad_supply_vdd_vertical pad_supply_vdd_vertical_bottom_0 (
);
pad_supply_vdd_vertical pad_supply_vdd_vertical_bottom_1 (
);
pad_supply_vss_horizontal pad_supply_vss_horizontal_right_0 (
);
assign clock_Int = clock_Ext;
assign reset_Int = pad_digital_from_tristate_foundry_vertical_input_array_reset_out;
assign io_a_Int = pad_digital_from_tristate_foundry_horizontal_input_array_io_a_out;
assign io_b_Int = pad_digital_from_tristate_foundry_horizontal_input_array_io_b_out;
assign io_c_Int = $signed(pad_digital_from_tristate_foundry_horizontal_input_array_io_c_out);
assign io_x_Ext = pad_digital_from_tristate_foundry_horizontal_output_array_io_x_out;
assign io_y_Ext = io_y_Int;
assign io_z_Ext = $signed(pad_digital_from_tristate_foundry_vertical_output_array_io_z_out);
assign io_v_0_Ext = pad_digital_from_tristate_foundry_horizontal_output_array_io_v_0_out;
assign io_v_1_Ext = pad_digital_from_tristate_foundry_horizontal_output_array_io_v_1_out;
assign io_v_2_Ext = pad_digital_from_tristate_foundry_horizontal_output_array_io_v_2_out;
assign pad_digital_from_tristate_foundry_vertical_input_array_reset_in = reset_Ext;
assign pad_digital_from_tristate_foundry_horizontal_input_array_io_a_in = io_a_Ext;
assign pad_digital_from_tristate_foundry_horizontal_input_array_io_b_in = io_b_Ext;
assign pad_digital_from_tristate_foundry_horizontal_input_array_io_c_in = $unsigned(io_c_Ext);
assign pad_digital_from_tristate_foundry_horizontal_output_array_io_x_in = io_x_Int;
assign pad_digital_from_tristate_foundry_vertical_output_array_io_z_in = $unsigned(io_z_Int);
assign pad_digital_from_tristate_foundry_horizontal_output_array_io_v_0_in = io_v_0_Int;
assign pad_digital_from_tristate_foundry_horizontal_output_array_io_v_1_in = io_v_1_Int;
assign pad_digital_from_tristate_foundry_horizontal_output_array_io_v_2_in = io_v_2_Int;
endmodule
module ExampleTopModuleWithBB(
input clock,
input reset,
input [14:0] io_a,
input [14:0] io_b,
input [13:0] io_c,
output [15:0] io_x,
output [15:0] io_y,
output [15:0] io_z,
inout [2:0] io_analog1,
inout [2:0] io_analog2,
output [4:0] io_v_0,
output [4:0] io_v_1,
output [4:0] io_v_2
);
wire ExampleTopModuleWithBB_PadFrame_clock_Int;
wire ExampleTopModuleWithBB_PadFrame_reset_Int;
wire [14:0] ExampleTopModuleWithBB_PadFrame_io_a_Int;
wire [14:0] ExampleTopModuleWithBB_PadFrame_io_b_Int;
wire [13:0] ExampleTopModuleWithBB_PadFrame_io_c_Int;
wire [15:0] ExampleTopModuleWithBB_PadFrame_io_x_Int;
wire [15:0] ExampleTopModuleWithBB_PadFrame_io_y_Int;
wire [15:0] ExampleTopModuleWithBB_PadFrame_io_z_Int;
wire [4:0] ExampleTopModuleWithBB_PadFrame_io_v_0_Int;
wire [4:0] ExampleTopModuleWithBB_PadFrame_io_v_1_Int;
wire [4:0] ExampleTopModuleWithBB_PadFrame_io_v_2_Int;
wire ExampleTopModuleWithBB_PadFrame_clock_Ext;
wire ExampleTopModuleWithBB_PadFrame_reset_Ext;
wire [14:0] ExampleTopModuleWithBB_PadFrame_io_a_Ext;
wire [14:0] ExampleTopModuleWithBB_PadFrame_io_b_Ext;
wire [13:0] ExampleTopModuleWithBB_PadFrame_io_c_Ext;
wire [15:0] ExampleTopModuleWithBB_PadFrame_io_x_Ext;
wire [15:0] ExampleTopModuleWithBB_PadFrame_io_y_Ext;
wire [15:0] ExampleTopModuleWithBB_PadFrame_io_z_Ext;
wire [4:0] ExampleTopModuleWithBB_PadFrame_io_v_0_Ext;
wire [4:0] ExampleTopModuleWithBB_PadFrame_io_v_1_Ext;
wire [4:0] ExampleTopModuleWithBB_PadFrame_io_v_2_Ext;
wire ExampleTopModuleWithBB_Internal_clock;
wire ExampleTopModuleWithBB_Internal_reset;
wire [14:0] ExampleTopModuleWithBB_Internal_io_a;
wire [14:0] ExampleTopModuleWithBB_Internal_io_b;
wire [13:0] ExampleTopModuleWithBB_Internal_io_c;
wire [15:0] ExampleTopModuleWithBB_Internal_io_x;
wire [15:0] ExampleTopModuleWithBB_Internal_io_y;
wire [15:0] ExampleTopModuleWithBB_Internal_io_z;
wire [4:0] ExampleTopModuleWithBB_Internal_io_v_0;
wire [4:0] ExampleTopModuleWithBB_Internal_io_v_1;
wire [4:0] ExampleTopModuleWithBB_Internal_io_v_2;
ExampleTopModuleWithBB_PadFrame ExampleTopModuleWithBB_PadFrame (
.clock_Int(ExampleTopModuleWithBB_PadFrame_clock_Int),
.reset_Int(ExampleTopModuleWithBB_PadFrame_reset_Int),
.io_a_Int(ExampleTopModuleWithBB_PadFrame_io_a_Int),
.io_b_Int(ExampleTopModuleWithBB_PadFrame_io_b_Int),
.io_c_Int(ExampleTopModuleWithBB_PadFrame_io_c_Int),
.io_x_Int(ExampleTopModuleWithBB_PadFrame_io_x_Int),
.io_y_Int(ExampleTopModuleWithBB_PadFrame_io_y_Int),
.io_z_Int(ExampleTopModuleWithBB_PadFrame_io_z_Int),
.io_v_0_Int(ExampleTopModuleWithBB_PadFrame_io_v_0_Int),
.io_v_1_Int(ExampleTopModuleWithBB_PadFrame_io_v_1_Int),
.io_v_2_Int(ExampleTopModuleWithBB_PadFrame_io_v_2_Int),
.clock_Ext(ExampleTopModuleWithBB_PadFrame_clock_Ext),
.reset_Ext(ExampleTopModuleWithBB_PadFrame_reset_Ext),
.io_a_Ext(ExampleTopModuleWithBB_PadFrame_io_a_Ext),
.io_b_Ext(ExampleTopModuleWithBB_PadFrame_io_b_Ext),
.io_c_Ext(ExampleTopModuleWithBB_PadFrame_io_c_Ext),
.io_x_Ext(ExampleTopModuleWithBB_PadFrame_io_x_Ext),
.io_y_Ext(ExampleTopModuleWithBB_PadFrame_io_y_Ext),
.io_z_Ext(ExampleTopModuleWithBB_PadFrame_io_z_Ext),
.io_analog1_Ext(io_analog1),
.io_analog2_Ext(io_analog2),
.io_v_0_Ext(ExampleTopModuleWithBB_PadFrame_io_v_0_Ext),
.io_v_1_Ext(ExampleTopModuleWithBB_PadFrame_io_v_1_Ext),
.io_v_2_Ext(ExampleTopModuleWithBB_PadFrame_io_v_2_Ext)
);
ExampleTopModuleWithBB_Internal ExampleTopModuleWithBB_Internal (
.clock(ExampleTopModuleWithBB_Internal_clock),
.reset(ExampleTopModuleWithBB_Internal_reset),
.io_a(ExampleTopModuleWithBB_Internal_io_a),
.io_b(ExampleTopModuleWithBB_Internal_io_b),
.io_c(ExampleTopModuleWithBB_Internal_io_c),
.io_x(ExampleTopModuleWithBB_Internal_io_x),
.io_y(ExampleTopModuleWithBB_Internal_io_y),
.io_z(ExampleTopModuleWithBB_Internal_io_z),
.io_analog1(io_analog1),
.io_analog2(io_analog2),
.io_v_0(ExampleTopModuleWithBB_Internal_io_v_0),
.io_v_1(ExampleTopModuleWithBB_Internal_io_v_1),
.io_v_2(ExampleTopModuleWithBB_Internal_io_v_2)
);
assign io_x = ExampleTopModuleWithBB_PadFrame_io_x_Ext;
assign io_y = ExampleTopModuleWithBB_PadFrame_io_y_Ext;
assign io_z = ExampleTopModuleWithBB_PadFrame_io_z_Ext;
assign io_v_0 = ExampleTopModuleWithBB_PadFrame_io_v_0_Ext;
assign io_v_1 = ExampleTopModuleWithBB_PadFrame_io_v_1_Ext;
assign io_v_2 = ExampleTopModuleWithBB_PadFrame_io_v_2_Ext;
assign ExampleTopModuleWithBB_PadFrame_io_x_Int = ExampleTopModuleWithBB_Internal_io_x;
assign ExampleTopModuleWithBB_PadFrame_io_y_Int = ExampleTopModuleWithBB_Internal_io_y;
assign ExampleTopModuleWithBB_PadFrame_io_z_Int = ExampleTopModuleWithBB_Internal_io_z;
assign ExampleTopModuleWithBB_PadFrame_io_v_0_Int = ExampleTopModuleWithBB_Internal_io_v_0;
assign ExampleTopModuleWithBB_PadFrame_io_v_1_Int = ExampleTopModuleWithBB_Internal_io_v_1;
assign ExampleTopModuleWithBB_PadFrame_io_v_2_Int = ExampleTopModuleWithBB_Internal_io_v_2;
assign ExampleTopModuleWithBB_PadFrame_clock_Ext = clock;
assign ExampleTopModuleWithBB_PadFrame_reset_Ext = reset;
assign ExampleTopModuleWithBB_PadFrame_io_a_Ext = io_a;
assign ExampleTopModuleWithBB_PadFrame_io_b_Ext = io_b;
assign ExampleTopModuleWithBB_PadFrame_io_c_Ext = io_c;
assign ExampleTopModuleWithBB_Internal_clock = ExampleTopModuleWithBB_PadFrame_clock_Int;
assign ExampleTopModuleWithBB_Internal_reset = ExampleTopModuleWithBB_PadFrame_reset_Int;
assign ExampleTopModuleWithBB_Internal_io_a = ExampleTopModuleWithBB_PadFrame_io_a_Int;
assign ExampleTopModuleWithBB_Internal_io_b = ExampleTopModuleWithBB_PadFrame_io_b_Int;
assign ExampleTopModuleWithBB_Internal_io_c = ExampleTopModuleWithBB_PadFrame_io_c_Int;
endmodule

View File

@@ -0,0 +1,41 @@
[
{
"name" : "example",
"type" : "flipchip",
"bump_dimensions" : [27,27],
"bump_locations" : [
["-", "GND", "VDDC0_SEL[0]", "VDDC0_SEL[1]", "VDDC1_SEL[0]", "VDDC1_SEL[1]", "VDDC2_SEL[0]", "VDDC2_SEL[1]", "VDDC3_SEL[0]", "VDDC3_SEL[1]", "VDDC0_EN", "VDDC1_EN", "VDDC2_EN", "VDDC3_EN", "CCLK0", "CCLK1", "CCLK2", "RESET", "BOOT", "I2C_SDA", "I2C_SCL", "SPI_SCLK", "SPI_MOSI", "SPI_MISO", "SPI_SS_L", "GND", "-"],
[ "GND", "", "", "", "GND", "GND","GPIO[1]", "VDD1V8", "VDD1V8", "VDD1V8", "VDD1V8", "VDD1V8", "VDD1V8", "VDD1V8", "VDD1V8", "VDD1V8", "VDD1V8","UART_RX","UART_TX", "GND", "GND", "GND", "GND", "", "", "", "GND"],
["TXP0", "VDDA", "VDDA", "GND", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "GND", "VDDA", "VDDA", "TXP4"],
["TXN0", "VDDA", "VDDA", "GND", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "GND", "VDDA", "VDDA", "TXN4"],
[ "GND", "", "", "", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "", "", "", "GND"],
["RXP0", "VDDA", "VDDA", "GND", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "GND", "VDDA", "VDDA", "RXP4"],
["RXN0", "VDDA", "VDDA", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "VDDA", "VDDA", "RXN4"],
[ "GND", "", "", "", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "", "", "", "GND"],
["TXP1", "VDDA", "VDDA", "GND", "VDDC1", "VDDC1", "VDDC1", "VDDC1", "VDDC1", "VDDC1", "VDDC1", "VDDC1", "VDDC1", "VDDC0", "VDDC0", "VDDC0", "VDDC0", "VDDC0", "VDDC0", "VDDC0", "VDDC0", "VDDC0", "VDDC0", "GND", "VDDA", "VDDA", "TXP5"],
["TXN1", "VDDA", "VDDA", "GND", "VDDC1", "VDDC1", "VDDC1", "VDDC1", "VDDC1", "VDDC1", "VDDC1", "VDDC1", "VDDC1", "VDDC0", "VDDC0", "VDDC0", "VDDC0", "VDDC0", "VDDC0", "VDDC0", "VDDC0", "VDDC0", "VDDC0", "GND", "VDDA", "VDDA", "TXN5"],
[ "GND", "", "", "", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "", "", "", "GND"],
["RXP1", "VDDA", "VDDA", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "VDDA", "VDDA", "RXP5"],
["RXN1", "VDDA", "VDDA", "GND", "VDDC2", "VDDC2", "VDDC2", "VDDC2", "VDDC1", "VDDC1", "VDDC1", "VDDC1", "VDDC1", "GND", "GND", "VDDC3", "VDDC3", "VDDC3", "VDDC3", "VDDC0", "VDDC0", "VDDC0", "VDDC0", "GND", "VDDA", "VDDA", "RXN5"],
[ "GND", "", "", "", "VDDC2", "VDDC2", "VDDC2", "VDDC2", "VDDC1", "VDDC1", "VDDC1", "VDDC1", "VDDC1", "GND", "GND", "VDDC3", "VDDC3", "VDDC3", "VDDC3", "VDDC0", "VDDC0", "VDDC0", "VDDC0", "", "", "", "GND"],
["TXP2", "VDDA", "VDDA", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "VDDA", "VDDA", "TXP6"],
["TXN2", "VDDA", "VDDA", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "VDDA", "VDDA", "TXN6"],
[ "GND", "", "", "", "VDDC2", "VDDC2", "VDDC2", "VDDC2", "VDDC2", "VDDC2", "VDDC2", "VDDC2", "VDDC2", "VDDC3", "VDDC3", "VDDC3", "VDDC3", "VDDC3", "VDDC3", "VDDC3", "VDDC3", "VDDC3", "VDDC3", "", "", "", "GND"],
["RXP2", "VDDA", "VDDA", "GND", "VDDC2", "VDDC2", "VDDC2", "VDDC2", "VDDC2", "VDDC2", "VDDC2", "VDDC2", "VDDC2", "VDDC3", "VDDC3", "VDDC3", "VDDC3", "VDDC3", "VDDC3", "VDDC3", "VDDC3", "VDDC3", "VDDC3", "GND", "VDDA", "VDDA", "RXP6"],
["RXN2", "VDDA", "VDDA", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "VDDA", "VDDA", "RXN6"],
[ "GND", "", "", "", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "GND", "", "", "", "GND"],
["TXP3", "VDDA", "VDDA", "GND", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "GND", "VDDA", "VDDA", "TXP7"],
["TXN3", "VDDA", "VDDA", "GND", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "VDD0V8", "GND", "VDDA", "VDDA", "TXN7"],
[ "GND", "", "", "", "-", "SERIAL_IN_READY", "-", "-", "SERIAL_IN_VALID", "-", "-", "-", "-", "", "-", "SERIAL_OUT_VALID", "-", "-", "SERIAL_OUT_READY", "-", "-", "GPIO[0]", "-", "", "", "", "GND"],
["RXP3", "VDDA", "VDDA", "GND", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "GND", "VDDA", "VDDA", "RXP7"],
["RXN3", "VDDA", "VDDA", "GND", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "GND", "VDDA", "VDDA", "RXN7"],
[ "GND", "", "", "", "GND", "GND", "GND", "GND", "GND", "VDD1V8", "VDD1V8", "VDD1V8", "VDD1V8", "VDD1V8", "VDD1V8", "VDD1V8", "VDD1V8", "VDD1V8", "GND", "GND", "GND", "GND", "GND", "", "", "", "GND"],
["-", "GND", "REFCLK0P", "REFCLK0N", "GND", "SERIAL_OUT[0]", "SERIAL_OUT[1]", "SERIAL_OUT[2]", "SERIAL_OUT[3]", "SERIAL_IN[0]", "SERIAL_IN[1]", "SERIAL_IN[2]", "SERIAL_IN[3]", "JTAG_TMS", "JTAG_TCK", "JTAG_TDO", "JTAG_TDI", "CLKSEL", "PLLCLK_OUT", "GND", "PLLREFCLKP", "PLLREFCLKN", "GND", "REFCLK1P", "REFCLK1N", "GND", "-"]
]
}
]

View File

@@ -0,0 +1,663 @@
[
{
"name": "My IOs",
"type": "io_properties",
"top": "EAGLE",
"ios": [
{
"name": "GND",
"type": "ground"
},
{
"name": "VDD0V8",
"type": "power"
},
{
"name": "VDD1V8",
"type": "power"
},
{
"name": "VDDC0",
"type": "power"
},
{
"name": "VDDC1",
"type": "power"
},
{
"name": "VDDC2",
"type": "power"
},
{
"name": "VDDC3",
"type": "power"
},
{
"name": "VDDA",
"type": "power"
},
{
"name": "VDDC0_SEL[1:0]",
"type": "digital",
"direction": "output",
"termination": "CMOS"
},
{
"name": "VDDC1_SEL[1:0]",
"type": "digital",
"direction": "output",
"termination": "CMOS"
},
{
"name": "VDDC2_SEL[1:0]",
"type": "digital",
"direction": "output",
"termination": "CMOS"
},
{
"name": "VDDC3_SEL[1:0]",
"type": "digital",
"direction": "output",
"termination": "CMOS"
},
{
"name": "VDDDC0_EN",
"type": "digital",
"direction": "output",
"termination": "CMOS"
},
{
"name": "VDDDC1_EN",
"type": "digital",
"direction": "output",
"termination": "CMOS"
},
{
"name": "VDDDC2_EN",
"type": "digital",
"direction": "output",
"termination": "CMOS"
},
{
"name": "VDDDC3_EN",
"type": "digital",
"direction": "output",
"termination": "CMOS"
},
{
"name": "CCLK0",
"type": "digital",
"direction": "input",
"termination": 50,
"termination_type": "single",
"termination_reference": "GND"
},
{
"name": "CCLK1",
"type": "digital",
"direction": "input",
"termination": 50,
"termination_type": "single",
"termination_reference": "GND"
},
{
"name": "CCLK2",
"type": "digital",
"direction": "input",
"termination": 50,
"termination_type": "single",
"termination_reference": "GND"
},
{
"name": "RESET",
"type": "digital",
"direction": "input",
"termination": "CMOS"
},
{
"name": "BOOT",
"type": "digital",
"direction": "input",
"termination": "CMOS"
},
{
"name": "I2C_SDA",
"type": "digital",
"direction": "inout",
"termination": "open-drain"
},
{
"name": "I2C_SCL",
"type": "digital",
"direction": "inout",
"termination": "open-drain"
},
{
"name": "SPI_SCLK",
"type": "digital",
"direction": "output",
"termination": "CMOS"
},
{
"name": "SPI_MOSI",
"type": "digital",
"direction": "output",
"termination": "CMOS"
},
{
"name": "SPI_MISO",
"type": "digital",
"direction": "input",
"termination": "CMOS"
},
{
"name": "SPI_SS_L",
"type": "digital",
"direction": "output",
"termination": "CMOS"
},
{
"name": "GPIO[1:0]",
"type": "digital",
"direction": "inout",
"termination": "CMOS"
},
{
"name": "UART_RX",
"type": "digital",
"direction": "input",
"termination": "CMOS"
},
{
"name": "UART_TX",
"type": "digital",
"direction": "output",
"termination": "CMOS"
},
{
"name": "SERIAL_IN_READY",
"type": "digital",
"direction": "output",
"termination": "CMOS"
},
{
"name": "SERIAL_IN_VALID",
"type": "digital",
"direction": "input",
"termination": "CMOS"
},
{
"name": "SERIAL_OUT_READY",
"type": "digital",
"direction": "input",
"termination": "CMOS"
},
{
"name": "SERIAL_OUT_VALID",
"type": "digital",
"direction": "output",
"termination": "CMOS"
},
{
"name": "SERIAL_OUT[3:0]",
"type": "digital",
"direction": "output",
"termination": "CMOS"
},
{
"name": "SERIAL_IN[3:0]",
"type": "digital",
"direction": "input",
"termination": "CMOS"
},
{
"name": "REFCLK0P",
"type": "analog",
"direction": "input",
"match": [
"REFCLK0N"
],
"termination": 100,
"termination_type": "differential",
"termination_reference": "GND"
},
{
"name": "REFCLK0N",
"type": "analog",
"direction": "input",
"match": [
"REFCLK0P"
],
"termination": 100,
"termination_type": "differential",
"termination_reference": "GND"
},
{
"name": "REFCLK1N",
"type": "analog",
"direction": "input",
"match": [
"REFCLK1P"
],
"termination": 100,
"termination_type": "differential",
"termination_reference": "GND"
},
{
"name": "REFCLK1P",
"type": "analog",
"direction": "input",
"match": [
"REFCLK1N"
],
"termination": 100,
"termination_type": "differential",
"termination_reference": "GND"
},
{
"name": "PLLREFCLKP",
"type": "analog",
"direction": "input",
"match": [
"PLLREFCLKP"
],
"termination": 100,
"termination_type": "differential",
"termination_reference": "GND"
},
{
"name": "PLLREFCLKN",
"type": "analog",
"direction": "input",
"match": [
"PLLREFCLKP"
],
"termination": 100,
"termination_type": "differential",
"termination_reference": "GND"
},
{
"name": "JTAG_TMS",
"type": "digital",
"direction": "input",
"termination": "CMOS"
},
{
"name": "JTAG_TCK",
"type": "digital",
"direction": "input",
"termination": "CMOS"
},
{
"name": "JTAG_TDI",
"type": "digital",
"direction": "input",
"termination": "CMOS"
},
{
"name": "JTAG_TDO",
"type": "digital",
"direction": "output",
"termination": "CMOS"
},
{
"name": "PLLCLK_OUT",
"type": "digital",
"direction": "output",
"termination": "CMOS"
},
{
"name": "TXP0",
"type": "analog",
"direction": "output",
"match": [
"TXN0"
],
"termination": 100,
"termination_type": "differential",
"termination_reference": "GND"
},
{
"name": "TXN0",
"type": "analog",
"direction": "output",
"match": [
"TXP0"
],
"termination": 100,
"termination_type": "differential",
"termination_reference": "GND"
},
{
"name": "RXP0",
"type": "analog",
"direction": "output",
"match": [
"RXN0"
],
"termination": 100,
"termination_type": "differential",
"termination_reference": "GND"
},
{
"name": "RXN1",
"type": "analog",
"direction": "input",
"match": [
"RXP1"
],
"termination": 100,
"termination_type": "differential",
"termination_reference": "GND"
},
{
"name": "TXP1",
"type": "analog",
"direction": "output",
"match": [
"TXN1"
],
"termination": 100,
"termination_type": "differential",
"termination_reference": "GND"
},
{
"name": "TXN1",
"type": "analog",
"direction": "output",
"match": [
"TXP1"
],
"termination": 100,
"termination_type": "differential",
"termination_reference": "GND"
},
{
"name": "RXP1",
"type": "analog",
"direction": "output",
"match": [
"RXN1"
],
"termination": 100,
"termination_type": "differential",
"termination_reference": "GND"
},
{
"name": "RXN1",
"type": "analog",
"direction": "input",
"match": [
"RXP1"
],
"termination": 100,
"termination_type": "differential",
"termination_reference": "GND"
},
{
"name": "TXP2",
"type": "analog",
"direction": "output",
"match": [
"TXN2"
],
"termination": 100,
"termination_type": "differential",
"termination_reference": "GND"
},
{
"name": "TXN2",
"type": "analog",
"direction": "output",
"match": [
"TXP2"
],
"termination": 100,
"termination_type": "differential",
"termination_reference": "GND"
},
{
"name": "RXP2",
"type": "analog",
"direction": "output",
"match": [
"RXN2"
],
"termination": 100,
"termination_type": "differential",
"termination_reference": "GND"
},
{
"name": "RXN2",
"type": "analog",
"direction": "input",
"match": [
"RXP2"
],
"termination": 100,
"termination_type": "differential",
"termination_reference": "GND"
},
{
"name": "TXP3",
"type": "analog",
"direction": "output",
"match": [
"TXN3"
],
"termination": 100,
"termination_type": "differential",
"termination_reference": "GND"
},
{
"name": "TXN3",
"type": "analog",
"direction": "output",
"match": [
"TXP3"
],
"termination": 100,
"termination_type": "differential",
"termination_reference": "GND"
},
{
"name": "RXP3",
"type": "analog",
"direction": "output",
"match": [
"RXN3"
],
"termination": 100,
"termination_type": "differential",
"termination_reference": "GND"
},
{
"name": "RXN3",
"type": "analog",
"direction": "input",
"match": [
"RXP3"
],
"termination": 100,
"termination_type": "differential",
"termination_reference": "GND"
},
{
"name": "TXP4",
"type": "analog",
"direction": "output",
"match": [
"TXN4"
],
"termination": 100,
"termination_type": "differential",
"termination_reference": "GND"
},
{
"name": "TXN4",
"type": "analog",
"direction": "output",
"match": [
"TXP4"
],
"termination": 100,
"termination_type": "differential",
"termination_reference": "GND"
},
{
"name": "RXP4",
"type": "analog",
"direction": "output",
"match": [
"RXN4"
],
"termination": 100,
"termination_type": "differential",
"termination_reference": "GND"
},
{
"name": "RXN4",
"type": "analog",
"direction": "input",
"match": [
"RXP4"
],
"termination": 100,
"termination_type": "differential",
"termination_reference": "GND"
},
{
"name": "TXP5",
"type": "analog",
"direction": "output",
"match": [
"TXN5"
],
"termination": 100,
"termination_type": "differential",
"termination_reference": "GND"
},
{
"name": "TXN5",
"type": "analog",
"direction": "output",
"match": [
"TXP5"
],
"termination": 100,
"termination_type": "differential",
"termination_reference": "GND"
},
{
"name": "RXP5",
"type": "analog",
"direction": "output",
"match": [
"RXN5"
],
"termination": 100,
"termination_type": "differential",
"termination_reference": "GND"
},
{
"name": "RXN5",
"type": "analog",
"direction": "input",
"match": [
"RXP5"
],
"termination": 100,
"termination_type": "differential",
"termination_reference": "GND"
},
{
"name": "TXP6",
"type": "analog",
"direction": "output",
"match": [
"TXN6"
],
"termination": 100,
"termination_type": "differential",
"termination_reference": "GND"
},
{
"name": "TXN6",
"type": "analog",
"direction": "output",
"match": [
"TXP6"
],
"termination": 100,
"termination_type": "differential",
"termination_reference": "GND"
},
{
"name": "RXP6",
"type": "analog",
"direction": "output",
"match": [
"RXN6"
],
"termination": 100,
"termination_type": "differential",
"termination_reference": "GND"
},
{
"name": "RXN6",
"type": "analog",
"direction": "input",
"match": [
"RXP6"
],
"termination": 100,
"termination_type": "differential",
"termination_reference": "GND"
},
{
"name": "TXP7",
"type": "analog",
"direction": "output",
"match": [
"TXN7"
],
"termination": 100,
"termination_type": "differential",
"termination_reference": "GND"
},
{
"name": "TXN7",
"type": "analog",
"direction": "output",
"match": [
"TXP7"
],
"termination": 100,
"termination_type": "differential",
"termination_reference": "GND"
},
{
"name": "RXP7",
"type": "analog",
"direction": "output",
"match": [
"RXN7"
],
"termination": 100,
"termination_type": "differential",
"termination_reference": "GND"
},
{
"name": "RXN7",
"type": "analog",
"direction": "input",
"match": [
"RXP7"
],
"termination": 100,
"termination_type": "differential",
"termination_reference": "GND"
}
]
}
]

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,29 @@
[
{
"type": "sram",
"name": "fake_mem",
"width": 64,
"depth": "512",
"mux": 4,
"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": "wen",
"write enable port polarity": "active high",
"read enable port name": "ren",
"read enable port polarity": "active high",
"output port name": "dataout",
"output port polarity": "active high",
"input port name": "datain",
"input port polarity": "active high",
"mask port name": "mport",
"mask port polarity": "active low",
"mask granularity": 1
}
]
}
]

View File

@@ -0,0 +1,26 @@
[
{
"type": "sram",
"name": "fake_mem",
"width": 64,
"depth": "4096",
"mux": 4,
"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": "wen",
"write enable port polarity": "active high",
"read enable port name": "ren",
"read enable port polarity": "active high",
"output port name": "dataout",
"output port polarity": "active high",
"input port name": "datain",
"input port polarity": "active high"
}
]
}
]

View File

@@ -0,0 +1,114 @@
package barstools.macros
import mdf.macrolib.SRAMMacro
/** Tests to check that the cost function mechanism is working properly. */
/** A test metric that simply favours memories with smaller widths, to test that
* the metric is chosen properly.
*/
object TestMinWidthMetric extends CostMetric with CostMetricCompanion {
// Smaller width = lower cost = favoured
override def cost(mem: Macro, lib: Macro): Option[Double] = Some(lib.src.width)
override def commandLineParams() = Map()
override def name() = "TestMinWidthMetric"
override def construct(m: Map[String, String]): CostMetric = TestMinWidthMetric
}
/** Test that cost metric selection is working. */
class SelectCostMetric extends MacroCompilerSpec with HasSRAMGenerator {
val mem = s"mem-SelectCostMetric.json"
val lib = s"lib-SelectCostMetric.json"
val v = s"SelectCostMetric.v"
// Cost metrics must be registered for them to work with the command line.
CostMetric.registerCostMetric(TestMinWidthMetric)
override val costMetric: Option[CostMetric] = Some(TestMinWidthMetric)
val libSRAMs = Seq(
SRAMMacro(
name = "SRAM_WIDTH_128",
depth = BigInt(1024),
width = 128,
family = "1rw",
ports = Seq(
generateReadWritePort("", 128, BigInt(1024))
)
),
SRAMMacro(
name = "SRAM_WIDTH_64",
depth = BigInt(1024),
width = 64,
family = "1rw",
ports = Seq(
generateReadWritePort("", 64, BigInt(1024))
)
),
SRAMMacro(
name = "SRAM_WIDTH_32",
depth = BigInt(1024),
width = 32,
family = "1rw",
ports = Seq(
generateReadWritePort("", 32, BigInt(1024))
)
)
)
val memSRAMs = Seq(generateSRAM("target_memory", "", 128, BigInt(1024)))
writeToLib(lib, libSRAMs)
writeToMem(mem, memSRAMs)
// Check that the min width SRAM was chosen, even though it is less efficient.
val output =
"""
circuit target_memory :
module target_memory :
input addr : UInt<10>
input clk : Clock
input din : UInt<128>
output dout : UInt<128>
input write_en : UInt<1>
inst mem_0_0 of SRAM_WIDTH_32
inst mem_0_1 of SRAM_WIDTH_32
inst mem_0_2 of SRAM_WIDTH_32
inst mem_0_3 of SRAM_WIDTH_32
mem_0_0.clk <= clk
mem_0_0.addr <= addr
node dout_0_0 = bits(mem_0_0.dout, 31, 0)
mem_0_0.din <= bits(din, 31, 0)
mem_0_0.write_en <= and(and(and(write_en, UInt<1>("h1")), UInt<1>("h1")), UInt<1>("h1"))
mem_0_1.clk <= clk
mem_0_1.addr <= addr
node dout_0_1 = bits(mem_0_1.dout, 31, 0)
mem_0_1.din <= bits(din, 63, 32)
mem_0_1.write_en <= and(and(and(write_en, UInt<1>("h1")), UInt<1>("h1")), UInt<1>("h1"))
mem_0_2.clk <= clk
mem_0_2.addr <= addr
node dout_0_2 = bits(mem_0_2.dout, 31, 0)
mem_0_2.din <= bits(din, 95, 64)
mem_0_2.write_en <= and(and(and(write_en, UInt<1>("h1")), UInt<1>("h1")), UInt<1>("h1"))
mem_0_3.clk <= clk
mem_0_3.addr <= addr
node dout_0_3 = bits(mem_0_3.dout, 31, 0)
mem_0_3.din <= bits(din, 127, 96)
mem_0_3.write_en <= and(and(and(write_en, UInt<1>("h1")), UInt<1>("h1")), UInt<1>("h1"))
node dout_0 = cat(dout_0_3, cat(dout_0_2, cat(dout_0_1, dout_0_0)))
dout <= mux(UInt<1>("h1"), dout_0, UInt<128>("h0"))
extmodule SRAM_WIDTH_32 :
input addr : UInt<10>
input clk : Clock
input din : UInt<32>
output dout : UInt<32>
input write_en : UInt<1>
defname = SRAM_WIDTH_32
"""
compileExecuteAndTest(mem, lib, v, output)
}

View File

@@ -0,0 +1,120 @@
package barstools.macros
// import firrtl.ir.Circuit
// import firrtl_interpreter.InterpretiveTester
// // Functional tests on memory compiler outputs.
// // Synchronous write and read back.
// class SynchronousReadAndWrite extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleDepthTestGenerator {
// override lazy val width = 12
// override lazy val memDepth = BigInt(2048)
// override lazy val libDepth = BigInt(1024)
// compile(mem, lib, v, synflops = true)
// val result: Circuit = execute(mem, lib, synflops = true)
// it should "run with InterpretedTester" in {
// pending // Enable this when https://github.com/freechipsproject/firrtl-interpreter/pull/88 is snapshot-published
// val addr1 = 0
// val addr1val = 0xff
// val addr2 = 42
// val addr2val = 0xf0
// val addr3 = 1 << 10
// val addr3val = 1 << 10
// val tester = new InterpretiveTester(result.serialize)
// //~ tester.setVerbose()
// tester.poke("outer_write_en", 0)
// tester.step()
// // Write addresses and read them.
// tester.poke("outer_addr", addr1)
// tester.poke("outer_din", addr1val)
// tester.poke("outer_write_en", 1)
// tester.step()
// tester.poke("outer_write_en", 0)
// tester.step()
// tester.poke("outer_addr", addr2)
// tester.poke("outer_din", addr2val)
// tester.poke("outer_write_en", 1)
// tester.step()
// tester.poke("outer_write_en", 0)
// tester.step()
// tester.poke("outer_addr", addr3)
// tester.poke("outer_din", addr3val)
// tester.poke("outer_write_en", 1)
// tester.step()
// tester.poke("outer_write_en", 0)
// tester.step()
// tester.poke("outer_addr", addr1)
// tester.step()
// tester.expect("outer_dout", addr1val)
// tester.poke("outer_addr", addr2)
// tester.step()
// tester.expect("outer_dout", addr2val)
// tester.poke("outer_addr", addr3)
// tester.step()
// tester.expect("outer_dout", addr3val)
// }
// }
// // Test to verify that the circuit doesn't read combinationally based on addr
// // between two submemories.
// class DontReadCombinationally extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleDepthTestGenerator {
// override lazy val width = 8
// override lazy val memDepth = BigInt(2048)
// override lazy val libDepth = BigInt(1024)
// compile(mem, lib, v, synflops = true)
// val result: Circuit = execute(mem, lib, synflops = true)
// it should "run with InterpretedTester" in {
// pending // Enable this when https://github.com/freechipsproject/firrtl-interpreter/pull/88 is snapshot-published
// val addr1 = 0
// val addr1a = 1
// val addr2 = 1 << 10
// val tester = new InterpretiveTester(result.serialize)
// //~ tester.setVerbose()
// tester.poke("outer_write_en", 0)
// tester.step()
// // Write two addresses, one in the lower submemory and the other in the
// // higher submemory.
// tester.poke("outer_addr", addr1)
// tester.poke("outer_din", 0x11)
// tester.poke("outer_write_en", 1)
// tester.step()
// tester.poke("outer_addr", addr1a)
// tester.poke("outer_din", 0x1a)
// tester.poke("outer_write_en", 1)
// tester.step()
// tester.poke("outer_addr", addr2)
// tester.poke("outer_din", 0xaa)
// tester.poke("outer_write_en", 1)
// tester.step()
// tester.poke("outer_write_en", 0)
// tester.poke("outer_addr", addr1)
// tester.step()
// // Test that there is no combinational read.
// tester.poke("outer_addr", addr1)
// tester.expect("outer_dout", 0x11)
// tester.poke("outer_addr", addr1a)
// tester.expect("outer_dout", 0x11)
// tester.poke("outer_addr", addr2)
// tester.expect("outer_dout", 0x11)
// // And upon step it should work again.
// tester.step()
// tester.expect("outer_addr", 0xaa)
// }
// }

View File

@@ -0,0 +1,546 @@
// See LICENSE for license details.
package barstools.macros
import firrtl.Parser.parse
import firrtl.ir.{Circuit, NoInfo}
import firrtl.passes.RemoveEmpty
import mdf.macrolib.SRAMMacro
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
import java.io.File
abstract class MacroCompilerSpec extends AnyFlatSpec with Matchers {
import scala.language.implicitConversions
implicit def String2SomeString(i: String): Option[String] = Some(i)
val testDir: String = "test_run_dir/macros"
new File(testDir).mkdirs // Make sure the testDir exists
// Override these to change the prefixing of macroDir and testDir
val memPrefix: String = testDir
val libPrefix: String = testDir
val vPrefix: String = testDir
// Override this to use a different cost metric.
// If this is None, the compile() call will not have any -c/-cp arguments, and
// execute() will use CostMetric.default.
val costMetric: Option[CostMetric] = None
private def getCostMetric: CostMetric = costMetric.getOrElse(CostMetric.default)
private def costMetricCmdLine = {
costMetric match {
case None => Nil
case Some(m) =>
val name = m.name()
val params = m.commandLineParams()
List("-c", name) ++ params.flatMap { case (key, value) => List("-cp", key, value) }
}
}
private def args(mem: String, lib: Option[String], v: String, synflops: Boolean, useCompiler: Boolean) =
List("-m", mem, "-v", v) ++
(lib match {
case None => Nil
case Some(l) => List("-l", l)
}) ++
costMetricCmdLine ++
(if (synflops) List("--mode", "synflops") else Nil) ++
(if (useCompiler) List("--use-compiler") else Nil)
// Run the full compiler as if from the command line interface.
// Generates the Verilog; useful in testing since an error will throw an
// exception.
def compile(mem: String, lib: String, v: String, synflops: Boolean): Unit = {
compile(mem, Some(lib), v, synflops)
}
def compile(mem: String, lib: Option[String], v: String, synflops: Boolean, useCompiler: Boolean = false): Unit = {
val mem_full = concat(memPrefix, mem)
val lib_full = concat(libPrefix, lib)
val v_full = concat(vPrefix, v)
MacroCompiler.run(args(mem_full, lib_full, v_full, synflops, useCompiler))
}
// Helper functions to write macro libraries to the given files.
def writeToLib(lib: String, libs: Seq[mdf.macrolib.Macro]): Boolean = {
mdf.macrolib.Utils.writeMDFToPath(Some(concat(libPrefix, lib)), libs)
}
def writeToMem(mem: String, mems: Seq[mdf.macrolib.Macro]): Boolean = {
mdf.macrolib.Utils.writeMDFToPath(Some(concat(memPrefix, mem)), mems)
}
// Convenience function for running both compile, execute, and test at once.
def compileExecuteAndTest(
mem: String,
lib: Option[String],
v: String,
output: String,
synflops: Boolean = false,
useCompiler: Boolean = false
): Unit = {
compile(mem, lib, v, synflops, useCompiler)
val result = execute(mem, lib, synflops, useCompiler)
test(result, output)
}
// Compare FIRRTL outputs after reparsing output with ScalaTest ("should be").
def test(result: Circuit, output: String): Unit = {
val gold = RemoveEmpty.run(parse(output))
result.serialize should be(gold.serialize)
}
// Execute the macro compiler and returns a Circuit containing the output of
// the memory compiler.
def execute(memFile: Option[String], libFile: Option[String], synflops: Boolean): Circuit =
execute(memFile, libFile, synflops, useCompiler = false)
def execute(memFile: Option[String], libFile: Option[String], synflops: Boolean, useCompiler: Boolean): Circuit = {
val mem_full = concat(memPrefix, memFile)
val lib_full = concat(libPrefix, libFile)
require(memFile.isDefined)
val mems: Seq[Macro] = Utils.filterForSRAM(mdf.macrolib.Utils.readMDFFromPath(mem_full)).get.map(new Macro(_))
val libs: Option[Seq[Macro]] = if (useCompiler) {
Utils.findSRAMCompiler(mdf.macrolib.Utils.readMDFFromPath(lib_full)).map { x =>
Utils.buildSRAMMacros(x).map(new Macro(_))
}
} else {
Utils.filterForSRAM(mdf.macrolib.Utils.readMDFFromPath(lib_full)) match {
case Some(x) => Some(x.map(new Macro(_)))
case None => None
}
}
val macros = mems.map(_.blackbox)
val circuit = Circuit(NoInfo, macros, macros.last.name)
val passes = Seq(
new MacroCompilerPass(
Some(mems),
libs,
None,
None,
getCostMetric,
if (synflops) MacroCompilerAnnotation.Synflops else MacroCompilerAnnotation.Default
),
new SynFlopsPass(synflops, libs.getOrElse(mems)),
RemoveEmpty
)
val result: Circuit = passes.foldLeft(circuit)((c, pass) => pass.run(c))
result
}
// Helper method to deal with String + Option[String]
private def concat(a: String, b: String): String = { a + "/" + b }
private def concat(a: String, b: Option[String]): Option[String] = {
b match {
case Some(b2: String) => Some(a + "/" + b2)
case _ => None
}
}
}
// A collection of standard SRAM generators.
trait HasSRAMGenerator {
import mdf.macrolib._
import scala.language.implicitConversions
implicit def Int2SomeInt(i: Int): Option[Int] = Some(i)
implicit def BigInt2SomeBigInt(i: BigInt): Option[BigInt] = Some(i)
// Generate a standard (read/write/combo) port for testing.
// Helper methods for optional width argument
def generateTestPort(
prefix: String,
width: Option[Int],
depth: Option[BigInt],
maskGran: Option[Int] = None,
read: Boolean,
readEnable: Boolean = false,
write: Boolean,
writeEnable: Boolean = false
): MacroPort = {
val realPrefix = if (prefix == "") "" else prefix + "_"
MacroPort(
address = PolarizedPort(name = realPrefix + "addr", polarity = ActiveHigh),
clock = Some(PolarizedPort(name = realPrefix + "clk", polarity = PositiveEdge)),
readEnable = if (readEnable) Some(PolarizedPort(name = realPrefix + "read_en", polarity = ActiveHigh)) else None,
writeEnable =
if (writeEnable) Some(PolarizedPort(name = realPrefix + "write_en", polarity = ActiveHigh)) else None,
output = if (read) Some(PolarizedPort(name = realPrefix + "dout", polarity = ActiveHigh)) else None,
input = if (write) Some(PolarizedPort(name = realPrefix + "din", polarity = ActiveHigh)) else None,
maskPort = maskGran match {
case Some(_: Int) => Some(PolarizedPort(name = realPrefix + "mask", polarity = ActiveHigh))
case _ => None
},
maskGran = maskGran,
width = width,
depth = depth // These numbers don't matter here.
)
}
// Generate a read port for testing.
def generateReadPort(
prefix: String,
width: Option[Int],
depth: Option[BigInt],
readEnable: Boolean = false
): MacroPort = {
generateTestPort(prefix, width, depth, write = false, read = true, readEnable = readEnable)
}
// Generate a write port for testing.
def generateWritePort(
prefix: String,
width: Option[Int],
depth: Option[BigInt],
maskGran: Option[Int] = None,
writeEnable: Boolean = true
): MacroPort = {
generateTestPort(prefix, width, depth, maskGran = maskGran, write = true, read = false, writeEnable = writeEnable)
}
// Generate a simple read-write port for testing.
def generateReadWritePort(
prefix: String,
width: Option[Int],
depth: Option[BigInt],
maskGran: Option[Int] = None
): MacroPort = {
generateTestPort(prefix, width, depth, maskGran = maskGran, write = true, writeEnable = true, read = true)
}
// Generate a "simple" SRAM (active high/positive edge, 1 read-write port).
def generateSRAM(
name: String,
prefix: String,
width: Int,
depth: BigInt,
maskGran: Option[Int] = None,
extraPorts: Seq[MacroExtraPort] = List()
): SRAMMacro = {
SRAMMacro(
name = name,
width = width,
depth = depth,
family = "1rw",
ports = Seq(generateReadWritePort(prefix, width, depth, maskGran)),
extraPorts = extraPorts
)
}
// Generate a "simple" SRAM group (active high/positive edge, 1 read-write port).
def generateSimpleSRAMGroup(
prefix: String,
mux: Int,
depth: Range,
width: Range,
maskGran: Option[Int] = None
): SRAMGroup = {
SRAMGroup(
Seq("mygroup_", "width", "x", "depth", "_", "VT"),
"1rw",
Seq("svt", "lvt", "ulvt"),
mux,
depth,
width,
Seq(generateReadWritePort(prefix, None, None, maskGran))
)
}
// 'vt': ('svt','lvt','ulvt'), 'mux': 2, 'depth': range(16,513,8), 'width': range(8,289,2), 'ports': 1
// 'vt': ('svt','lvt','ulvt'), 'mux': 4, 'depth': range(32,1025,16), 'width': range(4,145), 'ports': 1}
def generateSRAMCompiler(name: String, prefix: String): mdf.macrolib.SRAMCompiler = {
SRAMCompiler(
name,
Seq(
generateSimpleSRAMGroup(prefix, 2, Range(16, 512, 8), Range(8, 288, 2)),
generateSimpleSRAMGroup(prefix, 4, Range(32, 1024, 16), Range(4, 144, 1))
)
)
}
}
// Generic "simple" test generator.
// Set up scaffolding for generating memories, files, etc.
// Override this generator to specify the expected FIRRTL output.
trait HasSimpleTestGenerator {
this: MacroCompilerSpec with HasSRAMGenerator =>
// Override these with "override lazy val".
// Why lazy? These are used in the constructor here so overriding non-lazily
// would be too late.
def useCompiler: Boolean = false
def memWidth: Int
def libWidth: Int
def memDepth: BigInt
def libDepth: BigInt
def memMaskGran: Option[Int] = None
def libMaskGran: Option[Int] = None
def extraPorts: Seq[mdf.macrolib.MacroExtraPort] = List()
def extraTag: String = ""
// "Effective" libMaskGran by considering write_enable.
val effectiveLibMaskGran: Int = libMaskGran.getOrElse(libWidth)
// Override this in the sub-generator if you need a more specific name.
// Defaults to using reflection to pull the name of the test using this
// generator.
def generatorType: String = this.getClass.getSimpleName
//require (memDepth >= libDepth)
// Convenience variables to check if a mask exists.
val memHasMask: Boolean = memMaskGran.isDefined
val libHasMask: Boolean = libMaskGran.isDefined
// We need to figure out how many mask bits there are in the mem.
val memMaskBits: Int = if (memHasMask) memWidth / memMaskGran.get else 0
val libMaskBits: Int = if (libHasMask) libWidth / libMaskGran.get else 0
val extraTagPrefixed: String = if (extraTag == "") "" else "-" + extraTag
val mem = s"mem-$generatorType$extraTagPrefixed.json"
val lib = s"lib-$generatorType$extraTagPrefixed.json"
val v = s"$generatorType$extraTagPrefixed.v"
lazy val mem_name = "target_memory"
val mem_addr_width: Int = MacroCompilerMath.ceilLog2(memDepth)
lazy val lib_name = "awesome_lib_mem"
val lib_addr_width: Int = MacroCompilerMath.ceilLog2(libDepth)
// Override these to change the port prefixes if needed.
def libPortPrefix: String = "lib"
def memPortPrefix: String = "outer"
// These generate "simple" SRAMs (1 masked read-write port) by default,
// but can be overridden if need be.
def generateLibSRAM(): SRAMMacro = generateSRAM(lib_name, libPortPrefix, libWidth, libDepth, libMaskGran, extraPorts)
def generateMemSRAM(): SRAMMacro = generateSRAM(mem_name, memPortPrefix, memWidth, memDepth, memMaskGran)
def libSRAM: SRAMMacro = generateLibSRAM()
def memSRAM: SRAMMacro = generateMemSRAM()
def libSRAMs: Seq[SRAMMacro] = Seq(libSRAM)
def memSRAMs: Seq[SRAMMacro] = Seq(memSRAM)
writeToLib(lib, libSRAMs)
writeToMem(mem, memSRAMs)
// For masks, width it's a bit tricky since we have to consider cases like
// memMaskGran = 4 and libMaskGran = 8.
// Consider the actually usable libWidth in cases like the above.
val usableLibWidth: Int =
if (memMaskGran.getOrElse(Int.MaxValue) < effectiveLibMaskGran) memMaskGran.get else libWidth
// Number of lib instances needed to hold the mem, in both directions.
// Round up (e.g. 1.5 instances = effectively 2 instances)
val depthInstances: Int = math.ceil(memDepth.toFloat / libDepth.toFloat).toInt
val widthInstances: Int = math.ceil(memWidth.toFloat / usableLibWidth).toInt
// Number of width bits in the last width-direction memory.
// e.g. if memWidth = 16 and libWidth = 8, this would be 8 since the last memory 0_1 has 8 bits of input width.
// e.g. if memWidth = 9 and libWidth = 8, this would be 1 since the last memory 0_1 has 1 bit of input width.
lazy val lastWidthBits: Int = if (memWidth % usableLibWidth == 0) usableLibWidth else memWidth % usableLibWidth
lazy val selectBits: Int = mem_addr_width - lib_addr_width
/** Convenience function to generate a mask statement.
* @param widthInst Width instance (mem_0_x)
* @param depthInst Depth instance (mem_x_0)
*/
def generateMaskStatement(widthInst: Int, depthInst: Int): String = {
// Width of this submemory.
val myMemWidth = if (widthInst == widthInstances - 1) lastWidthBits else usableLibWidth
// Base bit of this submemory.
// e.g. if libWidth is 8 and this is submemory 2 (0-indexed), then this
// would be 16.
val myBaseBit = usableLibWidth * widthInst
if (libMaskGran.isDefined) {
if (memMaskGran.isEmpty) {
// If there is no memory mask, we should just turn all the lib mask
// bits high.
s"""mem_${depthInst}_$widthInst.lib_mask <= UInt<$libMaskBits>("h${((1 << libMaskBits) - 1).toHexString}")"""
} else {
// Calculate which bit of outer_mask contains the given bit.
// e.g. if memMaskGran = 2, libMaskGran = 1 and libWidth = 4, then
// calculateMaskBit({0, 1}) = 0 and calculateMaskBit({1, 2}) = 1
def calculateMaskBit(bit: Int): Int = bit / memMaskGran.getOrElse(memWidth)
val bitsArr = (libMaskBits - 1 to 0 by -1).map(x => {
if (x * libMaskGran.get > myMemWidth) {
// If we have extra mask bits leftover after the effective width,
// disable those bits.
"""UInt<1>("h0")"""
} else {
val outerMaskBit = calculateMaskBit(x * libMaskGran.get + myBaseBit)
s"bits(outer_mask, $outerMaskBit, $outerMaskBit)"
}
})
val maskVal = bitsArr.reduceRight((bit, rest) => s"cat($bit, $rest)")
s"mem_${depthInst}_$widthInst.lib_mask <= $maskVal"
}
} else ""
}
/** Helper function to generate a port.
*
* @param prefix Memory port prefix (e.g. "x" for ports like "x_clk")
* @param addrWidth Address port width
* @param width data width
* @param write Has a write port?
* @param writeEnable Has a write enable port?
* @param read Has a read port?
* @param readEnable Has a read enable port?
* @param mask Mask granularity (# bits) of the port or None.
* @param extraPorts Extra ports (name, # bits)
*/
def generatePort(
prefix: String,
addrWidth: Int,
width: Int,
write: Boolean,
writeEnable: Boolean,
read: Boolean,
readEnable: Boolean,
mask: Option[Int],
extraPorts: Seq[(String, Int)] = Seq()
): String = {
val realPrefix = if (prefix == "") "" else prefix + "_"
val readStr = if (read) s"output ${realPrefix}dout : UInt<$width>" else ""
val writeStr = if (write) s"input ${realPrefix}din : UInt<$width>" else ""
val readEnableStr = if (readEnable) s"input ${realPrefix}read_en : UInt<1>" else ""
val writeEnableStr = if (writeEnable) s"input ${realPrefix}write_en : UInt<1>" else ""
val maskStr = mask match {
case Some(maskBits: Int) => s"input ${realPrefix}mask : UInt<$maskBits>"
case _ => ""
}
val extraPortsStr = extraPorts.map { case (name, bits) => s" input $name : UInt<$bits>" }.mkString("\n")
s"""
input ${realPrefix}addr : UInt<$addrWidth>
input ${realPrefix}clk : Clock
$writeStr
$readStr
$readEnableStr
$writeEnableStr
$maskStr
$extraPortsStr
"""
}
/** Helper function to generate a RW footer port.
*
* @param prefix Memory port prefix (e.g. "x" for ports like "x_clk")
* @param readEnable Has a read enable port?
* @param mask Mask granularity (# bits) of the port or None.
* @param extraPorts Extra ports (name, # bits)
*/
def generateReadWriteFooterPort(
prefix: String,
readEnable: Boolean,
mask: Option[Int],
extraPorts: Seq[(String, Int)] = Seq()
): String = {
generatePort(
prefix,
lib_addr_width,
libWidth,
write = true,
writeEnable = true,
read = true,
readEnable = readEnable,
mask = mask,
extraPorts = extraPorts
)
}
/** Helper function to generate a RW header port.
* @param prefix Memory port prefix (e.g. "x" for ports like "x_clk")
* @param readEnable Has a read enable port?
* @param mask Mask granularity (# bits) of the port or None.
*/
def generateReadWriteHeaderPort(prefix: String, readEnable: Boolean, mask: Option[Int]): String = {
generatePort(
prefix,
mem_addr_width,
memWidth,
write = true,
writeEnable = true,
read = true,
readEnable = readEnable,
mask
)
}
// Generate the header memory ports.
def generateHeaderPorts(): String = {
require(memSRAM.ports.size == 1, "Header generator only supports single RW port mem")
generateReadWriteHeaderPort(
memPortPrefix,
memSRAM.ports.head.readEnable.isDefined,
if (memHasMask) Some(memMaskBits) else None
)
}
// Generate the header (contains the circuit statement and the target memory
// module.
def generateHeader(): String = {
s"""
circuit $mem_name :
module $mem_name :
${generateHeaderPorts()}
"""
}
// Generate the target memory ports.
def generateFooterPorts(): String = {
require(libSRAM.ports.size == 1, "Footer generator only supports single RW port mem")
generateReadWriteFooterPort(
libPortPrefix,
libSRAM.ports.head.readEnable.isDefined,
if (libHasMask) Some(libMaskBits) else None,
extraPorts.map(p => (p.name, p.width))
)
}
// Generate the footer (contains the target memory extmodule declaration by default).
def generateFooter(): String = {
s"""
extmodule $lib_name :
${generateFooterPorts()}
defname = $lib_name
"""
}
// Abstract method to generate body; to be overridden by specific generator type.
def generateBody(): String
// Generate the entire output from header, body, and footer.
def generateOutput(): String = {
s"""
${generateHeader()}
${generateBody()}
${generateFooter()}
"""
}
val output: String = generateOutput()
}
// Use this trait for tests that invoke the memory compiler without lib.
trait HasNoLibTestGenerator extends HasSimpleTestGenerator {
this: MacroCompilerSpec with HasSRAMGenerator =>
// If there isn't a lib, then the "lib" will become a FIRRTL "mem", which
// in turn becomes synthesized flops.
// Therefore, make "lib" width/depth equal to the mem.
override lazy val libDepth: BigInt = memDepth
override lazy val libWidth: Int = memWidth
override lazy val lib_name: String = mem_name
// Do the same for port names.
override lazy val libPortPrefix: String = memPortPrefix
// If there is no lib, don't generate a body.
override def generateBody() = ""
}

View File

@@ -0,0 +1,383 @@
package barstools.macros
// Test the ability of the compiler to deal with various mask combinations.
trait MasksTestSettings {
this: MacroCompilerSpec with HasSRAMGenerator with HasSimpleDepthTestGenerator =>
override lazy val memDepth = BigInt(2048)
override lazy val libDepth = BigInt(1024)
}
// Try all four different kinds of mask config:
/** Non-masked mem Masked mem
* ---------------------------------
* Non-masked lib | | |
* ---------------------------------
* Masked lib | | |
* ---------------------------------
*/
class Masks_FourTypes_NonMaskedMem_NonMaskedLib
extends MacroCompilerSpec
with HasSRAMGenerator
with HasSimpleWidthTestGenerator {
override lazy val depth = BigInt(1024)
override lazy val memWidth = 32
override lazy val memMaskGran: Option[Int] = None
override lazy val libWidth = 8
override lazy val libMaskGran: Option[Int] = None
it should "compile, execute, and test" in {
compileExecuteAndTest(mem, lib, v, output)
}
}
class Masks_FourTypes_NonMaskedMem_MaskedLib
extends MacroCompilerSpec
with HasSRAMGenerator
with HasSimpleWidthTestGenerator {
override lazy val depth = BigInt(1024)
override lazy val memWidth = 32
override lazy val memMaskGran: Option[Int] = None
override lazy val libWidth = 8
override lazy val libMaskGran: Option[Int] = Some(2)
it should "compile, execute, and test" in {
compileExecuteAndTest(mem, lib, v, output)
}
}
class Masks_FourTypes_MaskedMem_NonMaskedLib
extends MacroCompilerSpec
with HasSRAMGenerator
with HasSimpleWidthTestGenerator {
override lazy val depth = BigInt(1024)
override lazy val memWidth = 32
override lazy val memMaskGran: Option[Int] = Some(8)
override lazy val libWidth = 8
override lazy val libMaskGran: Option[Int] = None
it should "compile, execute, and test" in {
compileExecuteAndTest(mem, lib, v, output)
}
}
class Masks_FourTypes_MaskedMem_NonMaskedLib_SmallerMaskGran
extends MacroCompilerSpec
with HasSRAMGenerator
with HasSimpleWidthTestGenerator {
override lazy val depth = BigInt(1024)
override lazy val memWidth = 32
override lazy val memMaskGran: Option[Int] = Some(4)
override lazy val libWidth = 8
override lazy val libMaskGran: Option[Int] = None
it should "compile, execute, and test" in {
compileExecuteAndTest(mem, lib, v, output)
}
}
class Masks_FourTypes_MaskedMem_MaskedLib
extends MacroCompilerSpec
with HasSRAMGenerator
with HasSimpleWidthTestGenerator {
override lazy val depth = BigInt(1024)
override lazy val memWidth = 32
override lazy val memMaskGran: Option[Int] = Some(8)
override lazy val libWidth = 16
override lazy val libMaskGran: Option[Int] = Some(4)
it should "compile, execute, and test" in {
compileExecuteAndTest(mem, lib, v, output)
}
}
class Masks_FourTypes_MaskedMem_MaskedLib_SameMaskGran
extends MacroCompilerSpec
with HasSRAMGenerator
with HasSimpleWidthTestGenerator {
override lazy val depth = BigInt(1024)
override lazy val memWidth = 32
override lazy val memMaskGran: Option[Int] = Some(8)
override lazy val libWidth = 16
override lazy val libMaskGran: Option[Int] = Some(8)
it should "compile, execute, and test" in {
compileExecuteAndTest(mem, lib, v, output)
}
}
class Masks_FourTypes_MaskedMem_MaskedLib_SmallerMaskGran
extends MacroCompilerSpec
with HasSRAMGenerator
with HasSimpleWidthTestGenerator {
override lazy val depth = BigInt(1024)
override lazy val memWidth = 64
override lazy val memMaskGran: Option[Int] = Some(4)
override lazy val libWidth = 32
override lazy val libMaskGran: Option[Int] = Some(8)
it should "compile, execute, and test" in {
compileExecuteAndTest(mem, lib, v, output)
}
}
// Bit-mask memories to non-masked libs whose width is larger than 1.
class Masks_BitMaskedMem_NonMaskedLib extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleWidthTestGenerator {
override lazy val depth = BigInt(1024)
override lazy val memWidth = 16
override lazy val memMaskGran: Option[Int] = Some(1)
override lazy val libWidth = 8
override lazy val libMaskGran: Option[Int] = None
it should "compile, execute, and test" in {
compileExecuteAndTest(mem, lib, v, output)
}
}
// FPGA-style byte-masked memories.
class Masks_FPGAStyle_32_8
extends MacroCompilerSpec
with HasSRAMGenerator
with HasSimpleDepthTestGenerator
with MasksTestSettings {
override lazy val width = 32
override lazy val memMaskGran: Option[Int] = Some(32)
override lazy val libMaskGran: Option[Int] = Some(8)
it should "compile, execute, and test" in {
compileExecuteAndTest(mem, lib, v, output)
}
}
// Simple powers of two with bit-masked lib.
class Masks_PowersOfTwo_8_1
extends MacroCompilerSpec
with HasSRAMGenerator
with HasSimpleDepthTestGenerator
with MasksTestSettings {
override lazy val width = 64
override lazy val memMaskGran: Option[Int] = Some(8)
override lazy val libMaskGran: Option[Int] = Some(1)
it should "compile, execute, and test" in {
compileExecuteAndTest(mem, lib, v, output)
}
}
class Masks_PowersOfTwo_16_1
extends MacroCompilerSpec
with HasSRAMGenerator
with HasSimpleDepthTestGenerator
with MasksTestSettings {
override lazy val width = 64
override lazy val memMaskGran: Option[Int] = Some(16)
override lazy val libMaskGran: Option[Int] = Some(1)
it should "compile, execute, and test" in {
compileExecuteAndTest(mem, lib, v, output)
}
}
class Masks_PowersOfTwo_32_1
extends MacroCompilerSpec
with HasSRAMGenerator
with HasSimpleDepthTestGenerator
with MasksTestSettings {
override lazy val width = 64
override lazy val memMaskGran: Option[Int] = Some(32)
override lazy val libMaskGran: Option[Int] = Some(1)
it should "compile, execute, and test" in {
compileExecuteAndTest(mem, lib, v, output)
}
}
class Masks_PowersOfTwo_64_1
extends MacroCompilerSpec
with HasSRAMGenerator
with HasSimpleDepthTestGenerator
with MasksTestSettings {
override lazy val width = 64
override lazy val memMaskGran: Option[Int] = Some(64)
override lazy val libMaskGran: Option[Int] = Some(1)
it should "compile, execute, and test" in {
compileExecuteAndTest(mem, lib, v, output)
}
}
// Simple powers of two with non bit-masked lib.
class Masks_PowersOfTwo_32_4
extends MacroCompilerSpec
with HasSRAMGenerator
with HasSimpleDepthTestGenerator
with MasksTestSettings {
override lazy val width = 128
override lazy val memMaskGran: Option[Int] = Some(32)
override lazy val libMaskGran: Option[Int] = Some(4)
it should "compile, execute, and test" in {
compileExecuteAndTest(mem, lib, v, output)
}
}
class Masks_PowersOfTwo_32_8
extends MacroCompilerSpec
with HasSRAMGenerator
with HasSimpleDepthTestGenerator
with MasksTestSettings {
override lazy val width = 128
override lazy val memMaskGran: Option[Int] = Some(32)
override lazy val libMaskGran: Option[Int] = Some(8)
it should "compile, execute, and test" in {
compileExecuteAndTest(mem, lib, v, output)
}
}
class Masks_PowersOfTwo_8_8
extends MacroCompilerSpec
with HasSRAMGenerator
with HasSimpleDepthTestGenerator
with MasksTestSettings {
override lazy val width = 128
override lazy val memMaskGran: Option[Int] = Some(8)
override lazy val libMaskGran: Option[Int] = Some(8)
it should "compile, execute, and test" in {
compileExecuteAndTest(mem, lib, v, output)
}
}
// Width as a multiple of the mask, bit-masked lib
class Masks_IntegerMaskMultiple_20_10
extends MacroCompilerSpec
with HasSRAMGenerator
with HasSimpleDepthTestGenerator
with MasksTestSettings {
override lazy val width = 20
override lazy val memMaskGran: Option[Int] = Some(10)
override lazy val libMaskGran: Option[Int] = Some(1)
it should "compile, execute, and test" in {
compileExecuteAndTest(mem, lib, v, output)
}
}
class Masks_IntegerMaskMultiple_21_7
extends MacroCompilerSpec
with HasSRAMGenerator
with HasSimpleDepthTestGenerator
with MasksTestSettings {
override lazy val width = 21
override lazy val memMaskGran: Option[Int] = Some(21)
override lazy val libMaskGran: Option[Int] = Some(7)
(it should "be enabled when non-power of two masks are supported").is(pending)
//~ compileExecuteAndTest(mem, lib, v, output)
}
class Masks_IntegerMaskMultiple_21_21
extends MacroCompilerSpec
with HasSRAMGenerator
with HasSimpleDepthTestGenerator
with MasksTestSettings {
override lazy val width = 21
override lazy val memMaskGran: Option[Int] = Some(21)
override lazy val libMaskGran: Option[Int] = Some(1)
it should "compile, execute, and test" in {
compileExecuteAndTest(mem, lib, v, output)
}
}
class Masks_IntegerMaskMultiple_84_21
extends MacroCompilerSpec
with HasSRAMGenerator
with HasSimpleDepthTestGenerator
with MasksTestSettings {
override lazy val width = 84
override lazy val memMaskGran: Option[Int] = Some(21)
override lazy val libMaskGran: Option[Int] = Some(1)
it should "compile, execute, and test" in {
compileExecuteAndTest(mem, lib, v, output)
}
}
class Masks_IntegerMaskMultiple_92_23
extends MacroCompilerSpec
with HasSRAMGenerator
with HasSimpleDepthTestGenerator
with MasksTestSettings {
override lazy val width = 92
override lazy val memMaskGran: Option[Int] = Some(23)
override lazy val libMaskGran: Option[Int] = Some(1)
it should "compile, execute, and test" in {
compileExecuteAndTest(mem, lib, v, output)
}
}
class Masks_IntegerMaskMultiple_117_13
extends MacroCompilerSpec
with HasSRAMGenerator
with HasSimpleDepthTestGenerator
with MasksTestSettings {
override lazy val width = 117
override lazy val memMaskGran: Option[Int] = Some(13)
override lazy val libMaskGran: Option[Int] = Some(1)
it should "compile, execute, and test" in {
compileExecuteAndTest(mem, lib, v, output)
}
}
class Masks_IntegerMaskMultiple_160_20
extends MacroCompilerSpec
with HasSRAMGenerator
with HasSimpleDepthTestGenerator
with MasksTestSettings {
override lazy val width = 160
override lazy val memMaskGran: Option[Int] = Some(20)
override lazy val libMaskGran: Option[Int] = Some(1)
it should "compile, execute, and test" in {
compileExecuteAndTest(mem, lib, v, output)
}
}
class Masks_IntegerMaskMultiple_184_23
extends MacroCompilerSpec
with HasSRAMGenerator
with HasSimpleDepthTestGenerator
with MasksTestSettings {
override lazy val width = 184
override lazy val memMaskGran: Option[Int] = Some(23)
override lazy val libMaskGran: Option[Int] = Some(1)
it should "compile, execute, and test" in {
compileExecuteAndTest(mem, lib, v, output)
}
}
// Width as an non-integer multiple of the mask, bit-masked lib
class Masks_NonIntegerMaskMultiple_32_3
extends MacroCompilerSpec
with HasSRAMGenerator
with HasSimpleDepthTestGenerator
with MasksTestSettings {
override lazy val width = 32
override lazy val memMaskGran: Option[Int] = Some(3)
override lazy val libMaskGran: Option[Int] = Some(1)
(it should "be enabled when non-power of two masks are supported").is(pending)
//~ compileExecuteAndTest(mem, lib, v, output)
}

View File

@@ -0,0 +1,500 @@
package barstools.macros
// Test that the memory compiler works fine for compiling multi-port memories.
// TODO: extend test generator to also automatically generate multi-ported memories.
class SplitWidth_2rw extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleWidthTestGenerator {
import mdf.macrolib._
override lazy val depth = BigInt(1024)
override lazy val memWidth = 64
override lazy val memMaskGran: Option[Int] = Some(16)
override lazy val libWidth = 16
override def generateMemSRAM(): SRAMMacro = {
SRAMMacro(
name = mem_name,
width = memWidth,
depth = memDepth,
family = "2rw",
ports = Seq(
generateTestPort(
"portA",
memWidth,
Some(memDepth),
maskGran = memMaskGran,
write = true,
writeEnable = true,
read = true,
readEnable = true
),
generateTestPort(
"portB",
memWidth,
Some(memDepth),
maskGran = memMaskGran,
write = true,
writeEnable = true,
read = true,
readEnable = true
)
)
)
}
override def generateLibSRAM(): SRAMMacro = {
SRAMMacro(
name = lib_name,
width = libWidth,
depth = libDepth,
family = "2rw",
ports = Seq(
generateTestPort(
"portA",
libWidth,
libDepth,
write = true,
writeEnable = true,
read = true,
readEnable = true
),
generateTestPort(
"portB",
libWidth,
libDepth,
write = true,
writeEnable = true,
read = true,
readEnable = true
)
)
)
}
override def generateHeaderPorts(): String = {
generateReadWriteHeaderPort("portA", readEnable = true, Some(memMaskBits)) + "\n" + generateReadWriteHeaderPort(
"portB",
readEnable = true,
Some(memMaskBits)
)
}
override def generateFooterPorts(): String = {
generateReadWriteFooterPort("portA", readEnable = true, None) + "\n" + generateReadWriteFooterPort(
"portB",
readEnable = true,
None
)
}
override def generateBody() =
"""
inst mem_0_0 of awesome_lib_mem
inst mem_0_1 of awesome_lib_mem
inst mem_0_2 of awesome_lib_mem
inst mem_0_3 of awesome_lib_mem
mem_0_0.portA_clk <= portA_clk
mem_0_0.portA_addr <= portA_addr
node portA_dout_0_0 = bits(mem_0_0.portA_dout, 15, 0)
mem_0_0.portA_din <= bits(portA_din, 15, 0)
mem_0_0.portA_read_en <= and(portA_read_en, UInt<1>("h1"))
mem_0_0.portA_write_en <= and(and(and(portA_write_en, UInt<1>("h1")), bits(portA_mask, 0, 0)), UInt<1>("h1"))
mem_0_1.portA_clk <= portA_clk
mem_0_1.portA_addr <= portA_addr
node portA_dout_0_1 = bits(mem_0_1.portA_dout, 15, 0)
mem_0_1.portA_din <= bits(portA_din, 31, 16)
mem_0_1.portA_read_en <= and(portA_read_en, UInt<1>("h1"))
mem_0_1.portA_write_en <= and(and(and(portA_write_en, UInt<1>("h1")), bits(portA_mask, 1, 1)), UInt<1>("h1"))
mem_0_2.portA_clk <= portA_clk
mem_0_2.portA_addr <= portA_addr
node portA_dout_0_2 = bits(mem_0_2.portA_dout, 15, 0)
mem_0_2.portA_din <= bits(portA_din, 47, 32)
mem_0_2.portA_read_en <= and(portA_read_en, UInt<1>("h1"))
mem_0_2.portA_write_en <= and(and(and(portA_write_en, UInt<1>("h1")), bits(portA_mask, 2, 2)), UInt<1>("h1"))
mem_0_3.portA_clk <= portA_clk
mem_0_3.portA_addr <= portA_addr
node portA_dout_0_3 = bits(mem_0_3.portA_dout, 15, 0)
mem_0_3.portA_din <= bits(portA_din, 63, 48)
mem_0_3.portA_read_en <= and(portA_read_en, UInt<1>("h1"))
mem_0_3.portA_write_en <= and(and(and(portA_write_en, UInt<1>("h1")), bits(portA_mask, 3, 3)), UInt<1>("h1"))
node portA_dout_0 = cat(portA_dout_0_3, cat(portA_dout_0_2, cat(portA_dout_0_1, portA_dout_0_0)))
mem_0_0.portB_clk <= portB_clk
mem_0_0.portB_addr <= portB_addr
node portB_dout_0_0 = bits(mem_0_0.portB_dout, 15, 0)
mem_0_0.portB_din <= bits(portB_din, 15, 0)
mem_0_0.portB_read_en <= and(portB_read_en, UInt<1>("h1"))
mem_0_0.portB_write_en <= and(and(and(portB_write_en, UInt<1>("h1")), bits(portB_mask, 0, 0)), UInt<1>("h1"))
mem_0_1.portB_clk <= portB_clk
mem_0_1.portB_addr <= portB_addr
node portB_dout_0_1 = bits(mem_0_1.portB_dout, 15, 0)
mem_0_1.portB_din <= bits(portB_din, 31, 16)
mem_0_1.portB_read_en <= and(portB_read_en, UInt<1>("h1"))
mem_0_1.portB_write_en <= and(and(and(portB_write_en, UInt<1>("h1")), bits(portB_mask, 1, 1)), UInt<1>("h1"))
mem_0_2.portB_clk <= portB_clk
mem_0_2.portB_addr <= portB_addr
node portB_dout_0_2 = bits(mem_0_2.portB_dout, 15, 0)
mem_0_2.portB_din <= bits(portB_din, 47, 32)
mem_0_2.portB_read_en <= and(portB_read_en, UInt<1>("h1"))
mem_0_2.portB_write_en <= and(and(and(portB_write_en, UInt<1>("h1")), bits(portB_mask, 2, 2)), UInt<1>("h1"))
mem_0_3.portB_clk <= portB_clk
mem_0_3.portB_addr <= portB_addr
node portB_dout_0_3 = bits(mem_0_3.portB_dout, 15, 0)
mem_0_3.portB_din <= bits(portB_din, 63, 48)
mem_0_3.portB_read_en <= and(portB_read_en, UInt<1>("h1"))
mem_0_3.portB_write_en <= and(and(and(portB_write_en, UInt<1>("h1")), bits(portB_mask, 3, 3)), UInt<1>("h1"))
node portB_dout_0 = cat(portB_dout_0_3, cat(portB_dout_0_2, cat(portB_dout_0_1, portB_dout_0_0)))
portA_dout <= mux(UInt<1>("h1"), portA_dout_0, UInt<64>("h0"))
portB_dout <= mux(UInt<1>("h1"), portB_dout_0, UInt<64>("h0"))
"""
compileExecuteAndTest(mem, lib, v, output)
}
class SplitWidth_1r_1w extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleWidthTestGenerator {
import mdf.macrolib._
override lazy val depth = BigInt(1024)
override lazy val memWidth = 64
override lazy val memMaskGran: Option[Int] = Some(16)
override lazy val libWidth = 16
override def generateMemSRAM(): SRAMMacro = {
SRAMMacro(
name = mem_name,
width = memWidth,
depth = memDepth,
family = "1r1w",
ports = Seq(
generateTestPort(
"portA",
memWidth,
Some(memDepth),
maskGran = memMaskGran,
write = false,
read = true,
readEnable = true
),
generateTestPort(
"portB",
memWidth,
Some(memDepth),
maskGran = memMaskGran,
write = true,
writeEnable = true,
read = false
)
)
)
}
override def generateLibSRAM(): SRAMMacro = {
SRAMMacro(
name = lib_name,
width = libWidth,
depth = libDepth,
family = "1r1w",
ports = Seq(
generateTestPort(
"portA",
libWidth,
libDepth,
write = false,
read = true,
readEnable = true
),
generateTestPort("portB", libWidth, libDepth, write = true, writeEnable = true, read = false)
)
)
}
override def generateHeaderPorts(): String = {
generatePort(
"portA",
mem_addr_width,
memWidth,
write = false,
writeEnable = false,
read = true,
readEnable = true,
Some(memMaskBits)
) + "\n" +
generatePort(
"portB",
mem_addr_width,
memWidth,
write = true,
writeEnable = true,
read = false,
readEnable = false,
Some(memMaskBits)
)
}
override def generateFooterPorts(): String = {
generatePort(
"portA",
lib_addr_width,
libWidth,
write = false,
writeEnable = false,
read = true,
readEnable = true,
None
) + "\n" +
generatePort(
"portB",
lib_addr_width,
libWidth,
write = true,
writeEnable = true,
read = false,
readEnable = false,
None
)
}
override def generateBody() =
"""
inst mem_0_0 of awesome_lib_mem
inst mem_0_1 of awesome_lib_mem
inst mem_0_2 of awesome_lib_mem
inst mem_0_3 of awesome_lib_mem
mem_0_0.portB_clk <= portB_clk
mem_0_0.portB_addr <= portB_addr
mem_0_0.portB_din <= bits(portB_din, 15, 0)
mem_0_0.portB_write_en <= and(and(and(portB_write_en, UInt<1>("h1")), bits(portB_mask, 0, 0)), UInt<1>("h1"))
mem_0_1.portB_clk <= portB_clk
mem_0_1.portB_addr <= portB_addr
mem_0_1.portB_din <= bits(portB_din, 31, 16)
mem_0_1.portB_write_en <= and(and(and(portB_write_en, UInt<1>("h1")), bits(portB_mask, 1, 1)), UInt<1>("h1"))
mem_0_2.portB_clk <= portB_clk
mem_0_2.portB_addr <= portB_addr
mem_0_2.portB_din <= bits(portB_din, 47, 32)
mem_0_2.portB_write_en <= and(and(and(portB_write_en, UInt<1>("h1")), bits(portB_mask, 2, 2)), UInt<1>("h1"))
mem_0_3.portB_clk <= portB_clk
mem_0_3.portB_addr <= portB_addr
mem_0_3.portB_din <= bits(portB_din, 63, 48)
mem_0_3.portB_write_en <= and(and(and(portB_write_en, UInt<1>("h1")), bits(portB_mask, 3, 3)), UInt<1>("h1"))
mem_0_0.portA_clk <= portA_clk
mem_0_0.portA_addr <= portA_addr
node portA_dout_0_0 = bits(mem_0_0.portA_dout, 15, 0)
mem_0_0.portA_read_en <= and(portA_read_en, UInt<1>("h1"))
mem_0_1.portA_clk <= portA_clk
mem_0_1.portA_addr <= portA_addr
node portA_dout_0_1 = bits(mem_0_1.portA_dout, 15, 0)
mem_0_1.portA_read_en <= and(portA_read_en, UInt<1>("h1"))
mem_0_2.portA_clk <= portA_clk
mem_0_2.portA_addr <= portA_addr
node portA_dout_0_2 = bits(mem_0_2.portA_dout, 15, 0)
mem_0_2.portA_read_en <= and(portA_read_en, UInt<1>("h1"))
mem_0_3.portA_clk <= portA_clk
mem_0_3.portA_addr <= portA_addr
node portA_dout_0_3 = bits(mem_0_3.portA_dout, 15, 0)
mem_0_3.portA_read_en <= and(portA_read_en, UInt<1>("h1"))
node portA_dout_0 = cat(portA_dout_0_3, cat(portA_dout_0_2, cat(portA_dout_0_1, portA_dout_0_0)))
portA_dout <= mux(UInt<1>("h1"), portA_dout_0, UInt<64>("h0"))
"""
compileExecuteAndTest(mem, lib, v, output)
}
class SplitWidth_2rw_differentMasks extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleWidthTestGenerator {
import mdf.macrolib._
override lazy val depth = BigInt(1024)
override lazy val memWidth = 64
override lazy val memMaskGran: Option[Int] = Some(16)
override lazy val libWidth = 16
lazy val memMaskGranB = 8 // these generators are run at constructor time
override def generateMemSRAM(): SRAMMacro = {
SRAMMacro(
name = mem_name,
width = memWidth,
depth = memDepth,
family = "2rw",
ports = Seq(
generateTestPort(
"portA",
memWidth,
Some(memDepth),
maskGran = memMaskGran,
write = true,
writeEnable = true,
read = true,
readEnable = true
),
generateTestPort(
"portB",
memWidth,
Some(memDepth),
maskGran = Some(memMaskGranB),
write = true,
writeEnable = true,
read = true,
readEnable = true
)
)
)
}
override def generateLibSRAM(): SRAMMacro = {
SRAMMacro(
name = lib_name,
width = libWidth,
depth = libDepth,
family = "2rw",
ports = Seq(
generateTestPort(
"portA",
libWidth,
libDepth,
write = true,
writeEnable = true,
read = true,
readEnable = true
),
generateTestPort(
"portB",
libWidth,
libDepth,
write = true,
writeEnable = true,
read = true,
readEnable = true
)
)
)
}
override def generateHeaderPorts(): String = {
generateReadWriteHeaderPort("portA", readEnable = true, Some(memMaskBits)) + "\n" + generateReadWriteHeaderPort(
"portB",
readEnable = true,
Some(memWidth / memMaskGranB)
)
}
override def generateFooterPorts(): String = {
generateReadWriteFooterPort("portA", readEnable = true, None) + "\n" + generateReadWriteFooterPort(
"portB",
readEnable = true,
None
)
}
override def generateBody() =
"""
inst mem_0_0 of awesome_lib_mem
inst mem_0_1 of awesome_lib_mem
inst mem_0_2 of awesome_lib_mem
inst mem_0_3 of awesome_lib_mem
inst mem_0_4 of awesome_lib_mem
inst mem_0_5 of awesome_lib_mem
inst mem_0_6 of awesome_lib_mem
inst mem_0_7 of awesome_lib_mem
mem_0_0.portA_clk <= portA_clk
mem_0_0.portA_addr <= portA_addr
node portA_dout_0_0 = bits(mem_0_0.portA_dout, 7, 0)
mem_0_0.portA_din <= bits(portA_din, 7, 0)
mem_0_0.portA_read_en <= and(portA_read_en, UInt<1>("h1"))
mem_0_0.portA_write_en <= and(and(and(portA_write_en, UInt<1>("h1")), bits(portA_mask, 0, 0)), UInt<1>("h1"))
mem_0_1.portA_clk <= portA_clk
mem_0_1.portA_addr <= portA_addr
node portA_dout_0_1 = bits(mem_0_1.portA_dout, 7, 0)
mem_0_1.portA_din <= bits(portA_din, 15, 8)
mem_0_1.portA_read_en <= and(portA_read_en, UInt<1>("h1"))
mem_0_1.portA_write_en <= and(and(and(portA_write_en, UInt<1>("h1")), bits(portA_mask, 0, 0)), UInt<1>("h1"))
mem_0_2.portA_clk <= portA_clk
mem_0_2.portA_addr <= portA_addr
node portA_dout_0_2 = bits(mem_0_2.portA_dout, 7, 0)
mem_0_2.portA_din <= bits(portA_din, 23, 16)
mem_0_2.portA_read_en <= and(portA_read_en, UInt<1>("h1"))
mem_0_2.portA_write_en <= and(and(and(portA_write_en, UInt<1>("h1")), bits(portA_mask, 1, 1)), UInt<1>("h1"))
mem_0_3.portA_clk <= portA_clk
mem_0_3.portA_addr <= portA_addr
node portA_dout_0_3 = bits(mem_0_3.portA_dout, 7, 0)
mem_0_3.portA_din <= bits(portA_din, 31, 24)
mem_0_3.portA_read_en <= and(portA_read_en, UInt<1>("h1"))
mem_0_3.portA_write_en <= and(and(and(portA_write_en, UInt<1>("h1")), bits(portA_mask, 1, 1)), UInt<1>("h1"))
mem_0_4.portA_clk <= portA_clk
mem_0_4.portA_addr <= portA_addr
node portA_dout_0_4 = bits(mem_0_4.portA_dout, 7, 0)
mem_0_4.portA_din <= bits(portA_din, 39, 32)
mem_0_4.portA_read_en <= and(portA_read_en, UInt<1>("h1"))
mem_0_4.portA_write_en <= and(and(and(portA_write_en, UInt<1>("h1")), bits(portA_mask, 2, 2)), UInt<1>("h1"))
mem_0_5.portA_clk <= portA_clk
mem_0_5.portA_addr <= portA_addr
node portA_dout_0_5 = bits(mem_0_5.portA_dout, 7, 0)
mem_0_5.portA_din <= bits(portA_din, 47, 40)
mem_0_5.portA_read_en <= and(portA_read_en, UInt<1>("h1"))
mem_0_5.portA_write_en <= and(and(and(portA_write_en, UInt<1>("h1")), bits(portA_mask, 2, 2)), UInt<1>("h1"))
mem_0_6.portA_clk <= portA_clk
mem_0_6.portA_addr <= portA_addr
node portA_dout_0_6 = bits(mem_0_6.portA_dout, 7, 0)
mem_0_6.portA_din <= bits(portA_din, 55, 48)
mem_0_6.portA_read_en <= and(portA_read_en, UInt<1>("h1"))
mem_0_6.portA_write_en <= and(and(and(portA_write_en, UInt<1>("h1")), bits(portA_mask, 3, 3)), UInt<1>("h1"))
mem_0_7.portA_clk <= portA_clk
mem_0_7.portA_addr <= portA_addr
node portA_dout_0_7 = bits(mem_0_7.portA_dout, 7, 0)
mem_0_7.portA_din <= bits(portA_din, 63, 56)
mem_0_7.portA_read_en <= and(portA_read_en, UInt<1>("h1"))
mem_0_7.portA_write_en <= and(and(and(portA_write_en, UInt<1>("h1")), bits(portA_mask, 3, 3)), UInt<1>("h1"))
node portA_dout_0 = cat(portA_dout_0_7, cat(portA_dout_0_6, cat(portA_dout_0_5, cat(portA_dout_0_4, cat(portA_dout_0_3, cat(portA_dout_0_2, cat(portA_dout_0_1, portA_dout_0_0)))))))
mem_0_0.portB_clk <= portB_clk
mem_0_0.portB_addr <= portB_addr
node portB_dout_0_0 = bits(mem_0_0.portB_dout, 7, 0)
mem_0_0.portB_din <= bits(portB_din, 7, 0)
mem_0_0.portB_read_en <= and(portB_read_en, UInt<1>("h1"))
mem_0_0.portB_write_en <= and(and(and(portB_write_en, UInt<1>("h1")), bits(portB_mask, 0, 0)), UInt<1>("h1"))
mem_0_1.portB_clk <= portB_clk
mem_0_1.portB_addr <= portB_addr
node portB_dout_0_1 = bits(mem_0_1.portB_dout, 7, 0)
mem_0_1.portB_din <= bits(portB_din, 15, 8)
mem_0_1.portB_read_en <= and(portB_read_en, UInt<1>("h1"))
mem_0_1.portB_write_en <= and(and(and(portB_write_en, UInt<1>("h1")), bits(portB_mask, 1, 1)), UInt<1>("h1"))
mem_0_2.portB_clk <= portB_clk
mem_0_2.portB_addr <= portB_addr
node portB_dout_0_2 = bits(mem_0_2.portB_dout, 7, 0)
mem_0_2.portB_din <= bits(portB_din, 23, 16)
mem_0_2.portB_read_en <= and(portB_read_en, UInt<1>("h1"))
mem_0_2.portB_write_en <= and(and(and(portB_write_en, UInt<1>("h1")), bits(portB_mask, 2, 2)), UInt<1>("h1"))
mem_0_3.portB_clk <= portB_clk
mem_0_3.portB_addr <= portB_addr
node portB_dout_0_3 = bits(mem_0_3.portB_dout, 7, 0)
mem_0_3.portB_din <= bits(portB_din, 31, 24)
mem_0_3.portB_read_en <= and(portB_read_en, UInt<1>("h1"))
mem_0_3.portB_write_en <= and(and(and(portB_write_en, UInt<1>("h1")), bits(portB_mask, 3, 3)), UInt<1>("h1"))
mem_0_4.portB_clk <= portB_clk
mem_0_4.portB_addr <= portB_addr
node portB_dout_0_4 = bits(mem_0_4.portB_dout, 7, 0)
mem_0_4.portB_din <= bits(portB_din, 39, 32)
mem_0_4.portB_read_en <= and(portB_read_en, UInt<1>("h1"))
mem_0_4.portB_write_en <= and(and(and(portB_write_en, UInt<1>("h1")), bits(portB_mask, 4, 4)), UInt<1>("h1"))
mem_0_5.portB_clk <= portB_clk
mem_0_5.portB_addr <= portB_addr
node portB_dout_0_5 = bits(mem_0_5.portB_dout, 7, 0)
mem_0_5.portB_din <= bits(portB_din, 47, 40)
mem_0_5.portB_read_en <= and(portB_read_en, UInt<1>("h1"))
mem_0_5.portB_write_en <= and(and(and(portB_write_en, UInt<1>("h1")), bits(portB_mask, 5, 5)), UInt<1>("h1"))
mem_0_6.portB_clk <= portB_clk
mem_0_6.portB_addr <= portB_addr
node portB_dout_0_6 = bits(mem_0_6.portB_dout, 7, 0)
mem_0_6.portB_din <= bits(portB_din, 55, 48)
mem_0_6.portB_read_en <= and(portB_read_en, UInt<1>("h1"))
mem_0_6.portB_write_en <= and(and(and(portB_write_en, UInt<1>("h1")), bits(portB_mask, 6, 6)), UInt<1>("h1"))
mem_0_7.portB_clk <= portB_clk
mem_0_7.portB_addr <= portB_addr
node portB_dout_0_7 = bits(mem_0_7.portB_dout, 7, 0)
mem_0_7.portB_din <= bits(portB_din, 63, 56)
mem_0_7.portB_read_en <= and(portB_read_en, UInt<1>("h1"))
mem_0_7.portB_write_en <= and(and(and(portB_write_en, UInt<1>("h1")), bits(portB_mask, 7, 7)), UInt<1>("h1"))
node portB_dout_0 = cat(portB_dout_0_7, cat(portB_dout_0_6, cat(portB_dout_0_5, cat(portB_dout_0_4, cat(portB_dout_0_3, cat(portB_dout_0_2, cat(portB_dout_0_1, portB_dout_0_0)))))))
portA_dout <= mux(UInt<1>("h1"), portA_dout_0, UInt<64>("h0"))
portB_dout <= mux(UInt<1>("h1"), portB_dout_0, UInt<64>("h0"))
"""
compileExecuteAndTest(mem, lib, v, output)
}

View File

@@ -0,0 +1,21 @@
package barstools.macros
import mdf.macrolib
class SRAMCompiler extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleWidthTestGenerator {
val compiler: macrolib.SRAMCompiler = generateSRAMCompiler("awesome", "A")
val verilog = s"v-SRAMCompiler.v"
override lazy val depth = BigInt(16)
override lazy val memWidth = 8
override lazy val libWidth = 8
override lazy val mem_name = "mymem"
override lazy val memPortPrefix = "X"
override lazy val lib_name = "mygroup_8x16_SVT"
override lazy val libPortPrefix = "A"
writeToLib(lib, Seq(compiler))
writeToMem(mem, Seq(generateSRAM("mymem", "X", 8, 16)))
compileExecuteAndTest(mem, Some(lib), verilog, output = output, useCompiler = true)
}

View File

@@ -0,0 +1,638 @@
package barstools.macros
// Test the depth splitting aspect of the memory compiler.
// This file is for simple tests: one read-write port, powers of two sizes, etc.
// For example, implementing a 4096x32 memory using four 1024x32 memories.
trait HasSimpleDepthTestGenerator extends HasSimpleTestGenerator {
this: MacroCompilerSpec with HasSRAMGenerator =>
def width: Int
override lazy val memWidth: Int = width
override lazy val libWidth: Int = width
// Generate a depth-splitting body.
override def generateBody(): String = {
val output = new StringBuilder
if (selectBits > 0) {
output.append(
s"""
node ${memPortPrefix}_addr_sel = bits(${memPortPrefix}_addr, ${mem_addr_width - 1}, $lib_addr_width)
reg ${memPortPrefix}_addr_sel_reg : UInt<$selectBits>, ${memPortPrefix}_clk with :
reset => (UInt<1>("h0"), ${memPortPrefix}_addr_sel_reg)
${memPortPrefix}_addr_sel_reg <= mux(UInt<1>("h1"), ${memPortPrefix}_addr_sel, ${memPortPrefix}_addr_sel_reg)
"""
)
}
for (i <- 0 until depthInstances) {
val maskStatement = generateMaskStatement(0, i)
val enableIdentifier =
if (selectBits > 0) s"""eq(${memPortPrefix}_addr_sel, UInt<$selectBits>("h${i.toHexString}"))"""
else "UInt<1>(\"h1\")"
val chipEnable = s"""UInt<1>("h1")"""
val writeEnable =
if (memMaskGran.isEmpty) s"and(${memPortPrefix}_write_en, $chipEnable)" else s"${memPortPrefix}_write_en"
output.append(
s"""
inst mem_${i}_0 of $lib_name
mem_${i}_0.${libPortPrefix}_clk <= ${memPortPrefix}_clk
mem_${i}_0.${libPortPrefix}_addr <= ${memPortPrefix}_addr
node ${memPortPrefix}_dout_${i}_0 = bits(mem_${i}_0.${libPortPrefix}_dout, ${width - 1}, 0)
mem_${i}_0.${libPortPrefix}_din <= bits(${memPortPrefix}_din, ${width - 1}, 0)
$maskStatement
mem_${i}_0.${libPortPrefix}_write_en <= and(and($writeEnable, UInt<1>("h1")), $enableIdentifier)
node ${memPortPrefix}_dout_$i = ${memPortPrefix}_dout_${i}_0
"""
)
}
def generate_outer_dout_tree(i: Int, depthInstances: Int): String = {
if (i > depthInstances - 1) {
s"""UInt<$libWidth>("h0")"""
} else {
s"""mux(eq(${memPortPrefix}_addr_sel_reg, UInt<%d>("h%s")), ${memPortPrefix}_dout_%d, %s)""".format(
selectBits,
i.toHexString,
i,
generate_outer_dout_tree(i + 1, depthInstances)
)
}
}
output.append(s" ${memPortPrefix}_dout <= ")
if (selectBits > 0) {
output.append(generate_outer_dout_tree(0, depthInstances))
} else {
output.append(s"""mux(UInt<1>("h1"), ${memPortPrefix}_dout_0, UInt<$libWidth>("h0"))""")
}
output.toString
}
}
// Try different widths
class SplitDepth4096x32_rw extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleDepthTestGenerator {
override lazy val width = 32
override lazy val memDepth = BigInt(4096)
override lazy val libDepth = BigInt(1024)
compileExecuteAndTest(mem, lib, v, output)
}
class SplitDepth4096x16_rw extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleDepthTestGenerator {
override lazy val width = 16
override lazy val memDepth = BigInt(4096)
override lazy val libDepth = BigInt(1024)
compileExecuteAndTest(mem, lib, v, output)
}
class SplitDepth32768x8_rw extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleDepthTestGenerator {
override lazy val width = 8
override lazy val memDepth = BigInt(32768)
override lazy val libDepth = BigInt(1024)
compileExecuteAndTest(mem, lib, v, output)
}
class SplitDepth4096x8_rw extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleDepthTestGenerator {
override lazy val width = 8
override lazy val memDepth = BigInt(4096)
override lazy val libDepth = BigInt(1024)
compileExecuteAndTest(mem, lib, v, output)
}
class SplitDepth2048x8_rw extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleDepthTestGenerator {
override lazy val width = 8
override lazy val memDepth = BigInt(2048)
override lazy val libDepth = BigInt(1024)
compileExecuteAndTest(mem, lib, v, output)
}
class SplitDepth1024x8_rw extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleDepthTestGenerator {
override lazy val width = 8
override lazy val memDepth = BigInt(1024)
override lazy val libDepth = BigInt(1024)
compileExecuteAndTest(mem, lib, v, output)
}
// Non power of two
class SplitDepth2000x8_rw extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleDepthTestGenerator {
override lazy val width = 8
override lazy val memDepth = BigInt(2000)
override lazy val libDepth = BigInt(1024)
compileExecuteAndTest(mem, lib, v, output)
}
class SplitDepth2049x8_rw extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleDepthTestGenerator {
override lazy val width = 8
override lazy val memDepth = BigInt(2049)
override lazy val libDepth = BigInt(1024)
compileExecuteAndTest(mem, lib, v, output)
}
// Masked RAMs
// Test for mem mask == lib mask (i.e. mask is a write enable bit)
class SplitDepth2048x32_mrw_lib32 extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleDepthTestGenerator {
override lazy val width = 32
override lazy val memDepth = BigInt(2048)
override lazy val libDepth = BigInt(1024)
override lazy val memMaskGran: Option[Int] = Some(32)
override lazy val libMaskGran: Option[Int] = Some(32)
compileExecuteAndTest(mem, lib, v, output)
}
class SplitDepth2048x8_mrw_lib8 extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleDepthTestGenerator {
override lazy val width = 8
override lazy val memDepth = BigInt(2048)
override lazy val libDepth = BigInt(1024)
override lazy val memMaskGran: Option[Int] = Some(8)
override lazy val libMaskGran: Option[Int] = Some(8)
compileExecuteAndTest(mem, lib, v, output)
}
// Non-bit level mask
class SplitDepth2048x64_mrw_mem32_lib8
extends MacroCompilerSpec
with HasSRAMGenerator
with HasSimpleDepthTestGenerator {
override lazy val width = 64
override lazy val memDepth = BigInt(2048)
override lazy val libDepth = BigInt(1024)
override lazy val memMaskGran: Option[Int] = Some(32)
override lazy val libMaskGran: Option[Int] = Some(8)
compileExecuteAndTest(mem, lib, v, output)
}
// Bit level mask
class SplitDepth2048x32_mrw_mem16_lib1
extends MacroCompilerSpec
with HasSRAMGenerator
with HasSimpleDepthTestGenerator {
override lazy val width = 32
override lazy val memDepth = BigInt(2048)
override lazy val libDepth = BigInt(1024)
override lazy val memMaskGran: Option[Int] = Some(16)
override lazy val libMaskGran: Option[Int] = Some(1)
compileExecuteAndTest(mem, lib, v, output)
}
class SplitDepth2048x32_mrw_mem8_lib1 extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleDepthTestGenerator {
override lazy val width = 32
override lazy val memDepth = BigInt(2048)
override lazy val libDepth = BigInt(1024)
override lazy val memMaskGran: Option[Int] = Some(8)
override lazy val libMaskGran: Option[Int] = Some(1)
compileExecuteAndTest(mem, lib, v, output)
}
class SplitDepth2048x32_mrw_mem4_lib1 extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleDepthTestGenerator {
override lazy val width = 32
override lazy val memDepth = BigInt(2048)
override lazy val libDepth = BigInt(1024)
override lazy val memMaskGran: Option[Int] = Some(4)
override lazy val libMaskGran: Option[Int] = Some(1)
compileExecuteAndTest(mem, lib, v, output)
}
class SplitDepth2048x32_mrw_mem2_lib1 extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleDepthTestGenerator {
override lazy val width = 32
override lazy val memDepth = BigInt(2048)
override lazy val libDepth = BigInt(1024)
override lazy val memMaskGran: Option[Int] = Some(2)
override lazy val libMaskGran: Option[Int] = Some(1)
compileExecuteAndTest(mem, lib, v, output)
}
// Non-powers of 2 mask sizes
class SplitDepth2048x32_mrw_mem3_lib1 extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleDepthTestGenerator {
override lazy val width = 32
override lazy val memDepth = BigInt(2048)
override lazy val libDepth = BigInt(1024)
override lazy val memMaskGran: Option[Int] = Some(3)
override lazy val libMaskGran: Option[Int] = Some(1)
(it should "be enabled when non-power of two masks are supported").is(pending)
//compileExecuteAndTest(mem, lib, v, output)
}
class SplitDepth2048x32_mrw_mem7_lib1 extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleDepthTestGenerator {
override lazy val width = 32
override lazy val memDepth = BigInt(2048)
override lazy val libDepth = BigInt(1024)
override lazy val memMaskGran: Option[Int] = Some(7)
override lazy val libMaskGran: Option[Int] = Some(1)
(it should "be enabled when non-power of two masks are supported").is(pending)
//compileExecuteAndTest(mem, lib, v, output)
}
class SplitDepth2048x32_mrw_mem9_lib1 extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleDepthTestGenerator {
override lazy val width = 32
override lazy val memDepth = BigInt(2048)
override lazy val libDepth = BigInt(1024)
override lazy val memMaskGran: Option[Int] = Some(9)
override lazy val libMaskGran: Option[Int] = Some(1)
(it should "be enabled when non-power of two masks are supported").is(pending)
//compileExecuteAndTest(mem, lib, v, output)
}
// Try an extra port
class SplitDepth2048x8_extraPort extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleDepthTestGenerator {
import mdf.macrolib._
override lazy val width = 8
override lazy val memDepth = BigInt(2048)
override lazy val libDepth = BigInt(1024)
override lazy val extraPorts = List(
MacroExtraPort(name = "extra_port", width = 8, portType = Constant, value = 0xff)
)
override lazy val extraTag = "extraPort"
override def generateOutput(): String =
"""
circuit target_memory :
module target_memory :
input outer_addr : UInt<11>
input outer_clk : Clock
input outer_din : UInt<8>
output outer_dout : UInt<8>
input outer_write_en : UInt<1>
node outer_addr_sel = bits(outer_addr, 10, 10)
reg outer_addr_sel_reg : UInt<1>, outer_clk with :
reset => (UInt<1>("h0"), outer_addr_sel_reg)
outer_addr_sel_reg <= mux(UInt<1>("h1"), outer_addr_sel, outer_addr_sel_reg)
inst mem_0_0 of awesome_lib_mem
mem_0_0.extra_port <= UInt<8>("hff")
mem_0_0.lib_clk <= outer_clk
mem_0_0.lib_addr <= outer_addr
node outer_dout_0_0 = bits(mem_0_0.lib_dout, 7, 0)
mem_0_0.lib_din <= bits(outer_din, 7, 0)
mem_0_0.lib_write_en <= and(and(and(outer_write_en, UInt<1>("h1")), UInt<1>("h1")), eq(outer_addr_sel, UInt<1>("h0")))
node outer_dout_0 = outer_dout_0_0
inst mem_1_0 of awesome_lib_mem
mem_1_0.extra_port <= UInt<8>("hff")
mem_1_0.lib_clk <= outer_clk
mem_1_0.lib_addr <= outer_addr
node outer_dout_1_0 = bits(mem_1_0.lib_dout, 7, 0)
mem_1_0.lib_din <= bits(outer_din, 7, 0)
mem_1_0.lib_write_en <= and(and(and(outer_write_en, UInt<1>("h1")), UInt<1>("h1")), eq(outer_addr_sel, UInt<1>("h1")))
node outer_dout_1 = outer_dout_1_0
outer_dout <= mux(eq(outer_addr_sel_reg, UInt<1>("h0")), outer_dout_0, mux(eq(outer_addr_sel_reg, UInt<1>("h1")), outer_dout_1, UInt<8>("h0")))
extmodule awesome_lib_mem :
input lib_addr : UInt<10>
input lib_clk : Clock
input lib_din : UInt<8>
output lib_dout : UInt<8>
input lib_write_en : UInt<1>
input extra_port : UInt<8>
defname = awesome_lib_mem
"""
compileExecuteAndTest(mem, lib, v, output)
}
// Split read and (non-masked) write ports (r+w).
class SplitDepth_SplitPortsNonMasked extends MacroCompilerSpec with HasSRAMGenerator {
lazy val width = 8
lazy val memDepth = BigInt(2048)
lazy val libDepth = BigInt(1024)
override val memPrefix: String = testDir
override val libPrefix: String = testDir
import mdf.macrolib._
"Non-masked split lib; split mem" should "split fine" in {
val lib = "lib-split_depth-r-w-split-lib-split-mem.json"
val mem = "mem-split_depth-r-w-split-lib-split-mem.json"
val v = "split_depth-r-w-split-lib-split-mem.v"
val libMacro = SRAMMacro(
name = "awesome_lib_mem",
width = width,
depth = libDepth,
family = "1r1w",
ports = Seq(
generateReadPort("innerA", width, libDepth),
generateWritePort("innerB", width, libDepth)
)
)
val memMacro = SRAMMacro(
name = "target_memory",
width = width,
depth = memDepth,
family = "1r1w",
ports = Seq(
generateReadPort("outerB", width, memDepth),
generateWritePort("outerA", width, memDepth)
)
)
writeToLib(mem, Seq(memMacro))
writeToLib(lib, Seq(libMacro))
val output =
"""
circuit target_memory :
module target_memory :
input outerB_addr : UInt<11>
input outerB_clk : Clock
output outerB_dout : UInt<8>
input outerA_addr : UInt<11>
input outerA_clk : Clock
input outerA_din : UInt<8>
input outerA_write_en : UInt<1>
node outerB_addr_sel = bits(outerB_addr, 10, 10)
reg outerB_addr_sel_reg : UInt<1>, outerB_clk with :
reset => (UInt<1>("h0"), outerB_addr_sel_reg)
outerB_addr_sel_reg <= mux(UInt<1>("h1"), outerB_addr_sel, outerB_addr_sel_reg)
node outerA_addr_sel = bits(outerA_addr, 10, 10)
inst mem_0_0 of awesome_lib_mem
mem_0_0.innerB_clk <= outerA_clk
mem_0_0.innerB_addr <= outerA_addr
mem_0_0.innerB_din <= bits(outerA_din, 7, 0)
mem_0_0.innerB_write_en <= and(and(and(outerA_write_en, UInt<1>("h1")), UInt<1>("h1")), eq(outerA_addr_sel, UInt<1>("h0")))
mem_0_0.innerA_clk <= outerB_clk
mem_0_0.innerA_addr <= outerB_addr
node outerB_dout_0_0 = bits(mem_0_0.innerA_dout, 7, 0)
node outerB_dout_0 = outerB_dout_0_0
inst mem_1_0 of awesome_lib_mem
mem_1_0.innerB_clk <= outerA_clk
mem_1_0.innerB_addr <= outerA_addr
mem_1_0.innerB_din <= bits(outerA_din, 7, 0)
mem_1_0.innerB_write_en <= and(and(and(outerA_write_en, UInt<1>("h1")), UInt<1>("h1")), eq(outerA_addr_sel, UInt<1>("h1")))
mem_1_0.innerA_clk <= outerB_clk
mem_1_0.innerA_addr <= outerB_addr
node outerB_dout_1_0 = bits(mem_1_0.innerA_dout, 7, 0)
node outerB_dout_1 = outerB_dout_1_0
outerB_dout <= mux(eq(outerB_addr_sel_reg, UInt<1>("h0")), outerB_dout_0, mux(eq(outerB_addr_sel_reg, UInt<1>("h1")), outerB_dout_1, UInt<8>("h0")))
extmodule awesome_lib_mem :
input innerA_addr : UInt<10>
input innerA_clk : Clock
output innerA_dout : UInt<8>
input innerB_addr : UInt<10>
input innerB_clk : Clock
input innerB_din : UInt<8>
input innerB_write_en : UInt<1>
defname = awesome_lib_mem
"""
compileExecuteAndTest(mem, lib, v, output)
}
"Non-masked regular lib; split mem" should "split fine" in {
// Enable this test when the memory compiler can compile non-matched
// memories (e.g. mrw mem and r+mw lib).
// Right now all we can get is a "port count must match" error.
pending
val lib = "lib-split_depth-r-w-regular-lib-split-mem.json"
val mem = "mem-split_depth-r-w-regular-lib-split-mem.json"
val v = "split_depth-r-w-regular-lib-split-mem.v"
val memMacro = SRAMMacro(
name = "target_memory",
width = width,
depth = memDepth,
family = "1r1w",
ports = Seq(
generateReadPort("outerB", width, memDepth),
generateWritePort("outerA", width, memDepth)
)
)
writeToLib(mem, Seq(memMacro))
writeToLib(lib, Seq(generateSRAM("awesome_lib_mem", "lib", width, libDepth)))
val output =
"""
TODO
"""
compileExecuteAndTest(mem, lib, v, output)
}
"Non-masked split lib; regular mem" should "split fine" in {
// Enable this test when the memory compiler can compile non-matched
// memories (e.g. mrw mem and r+mw lib).
// Right now all we can get is a "port count must match" error.
// [edwardw]: does this even make sense? Can we compile a 2-ported memory using 1-ported memories?
pending
val lib = "lib-split_depth-r-w-split-lib-regular-mem.json"
val mem = "mem-split_depth-r-w-split-lib-regular-mem.json"
val v = "split_depth-r-w-split-lib-regular-mem.v"
val libMacro = SRAMMacro(
name = "awesome_lib_mem",
width = width,
depth = libDepth,
family = "1rw",
ports = Seq(
generateReadPort("innerA", width, libDepth),
generateWritePort("innerB", width, libDepth)
)
)
writeToLib(mem, Seq(generateSRAM("target_memory", "outer", width, memDepth)))
writeToLib(lib, Seq(libMacro))
val output =
"""
TODO
"""
compileExecuteAndTest(mem, lib, v, output)
}
}
// Split read and (masked) write ports (r+mw).
class SplitDepth_SplitPortsMasked extends MacroCompilerSpec with HasSRAMGenerator {
lazy val width = 8
lazy val memDepth = BigInt(2048)
lazy val libDepth = BigInt(1024)
lazy val memMaskGran: Option[Int] = Some(8)
lazy val libMaskGran: Option[Int] = Some(1)
override val memPrefix: String = testDir
override val libPrefix: String = testDir
import mdf.macrolib._
"Masked split lib; split mem" should "split fine" in {
val lib = "lib-split_depth-r-mw-split-lib-split-mem.json"
val mem = "mem-split_depth-r-mw-split-lib-split-mem.json"
val v = "split_depth-r-mw-split-lib-split-mem.v"
val libMacro = SRAMMacro(
name = "awesome_lib_mem",
width = width,
depth = libDepth,
family = "1r1w",
ports = Seq(
generateReadPort("innerA", width, libDepth),
generateWritePort("innerB", width, libDepth, libMaskGran)
)
)
val memMacro = SRAMMacro(
name = "target_memory",
width = width,
depth = memDepth,
family = "1r1w",
ports = Seq(
generateReadPort("outerB", width, memDepth),
generateWritePort("outerA", width, memDepth, memMaskGran)
)
)
writeToLib(mem, Seq(memMacro))
writeToLib(lib, Seq(libMacro))
val output =
"""
circuit target_memory :
module target_memory :
input outerB_addr : UInt<11>
input outerB_clk : Clock
output outerB_dout : UInt<8>
input outerA_addr : UInt<11>
input outerA_clk : Clock
input outerA_din : UInt<8>
input outerA_write_en : UInt<1>
input outerA_mask : UInt<1>
node outerB_addr_sel = bits(outerB_addr, 10, 10)
reg outerB_addr_sel_reg : UInt<1>, outerB_clk with :
reset => (UInt<1>("h0"), outerB_addr_sel_reg)
outerB_addr_sel_reg <= mux(UInt<1>("h1"), outerB_addr_sel, outerB_addr_sel_reg)
node outerA_addr_sel = bits(outerA_addr, 10, 10)
inst mem_0_0 of awesome_lib_mem
mem_0_0.innerB_clk <= outerA_clk
mem_0_0.innerB_addr <= outerA_addr
mem_0_0.innerB_din <= bits(outerA_din, 7, 0)
mem_0_0.innerB_mask <= cat(bits(outerA_mask, 0, 0), cat(bits(outerA_mask, 0, 0), cat(bits(outerA_mask, 0, 0), cat(bits(outerA_mask, 0, 0), cat(bits(outerA_mask, 0, 0), cat(bits(outerA_mask, 0, 0), cat(bits(outerA_mask, 0, 0), bits(outerA_mask, 0, 0))))))))
mem_0_0.innerB_write_en <= and(and(outerA_write_en, UInt<1>("h1")), eq(outerA_addr_sel, UInt<1>("h0")))
mem_0_0.innerA_clk <= outerB_clk
mem_0_0.innerA_addr <= outerB_addr
node outerB_dout_0_0 = bits(mem_0_0.innerA_dout, 7, 0)
node outerB_dout_0 = outerB_dout_0_0
inst mem_1_0 of awesome_lib_mem
mem_1_0.innerB_clk <= outerA_clk
mem_1_0.innerB_addr <= outerA_addr
mem_1_0.innerB_din <= bits(outerA_din, 7, 0)
mem_1_0.innerB_mask <= cat(bits(outerA_mask, 0, 0), cat(bits(outerA_mask, 0, 0), cat(bits(outerA_mask, 0, 0), cat(bits(outerA_mask, 0, 0), cat(bits(outerA_mask, 0, 0), cat(bits(outerA_mask, 0, 0), cat(bits(outerA_mask, 0, 0), bits(outerA_mask, 0, 0))))))))
mem_1_0.innerB_write_en <= and(and(outerA_write_en, UInt<1>("h1")), eq(outerA_addr_sel, UInt<1>("h1")))
mem_1_0.innerA_clk <= outerB_clk
mem_1_0.innerA_addr <= outerB_addr
node outerB_dout_1_0 = bits(mem_1_0.innerA_dout, 7, 0)
node outerB_dout_1 = outerB_dout_1_0
outerB_dout <= mux(eq(outerB_addr_sel_reg, UInt<1>("h0")), outerB_dout_0, mux(eq(outerB_addr_sel_reg, UInt<1>("h1")), outerB_dout_1, UInt<8>("h0")))
extmodule awesome_lib_mem :
input innerA_addr : UInt<10>
input innerA_clk : Clock
output innerA_dout : UInt<8>
input innerB_addr : UInt<10>
input innerB_clk : Clock
input innerB_din : UInt<8>
input innerB_write_en : UInt<1>
input innerB_mask : UInt<8>
defname = awesome_lib_mem
"""
compileExecuteAndTest(mem, lib, v, output)
}
"Non-masked regular lib; split mem" should "split fine" in {
// Enable this test when the memory compiler can compile non-matched
// memories (e.g. mrw mem and r+mw lib).
// Right now all we can get is a "port count must match" error.
pending
val lib = "lib-split_depth-r-mw-regular-lib-split-mem.json"
val mem = "mem-split_depth-r-mw-regular-lib-split-mem.json"
val v = "split_depth-r-mw-regular-lib-split-mem.v"
val memMacro = SRAMMacro(
name = "target_memory",
width = width,
depth = memDepth,
family = "1r1w",
ports = Seq(
generateReadPort("outerB", width, memDepth),
generateWritePort("outerA", width, memDepth, memMaskGran)
)
)
writeToLib(mem, Seq(memMacro))
writeToLib(lib, Seq(generateSRAM("awesome_lib_mem", "lib", width, libDepth, libMaskGran)))
val output =
"""
TODO
"""
compileExecuteAndTest(mem, lib, v, output)
}
"Non-masked split lib; regular mem" should "split fine" in {
// Enable this test when the memory compiler can compile non-matched
// memories (e.g. mrw mem and r+mw lib).
// Right now all we can get is a "port count must match" error.
// [edwardw]: does this even make sense? Can we compile a 2-ported memory using 1-ported memories?
pending
val lib = "lib-split_depth-r-mw-split-lib-regular-mem.json"
val mem = "mem-split_depth-r-mw-split-lib-regular-mem.json"
val v = "split_depth-r-mw-split-lib-regular-mem.v"
val libMacro = SRAMMacro(
name = "awesome_lib_mem",
width = width,
depth = libDepth,
family = "1rw",
ports = Seq(
generateReadPort("innerA", width, libDepth),
generateWritePort("innerB", width, libDepth, libMaskGran)
)
)
writeToLib(mem, Seq(generateSRAM("target_memory", "outer", width, memDepth, memMaskGran)))
writeToLib(lib, Seq(libMacro))
val output =
"""
TODO
"""
compileExecuteAndTest(mem, lib, v, output)
}
}

View File

@@ -0,0 +1,608 @@
package barstools.macros
// Test the width splitting aspect of the memory compiler.
// For example, implementing a 1024x32 memory using four 1024x8 memories.
trait HasSimpleWidthTestGenerator extends HasSimpleTestGenerator {
this: MacroCompilerSpec with HasSRAMGenerator =>
def depth: BigInt
override lazy val memDepth: BigInt = depth
override lazy val libDepth: BigInt = depth
override def generateBody(): String = {
val output = new StringBuilder
// Generate mem_0_<i> lines for number of width instances.
output.append(
(0 until widthInstances).map { i: Int =>
s"""
inst mem_0_$i of $lib_name
"""
}.reduceLeft(_ + _)
)
// Generate submemory connection blocks.
output.append((for (i <- 0 until widthInstances) yield {
// Width of this submemory.
val myMemWidth = if (i == widthInstances - 1) lastWidthBits else usableLibWidth
// Base bit of this submemory.
// e.g. if libWidth is 8 and this is submemory 2 (0-indexed), then this
// would be 16.
val myBaseBit = usableLibWidth * i
val maskStatement = generateMaskStatement(i, 0)
// We need to use writeEnable as a crude "mask" if mem has a mask but
// lib does not.
val writeEnableBit = if (libMaskGran.isEmpty && memMaskGran.isDefined) {
val outerMaskBit = myBaseBit / memMaskGran.get
s"bits(outer_mask, $outerMaskBit, $outerMaskBit)"
} else """UInt<1>("h1")"""
val chipEnable = s"""UInt<1>("h1")"""
val writeEnableExpr =
if (libMaskGran.isEmpty) s"and(${memPortPrefix}_write_en, $chipEnable)" else s"${memPortPrefix}_write_en"
s"""
mem_0_$i.${libPortPrefix}_clk <= ${memPortPrefix}_clk
mem_0_$i.${libPortPrefix}_addr <= ${memPortPrefix}_addr
node ${memPortPrefix}_dout_0_$i = bits(mem_0_$i.${libPortPrefix}_dout, ${myMemWidth - 1}, 0)
mem_0_$i.${libPortPrefix}_din <= bits(${memPortPrefix}_din, ${myBaseBit + myMemWidth - 1}, $myBaseBit)
$maskStatement
mem_0_$i.${libPortPrefix}_write_en <= and(and($writeEnableExpr, $writeEnableBit), UInt<1>("h1"))
"""
}).reduceLeft(_ + _))
// Generate final output that concats together the sub-memories.
// e.g. cat(outer_dout_0_2, cat(outer_dout_0_1, outer_dout_0_0))
output.append {
val doutStatements = (widthInstances - 1 to 0 by -1).map(i => s"${memPortPrefix}_dout_0_$i")
val catStmt = doutStatements.init.foldRight(doutStatements.last)((l: String, r: String) => s"cat($l, $r)")
s"""
node ${memPortPrefix}_dout_0 = $catStmt
"""
}
output.append(s"""
${memPortPrefix}_dout <= mux(UInt<1>("h1"), ${memPortPrefix}_dout_0, UInt<$memWidth>("h0"))
""")
output.toString
}
}
// Try different widths against a base memory width of 8.
class SplitWidth1024x128_rw extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleWidthTestGenerator {
override lazy val depth = BigInt(1024)
override lazy val memWidth = 128
override lazy val libWidth = 8
compileExecuteAndTest(mem, lib, v, output)
}
class SplitWidth1024x64_rw extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleWidthTestGenerator {
override lazy val depth = BigInt(1024)
override lazy val memWidth = 64
override lazy val libWidth = 8
compileExecuteAndTest(mem, lib, v, output)
}
class SplitWidth1024x32_rw extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleWidthTestGenerator {
override lazy val depth = BigInt(1024)
override lazy val memWidth = 32
override lazy val libWidth = 8
compileExecuteAndTest(mem, lib, v, output)
}
class SplitWidth1024x16_rw extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleWidthTestGenerator {
override lazy val depth = BigInt(1024)
override lazy val memWidth = 16
override lazy val libWidth = 8
compileExecuteAndTest(mem, lib, v, output)
}
class SplitWidth1024x8_rw extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleWidthTestGenerator {
override lazy val depth = BigInt(1024)
override lazy val memWidth = 8
override lazy val libWidth = 8
compileExecuteAndTest(mem, lib, v, output)
}
// Try different widths against a base memory width of 16.
class SplitWidth1024x128_lib16_rw extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleWidthTestGenerator {
override lazy val depth = BigInt(1024)
override lazy val memWidth = 128
override lazy val libWidth = 16
compileExecuteAndTest(mem, lib, v, output)
}
class SplitWidth1024x64_lib16_rw extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleWidthTestGenerator {
override lazy val depth = BigInt(1024)
override lazy val memWidth = 64
override lazy val libWidth = 16
compileExecuteAndTest(mem, lib, v, output)
}
class SplitWidth1024x32_lib16_rw extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleWidthTestGenerator {
override lazy val depth = BigInt(1024)
override lazy val memWidth = 32
override lazy val libWidth = 16
compileExecuteAndTest(mem, lib, v, output)
}
class SplitWidth1024x16_lib16_rw extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleWidthTestGenerator {
override lazy val depth = BigInt(1024)
override lazy val memWidth = 16
override lazy val libWidth = 16
compileExecuteAndTest(mem, lib, v, output)
}
// Try different widths against a base memory width of 8 but depth 512 instead of 1024.
class SplitWidth512x128_rw extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleWidthTestGenerator {
override lazy val depth = BigInt(512)
override lazy val memWidth = 128
override lazy val libWidth = 8
compileExecuteAndTest(mem, lib, v, output)
}
class SplitWidth512x64_rw extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleWidthTestGenerator {
override lazy val depth = BigInt(512)
override lazy val memWidth = 64
override lazy val libWidth = 8
compileExecuteAndTest(mem, lib, v, output)
}
class SplitWidth512x32_rw extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleWidthTestGenerator {
override lazy val depth = BigInt(512)
override lazy val memWidth = 32
override lazy val libWidth = 8
compileExecuteAndTest(mem, lib, v, output)
}
class SplitWidth512x16_rw extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleWidthTestGenerator {
override lazy val depth = BigInt(512)
override lazy val memWidth = 16
override lazy val libWidth = 8
compileExecuteAndTest(mem, lib, v, output)
}
class SplitWidth512x8_rw extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleWidthTestGenerator {
override lazy val depth = BigInt(512)
override lazy val memWidth = 8
override lazy val libWidth = 8
compileExecuteAndTest(mem, lib, v, output)
}
// Try non-power of two widths against a base memory width of 8.
class SplitWidth1024x67_rw extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleWidthTestGenerator {
override lazy val depth = BigInt(1024)
override lazy val memWidth = 67
override lazy val libWidth = 8
compileExecuteAndTest(mem, lib, v, output)
}
class SplitWidth1024x60_rw extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleWidthTestGenerator {
override lazy val depth = BigInt(1024)
override lazy val memWidth = 60
override lazy val libWidth = 8
compileExecuteAndTest(mem, lib, v, output)
}
class SplitWidth1024x42_rw extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleWidthTestGenerator {
override lazy val depth = BigInt(1024)
override lazy val memWidth = 42
override lazy val libWidth = 8
compileExecuteAndTest(mem, lib, v, output)
}
class SplitWidth1024x20_rw extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleWidthTestGenerator {
override lazy val depth = BigInt(1024)
override lazy val memWidth = 20
override lazy val libWidth = 8
compileExecuteAndTest(mem, lib, v, output)
}
class SplitWidth1024x17_rw extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleWidthTestGenerator {
override lazy val depth = BigInt(1024)
override lazy val memWidth = 17
override lazy val libWidth = 8
compileExecuteAndTest(mem, lib, v, output)
}
class SplitWidth1024x15_rw extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleWidthTestGenerator {
override lazy val depth = BigInt(1024)
override lazy val memWidth = 15
override lazy val libWidth = 8
compileExecuteAndTest(mem, lib, v, output)
}
class SplitWidth1024x9_rw extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleWidthTestGenerator {
override lazy val depth = BigInt(1024)
override lazy val memWidth = 9
override lazy val libWidth = 8
compileExecuteAndTest(mem, lib, v, output)
}
// Try against a non-power of two base memory width.
class SplitWidth1024x64_mem11_rw extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleWidthTestGenerator {
override lazy val depth = BigInt(1024)
override lazy val memWidth = 64
override lazy val libWidth = 11
compileExecuteAndTest(mem, lib, v, output)
}
class SplitWidth1024x33_mem11_rw extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleWidthTestGenerator {
override lazy val depth = BigInt(1024)
override lazy val memWidth = 33
override lazy val libWidth = 11
compileExecuteAndTest(mem, lib, v, output)
}
class SplitWidth1024x16_mem11_rw extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleWidthTestGenerator {
override lazy val depth = BigInt(1024)
override lazy val memWidth = 16
override lazy val libWidth = 11
compileExecuteAndTest(mem, lib, v, output)
}
// Masked RAM
class SplitWidth1024x8_memGran_8_libGran_1_rw
extends MacroCompilerSpec
with HasSRAMGenerator
with HasSimpleWidthTestGenerator {
override lazy val depth = BigInt(1024)
override lazy val memWidth = 8
override lazy val libWidth = 8
override lazy val memMaskGran: Option[Int] = Some(8)
override lazy val libMaskGran: Option[Int] = Some(1)
compileExecuteAndTest(mem, lib, v, output)
}
class SplitWidth1024x16_memGran_8_libGran_1_rw
extends MacroCompilerSpec
with HasSRAMGenerator
with HasSimpleWidthTestGenerator {
override lazy val depth = BigInt(1024)
override lazy val memWidth = 16
override lazy val libWidth = 8
override lazy val memMaskGran: Option[Int] = Some(8)
override lazy val libMaskGran: Option[Int] = Some(1)
compileExecuteAndTest(mem, lib, v, output)
}
class SplitWidth1024x16_memGran_8_libGran_8_rw
extends MacroCompilerSpec
with HasSRAMGenerator
with HasSimpleWidthTestGenerator {
override lazy val depth = BigInt(1024)
override lazy val memWidth = 16
override lazy val libWidth = 8
override lazy val memMaskGran: Option[Int] = Some(8)
override lazy val libMaskGran: Option[Int] = Some(8)
compileExecuteAndTest(mem, lib, v, output)
}
class SplitWidth1024x128_memGran_8_libGran_1_rw
extends MacroCompilerSpec
with HasSRAMGenerator
with HasSimpleWidthTestGenerator {
override lazy val depth = BigInt(1024)
override lazy val memWidth = 128
override lazy val libWidth = 32
override lazy val memMaskGran: Option[Int] = Some(8)
override lazy val libMaskGran: Option[Int] = Some(1)
compileExecuteAndTest(mem, lib, v, output)
}
class SplitWidth1024x16_memGran_4_libGran_1_rw
extends MacroCompilerSpec
with HasSRAMGenerator
with HasSimpleWidthTestGenerator {
override lazy val depth = BigInt(1024)
override lazy val memWidth = 16
override lazy val libWidth = 8
override lazy val memMaskGran: Option[Int] = Some(4)
override lazy val libMaskGran: Option[Int] = Some(1)
compileExecuteAndTest(mem, lib, v, output)
}
class SplitWidth1024x16_memGran_2_libGran_1_rw
extends MacroCompilerSpec
with HasSRAMGenerator
with HasSimpleWidthTestGenerator {
override lazy val depth = BigInt(1024)
override lazy val memWidth = 16
override lazy val libWidth = 8
override lazy val memMaskGran: Option[Int] = Some(2)
override lazy val libMaskGran: Option[Int] = Some(1)
compileExecuteAndTest(mem, lib, v, output)
}
class SplitWidth1024x16_memGran_16_libGran_1_rw
extends MacroCompilerSpec
with HasSRAMGenerator
with HasSimpleWidthTestGenerator {
override lazy val depth = BigInt(1024)
override lazy val memWidth = 16
override lazy val libWidth = 8
override lazy val memMaskGran: Option[Int] = Some(16)
override lazy val libMaskGran: Option[Int] = Some(1)
compileExecuteAndTest(mem, lib, v, output)
}
// Non-masked mem, masked lib
class SplitWidth1024x16_libGran_8_rw extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleWidthTestGenerator {
override lazy val depth = BigInt(1024)
override lazy val memWidth = 16
override lazy val libWidth = 8
override lazy val libMaskGran: Option[Int] = Some(8)
compileExecuteAndTest(mem, lib, v, output)
}
class SplitWidth1024x16_libGran_1_rw extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleWidthTestGenerator {
override lazy val depth = BigInt(1024)
override lazy val memWidth = 16
override lazy val libWidth = 8
override lazy val libMaskGran: Option[Int] = Some(1)
compileExecuteAndTest(mem, lib, v, output)
}
// Non-memMask and non-1 libMask
class SplitWidth1024x16_memGran_8_libGran_2_rw
extends MacroCompilerSpec
with HasSRAMGenerator
with HasSimpleWidthTestGenerator {
override lazy val depth = BigInt(1024)
override lazy val memWidth = 16
override lazy val libWidth = 8
override lazy val memMaskGran: Option[Int] = Some(8)
override lazy val libMaskGran: Option[Int] = Some(2)
compileExecuteAndTest(mem, lib, v, output)
}
// Non-power of two memGran
class SplitWidth1024x16_memGran_9_libGran_1_rw
extends MacroCompilerSpec
with HasSRAMGenerator
with HasSimpleWidthTestGenerator {
override lazy val depth = BigInt(1024)
override lazy val memWidth = 16
override lazy val libWidth = 8
override lazy val memMaskGran: Option[Int] = Some(9)
override lazy val libMaskGran: Option[Int] = Some(1)
(it should "be enabled when non-power of two masks are supported").is(pending)
//~ compile(mem, lib, v, false)
//~ execute(mem, lib, false, output)
}
// Read enable
class SplitWidth1024x32_readEnable_Lib
extends MacroCompilerSpec
with HasSRAMGenerator
with HasSimpleWidthTestGenerator {
import mdf.macrolib._
override lazy val depth = BigInt(1024)
override lazy val memWidth = 32
override lazy val libWidth = 8
override def generateLibSRAM(): SRAMMacro = {
SRAMMacro(
name = lib_name,
width = libWidth,
depth = libDepth,
family = "1rw",
ports = Seq(
generateTestPort(
"lib",
Some(libWidth),
Some(libDepth),
maskGran = libMaskGran,
write = true,
writeEnable = true,
read = true,
readEnable = true
)
)
)
}
override def generateBody() =
"""
inst mem_0_0 of awesome_lib_mem
inst mem_0_1 of awesome_lib_mem
inst mem_0_2 of awesome_lib_mem
inst mem_0_3 of awesome_lib_mem
mem_0_0.lib_clk <= outer_clk
mem_0_0.lib_addr <= outer_addr
node outer_dout_0_0 = bits(mem_0_0.lib_dout, 7, 0)
mem_0_0.lib_din <= bits(outer_din, 7, 0)
mem_0_0.lib_read_en <= and(and(not(outer_write_en), UInt<1>("h1")), UInt<1>("h1"))
mem_0_0.lib_write_en <= and(and(and(outer_write_en, UInt<1>("h1")), UInt<1>("h1")), UInt<1>("h1"))
mem_0_1.lib_clk <= outer_clk
mem_0_1.lib_addr <= outer_addr
node outer_dout_0_1 = bits(mem_0_1.lib_dout, 7, 0)
mem_0_1.lib_din <= bits(outer_din, 15, 8)
mem_0_1.lib_read_en <= and(and(not(outer_write_en), UInt<1>("h1")), UInt<1>("h1"))
mem_0_1.lib_write_en <= and(and(and(outer_write_en, UInt<1>("h1")), UInt<1>("h1")), UInt<1>("h1"))
mem_0_2.lib_clk <= outer_clk
mem_0_2.lib_addr <= outer_addr
node outer_dout_0_2 = bits(mem_0_2.lib_dout, 7, 0)
mem_0_2.lib_din <= bits(outer_din, 23, 16)
mem_0_2.lib_read_en <= and(and(not(outer_write_en), UInt<1>("h1")), UInt<1>("h1"))
mem_0_2.lib_write_en <= and(and(and(outer_write_en, UInt<1>("h1")), UInt<1>("h1")), UInt<1>("h1"))
mem_0_3.lib_clk <= outer_clk
mem_0_3.lib_addr <= outer_addr
node outer_dout_0_3 = bits(mem_0_3.lib_dout, 7, 0)
mem_0_3.lib_din <= bits(outer_din, 31, 24)
mem_0_3.lib_read_en <= and(and(not(outer_write_en), UInt<1>("h1")), UInt<1>("h1"))
mem_0_3.lib_write_en <= and(and(and(outer_write_en, UInt<1>("h1")), UInt<1>("h1")), UInt<1>("h1"))
node outer_dout_0 = cat(outer_dout_0_3, cat(outer_dout_0_2, cat(outer_dout_0_1, outer_dout_0_0)))
outer_dout <= mux(UInt<1>("h1"), outer_dout_0, UInt<32>("h0"))
"""
compileExecuteAndTest(mem, lib, v, output)
}
class SplitWidth1024x32_readEnable_Mem
extends MacroCompilerSpec
with HasSRAMGenerator
with HasSimpleWidthTestGenerator {
import mdf.macrolib._
override lazy val depth = BigInt(1024)
override lazy val memWidth = 32
override lazy val libWidth = 8
override def generateMemSRAM(): SRAMMacro = {
SRAMMacro(
name = mem_name,
width = memWidth,
depth = memDepth,
family = "1rw",
ports = Seq(
generateTestPort(
"outer",
Some(memWidth),
Some(memDepth),
maskGran = memMaskGran,
write = true,
writeEnable = true,
read = true,
readEnable = true
)
)
)
}
// No need to override body here due to the lack of a readEnable in the lib.
compileExecuteAndTest(mem, lib, v, output)
}
class SplitWidth1024x32_readEnable_LibMem
extends MacroCompilerSpec
with HasSRAMGenerator
with HasSimpleWidthTestGenerator {
import mdf.macrolib._
override lazy val depth = BigInt(1024)
override lazy val memWidth = 32
override lazy val libWidth = 8
override def generateLibSRAM(): SRAMMacro = {
SRAMMacro(
name = lib_name,
width = libWidth,
depth = libDepth,
family = "1rw",
ports = Seq(
generateTestPort(
"lib",
Some(libWidth),
Some(libDepth),
maskGran = libMaskGran,
write = true,
writeEnable = true,
read = true,
readEnable = true
)
)
)
}
override def generateMemSRAM(): SRAMMacro = {
SRAMMacro(
name = mem_name,
width = memWidth,
depth = memDepth,
family = "1rw",
ports = Seq(
generateTestPort(
"outer",
Some(memWidth),
Some(memDepth),
maskGran = memMaskGran,
write = true,
writeEnable = true,
read = true,
readEnable = true
)
)
)
}
override def generateBody() =
"""
inst mem_0_0 of awesome_lib_mem
inst mem_0_1 of awesome_lib_mem
inst mem_0_2 of awesome_lib_mem
inst mem_0_3 of awesome_lib_mem
mem_0_0.lib_clk <= outer_clk
mem_0_0.lib_addr <= outer_addr
node outer_dout_0_0 = bits(mem_0_0.lib_dout, 7, 0)
mem_0_0.lib_din <= bits(outer_din, 7, 0)
mem_0_0.lib_read_en <= and(outer_read_en, UInt<1>("h1"))
mem_0_0.lib_write_en <= and(and(and(outer_write_en, UInt<1>("h1")), UInt<1>("h1")), UInt<1>("h1"))
mem_0_1.lib_clk <= outer_clk
mem_0_1.lib_addr <= outer_addr
node outer_dout_0_1 = bits(mem_0_1.lib_dout, 7, 0)
mem_0_1.lib_din <= bits(outer_din, 15, 8)
mem_0_1.lib_read_en <= and(outer_read_en, UInt<1>("h1"))
mem_0_1.lib_write_en <= and(and(and(outer_write_en, UInt<1>("h1")), UInt<1>("h1")), UInt<1>("h1"))
mem_0_2.lib_clk <= outer_clk
mem_0_2.lib_addr <= outer_addr
node outer_dout_0_2 = bits(mem_0_2.lib_dout, 7, 0)
mem_0_2.lib_din <= bits(outer_din, 23, 16)
mem_0_2.lib_read_en <= and(outer_read_en, UInt<1>("h1"))
mem_0_2.lib_write_en <= and(and(and(outer_write_en, UInt<1>("h1")), UInt<1>("h1")), UInt<1>("h1"))
mem_0_3.lib_clk <= outer_clk
mem_0_3.lib_addr <= outer_addr
node outer_dout_0_3 = bits(mem_0_3.lib_dout, 7, 0)
mem_0_3.lib_din <= bits(outer_din, 31, 24)
mem_0_3.lib_read_en <= and(outer_read_en, UInt<1>("h1"))
mem_0_3.lib_write_en <= and(and(and(outer_write_en, UInt<1>("h1")), UInt<1>("h1")), UInt<1>("h1"))
node outer_dout_0 = cat(outer_dout_0_3, cat(outer_dout_0_2, cat(outer_dout_0_1, outer_dout_0_0)))
outer_dout <= mux(UInt<1>("h1"), outer_dout_0, UInt<32>("h0"))
"""
compileExecuteAndTest(mem, lib, v, output)
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,455 @@
package barstools.macros
// Test flop synthesis of the memory compiler.
trait HasSynFlopsTestGenerator extends HasSimpleTestGenerator {
this: MacroCompilerSpec with HasSRAMGenerator =>
def generateFlops(): String = {
s"""
inst mem_0_0 of split_$lib_name
mem_0_0.${libPortPrefix}_clk <= ${libPortPrefix}_clk
mem_0_0.${libPortPrefix}_addr <= ${libPortPrefix}_addr
node ${libPortPrefix}_dout_0_0 = bits(mem_0_0.${libPortPrefix}_dout, ${libWidth - 1}, 0)
mem_0_0.${libPortPrefix}_din <= bits(${libPortPrefix}_din, ${libWidth - 1}, 0)
mem_0_0.${libPortPrefix}_write_en <= and(and(and(${libPortPrefix}_write_en, UInt<1>("h1")), UInt<1>("h1")), UInt<1>("h1"))
node ${libPortPrefix}_dout_0 = ${libPortPrefix}_dout_0_0
${libPortPrefix}_dout <= mux(UInt<1>("h1"), ${libPortPrefix}_dout_0, UInt<$libWidth>("h0"))
module split_$lib_name :
input ${libPortPrefix}_addr : UInt<$lib_addr_width>
input ${libPortPrefix}_clk : Clock
input ${libPortPrefix}_din : UInt<$libWidth>
output ${libPortPrefix}_dout : UInt<$libWidth>
input ${libPortPrefix}_write_en : UInt<1>
mem ram :
data-type => UInt<$libWidth>
depth => $libDepth
read-latency => 1
write-latency => 1
readwriter => RW_0
read-under-write => undefined
ram.RW_0.clk <= ${libPortPrefix}_clk
ram.RW_0.addr <= ${libPortPrefix}_addr
ram.RW_0.en <= UInt<1>("h1")
ram.RW_0.wmode <= ${libPortPrefix}_write_en
ram.RW_0.wmask <= UInt<1>("h1")
${libPortPrefix}_dout <= ram.RW_0.rdata
ram.RW_0.wdata <= ${libPortPrefix}_din
"""
}
// If there is no lib, put the flops definition into the body.
abstract override def generateBody(): String = {
if (this.isInstanceOf[HasNoLibTestGenerator]) {
generateFlops()
} else {
super.generateBody()
}
}
// If there is no lib, don't generate a footer, since the flops definition
// will be in the body.
override def generateFooter(): String = {
if (this.isInstanceOf[HasNoLibTestGenerator]) ""
else
s"""
module $lib_name :
${generateFooterPorts()}
${generateFlops()}
"""
}
}
class Synflops2048x8_noLib
extends MacroCompilerSpec
with HasSRAMGenerator
with HasNoLibTestGenerator
with HasSynFlopsTestGenerator {
override lazy val memDepth = BigInt(2048)
override lazy val memWidth = 8
compileExecuteAndTest(mem, None, v, output, synflops = true)
}
class Synflops2048x16_noLib
extends MacroCompilerSpec
with HasSRAMGenerator
with HasNoLibTestGenerator
with HasSynFlopsTestGenerator {
override lazy val memDepth = BigInt(2048)
override lazy val memWidth = 16
compileExecuteAndTest(mem, None, v, output, synflops = true)
}
class Synflops8192x16_noLib
extends MacroCompilerSpec
with HasSRAMGenerator
with HasNoLibTestGenerator
with HasSynFlopsTestGenerator {
override lazy val memDepth = BigInt(8192)
override lazy val memWidth = 16
compileExecuteAndTest(mem, None, v, output, synflops = true)
}
class Synflops2048x16_depth_Lib
extends MacroCompilerSpec
with HasSRAMGenerator
with HasSimpleDepthTestGenerator
with HasSynFlopsTestGenerator {
override lazy val memDepth = BigInt(2048)
override lazy val libDepth = BigInt(1024)
override lazy val width = 16
compileExecuteAndTest(mem, lib, v, output, synflops = true)
}
class Synflops2048x64_width_Lib
extends MacroCompilerSpec
with HasSRAMGenerator
with HasSimpleWidthTestGenerator
with HasSynFlopsTestGenerator {
override lazy val memWidth = 64
override lazy val libWidth = 8
override lazy val depth = BigInt(1024)
compileExecuteAndTest(mem, lib, v, output, synflops = true)
}
class Synflops_SplitPorts_Read_Write
extends MacroCompilerSpec
with HasSRAMGenerator
with HasSimpleDepthTestGenerator
with HasSynFlopsTestGenerator {
import mdf.macrolib._
override lazy val memDepth = BigInt(2048)
override lazy val libDepth = BigInt(1024)
override lazy val width = 8
override def generateLibSRAM(): SRAMMacro = SRAMMacro(
name = lib_name,
width = width,
depth = libDepth,
family = "1r1w",
ports = Seq(
generateReadPort("innerA", width, libDepth),
generateWritePort("innerB", width, libDepth)
)
)
override def generateMemSRAM(): SRAMMacro = SRAMMacro(
name = mem_name,
width = width,
depth = memDepth,
family = "1r1w",
ports = Seq(
generateReadPort("outerB", width, memDepth),
generateWritePort("outerA", width, memDepth)
)
)
override def generateHeader() =
"""
circuit target_memory :
module target_memory :
input outerB_addr : UInt<11>
input outerB_clk : Clock
output outerB_dout : UInt<8>
input outerA_addr : UInt<11>
input outerA_clk : Clock
input outerA_din : UInt<8>
input outerA_write_en : UInt<1>
"""
override def generateBody() =
"""
node outerB_addr_sel = bits(outerB_addr, 10, 10)
reg outerB_addr_sel_reg : UInt<1>, outerB_clk with :
reset => (UInt<1>("h0"), outerB_addr_sel_reg)
outerB_addr_sel_reg <= mux(UInt<1>("h1"), outerB_addr_sel, outerB_addr_sel_reg)
node outerA_addr_sel = bits(outerA_addr, 10, 10)
inst mem_0_0 of awesome_lib_mem
mem_0_0.innerB_clk <= outerA_clk
mem_0_0.innerB_addr <= outerA_addr
mem_0_0.innerB_din <= bits(outerA_din, 7, 0)
mem_0_0.innerB_write_en <= and(and(and(outerA_write_en, UInt<1>("h1")), UInt<1>("h1")), eq(outerA_addr_sel, UInt<1>("h0")))
mem_0_0.innerA_clk <= outerB_clk
mem_0_0.innerA_addr <= outerB_addr
node outerB_dout_0_0 = bits(mem_0_0.innerA_dout, 7, 0)
node outerB_dout_0 = outerB_dout_0_0
inst mem_1_0 of awesome_lib_mem
mem_1_0.innerB_clk <= outerA_clk
mem_1_0.innerB_addr <= outerA_addr
mem_1_0.innerB_din <= bits(outerA_din, 7, 0)
mem_1_0.innerB_write_en <= and(and(and(outerA_write_en, UInt<1>("h1")), UInt<1>("h1")), eq(outerA_addr_sel, UInt<1>("h1")))
mem_1_0.innerA_clk <= outerB_clk
mem_1_0.innerA_addr <= outerB_addr
node outerB_dout_1_0 = bits(mem_1_0.innerA_dout, 7, 0)
node outerB_dout_1 = outerB_dout_1_0
outerB_dout <= mux(eq(outerB_addr_sel_reg, UInt<1>("h0")), outerB_dout_0, mux(eq(outerB_addr_sel_reg, UInt<1>("h1")), outerB_dout_1, UInt<8>("h0")))
"""
override def generateFooterPorts() =
"""
input innerA_addr : UInt<10>
input innerA_clk : Clock
output innerA_dout : UInt<8>
input innerB_addr : UInt<10>
input innerB_clk : Clock
input innerB_din : UInt<8>
input innerB_write_en : UInt<1>
"""
override def generateFlops() =
"""
inst mem_0_0 of split_awesome_lib_mem
mem_0_0.innerB_clk <= innerB_clk
mem_0_0.innerB_addr <= innerB_addr
mem_0_0.innerB_din <= bits(innerB_din, 7, 0)
mem_0_0.innerB_write_en <= and(and(and(innerB_write_en, UInt<1>("h1")), UInt<1>("h1")), UInt<1>("h1"))
mem_0_0.innerA_clk <= innerA_clk
mem_0_0.innerA_addr <= innerA_addr
node innerA_dout_0_0 = bits(mem_0_0.innerA_dout, 7, 0)
node innerA_dout_0 = innerA_dout_0_0
innerA_dout <= mux(UInt<1>("h1"), innerA_dout_0, UInt<8>("h0"))
module split_awesome_lib_mem :
input innerA_addr : UInt<10>
input innerA_clk : Clock
output innerA_dout : UInt<8>
input innerB_addr : UInt<10>
input innerB_clk : Clock
input innerB_din : UInt<8>
input innerB_write_en : UInt<1>
mem ram :
data-type => UInt<8>
depth => 1024
read-latency => 1
write-latency => 1
reader => R_0
writer => W_0
read-under-write => undefined
ram.R_0.clk <= innerA_clk
ram.R_0.addr <= innerA_addr
ram.R_0.en <= UInt<1>("h1")
innerA_dout <= ram.R_0.data
ram.W_0.clk <= innerB_clk
ram.W_0.addr <= innerB_addr
ram.W_0.en <= innerB_write_en
ram.W_0.mask <= UInt<1>("h1")
ram.W_0.data <= innerB_din
"""
"Non-masked split lib; split mem" should "syn flops fine" in {
compileExecuteAndTest(mem, lib, v, output, synflops = true)
}
}
class Synflops_SplitPorts_MaskedMem_Read_MaskedWrite
extends MacroCompilerSpec
with HasSRAMGenerator
with HasSimpleDepthTestGenerator
with HasSynFlopsTestGenerator {
import mdf.macrolib._
override lazy val memDepth = BigInt(2048)
override lazy val libDepth = BigInt(1024)
override lazy val width = 8
override lazy val memMaskGran: Option[Int] = Some(8)
override lazy val libMaskGran: Option[Int] = Some(1)
override def generateLibSRAM(): SRAMMacro = SRAMMacro(
name = lib_name,
width = width,
depth = libDepth,
family = "1r1w",
ports = Seq(
generateReadPort("innerA", width, libDepth),
generateWritePort("innerB", width, libDepth, libMaskGran)
)
)
override def generateMemSRAM(): SRAMMacro = SRAMMacro(
name = mem_name,
width = width,
depth = memDepth,
family = "1r1w",
ports = Seq(
generateReadPort("outerB", width, memDepth),
generateWritePort("outerA", width, memDepth, memMaskGran)
)
)
override def generateHeader() =
"""
circuit target_memory :
module target_memory :
input outerB_addr : UInt<11>
input outerB_clk : Clock
output outerB_dout : UInt<8>
input outerA_addr : UInt<11>
input outerA_clk : Clock
input outerA_din : UInt<8>
input outerA_write_en : UInt<1>
input outerA_mask : UInt<1>
"""
override def generateBody() =
"""
node outerB_addr_sel = bits(outerB_addr, 10, 10)
reg outerB_addr_sel_reg : UInt<1>, outerB_clk with :
reset => (UInt<1>("h0"), outerB_addr_sel_reg)
outerB_addr_sel_reg <= mux(UInt<1>("h1"), outerB_addr_sel, outerB_addr_sel_reg)
node outerA_addr_sel = bits(outerA_addr, 10, 10)
inst mem_0_0 of awesome_lib_mem
mem_0_0.innerB_clk <= outerA_clk
mem_0_0.innerB_addr <= outerA_addr
mem_0_0.innerB_din <= bits(outerA_din, 7, 0)
mem_0_0.innerB_mask <= cat(bits(outerA_mask, 0, 0), cat(bits(outerA_mask, 0, 0), cat(bits(outerA_mask, 0, 0), cat(bits(outerA_mask, 0, 0), cat(bits(outerA_mask, 0, 0), cat(bits(outerA_mask, 0, 0), cat(bits(outerA_mask, 0, 0), bits(outerA_mask, 0, 0))))))))
mem_0_0.innerB_write_en <= and(and(outerA_write_en, UInt<1>("h1")), eq(outerA_addr_sel, UInt<1>("h0")))
mem_0_0.innerA_clk <= outerB_clk
mem_0_0.innerA_addr <= outerB_addr
node outerB_dout_0_0 = bits(mem_0_0.innerA_dout, 7, 0)
node outerB_dout_0 = outerB_dout_0_0
inst mem_1_0 of awesome_lib_mem
mem_1_0.innerB_clk <= outerA_clk
mem_1_0.innerB_addr <= outerA_addr
mem_1_0.innerB_din <= bits(outerA_din, 7, 0)
mem_1_0.innerB_mask <= cat(bits(outerA_mask, 0, 0), cat(bits(outerA_mask, 0, 0), cat(bits(outerA_mask, 0, 0), cat(bits(outerA_mask, 0, 0), cat(bits(outerA_mask, 0, 0), cat(bits(outerA_mask, 0, 0), cat(bits(outerA_mask, 0, 0), bits(outerA_mask, 0, 0))))))))
mem_1_0.innerB_write_en <= and(and(outerA_write_en, UInt<1>("h1")), eq(outerA_addr_sel, UInt<1>("h1")))
mem_1_0.innerA_clk <= outerB_clk
mem_1_0.innerA_addr <= outerB_addr
node outerB_dout_1_0 = bits(mem_1_0.innerA_dout, 7, 0)
node outerB_dout_1 = outerB_dout_1_0
outerB_dout <= mux(eq(outerB_addr_sel_reg, UInt<1>("h0")), outerB_dout_0, mux(eq(outerB_addr_sel_reg, UInt<1>("h1")), outerB_dout_1, UInt<8>("h0")))
"""
override def generateFooterPorts() =
"""
input innerA_addr : UInt<10>
input innerA_clk : Clock
output innerA_dout : UInt<8>
input innerB_addr : UInt<10>
input innerB_clk : Clock
input innerB_din : UInt<8>
input innerB_write_en : UInt<1>
input innerB_mask : UInt<8>
"""
override def generateFlops() =
"""
inst mem_0_0 of split_awesome_lib_mem
inst mem_0_1 of split_awesome_lib_mem
inst mem_0_2 of split_awesome_lib_mem
inst mem_0_3 of split_awesome_lib_mem
inst mem_0_4 of split_awesome_lib_mem
inst mem_0_5 of split_awesome_lib_mem
inst mem_0_6 of split_awesome_lib_mem
inst mem_0_7 of split_awesome_lib_mem
mem_0_0.innerB_clk <= innerB_clk
mem_0_0.innerB_addr <= innerB_addr
mem_0_0.innerB_din <= bits(innerB_din, 0, 0)
mem_0_0.innerB_mask <= bits(innerB_mask, 0, 0)
mem_0_0.innerB_write_en <= and(and(innerB_write_en, UInt<1>("h1")), UInt<1>("h1"))
mem_0_1.innerB_clk <= innerB_clk
mem_0_1.innerB_addr <= innerB_addr
mem_0_1.innerB_din <= bits(innerB_din, 1, 1)
mem_0_1.innerB_mask <= bits(innerB_mask, 1, 1)
mem_0_1.innerB_write_en <= and(and(innerB_write_en, UInt<1>("h1")), UInt<1>("h1"))
mem_0_2.innerB_clk <= innerB_clk
mem_0_2.innerB_addr <= innerB_addr
mem_0_2.innerB_din <= bits(innerB_din, 2, 2)
mem_0_2.innerB_mask <= bits(innerB_mask, 2, 2)
mem_0_2.innerB_write_en <= and(and(innerB_write_en, UInt<1>("h1")), UInt<1>("h1"))
mem_0_3.innerB_clk <= innerB_clk
mem_0_3.innerB_addr <= innerB_addr
mem_0_3.innerB_din <= bits(innerB_din, 3, 3)
mem_0_3.innerB_mask <= bits(innerB_mask, 3, 3)
mem_0_3.innerB_write_en <= and(and(innerB_write_en, UInt<1>("h1")), UInt<1>("h1"))
mem_0_4.innerB_clk <= innerB_clk
mem_0_4.innerB_addr <= innerB_addr
mem_0_4.innerB_din <= bits(innerB_din, 4, 4)
mem_0_4.innerB_mask <= bits(innerB_mask, 4, 4)
mem_0_4.innerB_write_en <= and(and(innerB_write_en, UInt<1>("h1")), UInt<1>("h1"))
mem_0_5.innerB_clk <= innerB_clk
mem_0_5.innerB_addr <= innerB_addr
mem_0_5.innerB_din <= bits(innerB_din, 5, 5)
mem_0_5.innerB_mask <= bits(innerB_mask, 5, 5)
mem_0_5.innerB_write_en <= and(and(innerB_write_en, UInt<1>("h1")), UInt<1>("h1"))
mem_0_6.innerB_clk <= innerB_clk
mem_0_6.innerB_addr <= innerB_addr
mem_0_6.innerB_din <= bits(innerB_din, 6, 6)
mem_0_6.innerB_mask <= bits(innerB_mask, 6, 6)
mem_0_6.innerB_write_en <= and(and(innerB_write_en, UInt<1>("h1")), UInt<1>("h1"))
mem_0_7.innerB_clk <= innerB_clk
mem_0_7.innerB_addr <= innerB_addr
mem_0_7.innerB_din <= bits(innerB_din, 7, 7)
mem_0_7.innerB_mask <= bits(innerB_mask, 7, 7)
mem_0_7.innerB_write_en <= and(and(innerB_write_en, UInt<1>("h1")), UInt<1>("h1"))
mem_0_0.innerA_clk <= innerA_clk
mem_0_0.innerA_addr <= innerA_addr
node innerA_dout_0_0 = bits(mem_0_0.innerA_dout, 0, 0)
mem_0_1.innerA_clk <= innerA_clk
mem_0_1.innerA_addr <= innerA_addr
node innerA_dout_0_1 = bits(mem_0_1.innerA_dout, 0, 0)
mem_0_2.innerA_clk <= innerA_clk
mem_0_2.innerA_addr <= innerA_addr
node innerA_dout_0_2 = bits(mem_0_2.innerA_dout, 0, 0)
mem_0_3.innerA_clk <= innerA_clk
mem_0_3.innerA_addr <= innerA_addr
node innerA_dout_0_3 = bits(mem_0_3.innerA_dout, 0, 0)
mem_0_4.innerA_clk <= innerA_clk
mem_0_4.innerA_addr <= innerA_addr
node innerA_dout_0_4 = bits(mem_0_4.innerA_dout, 0, 0)
mem_0_5.innerA_clk <= innerA_clk
mem_0_5.innerA_addr <= innerA_addr
node innerA_dout_0_5 = bits(mem_0_5.innerA_dout, 0, 0)
mem_0_6.innerA_clk <= innerA_clk
mem_0_6.innerA_addr <= innerA_addr
node innerA_dout_0_6 = bits(mem_0_6.innerA_dout, 0, 0)
mem_0_7.innerA_clk <= innerA_clk
mem_0_7.innerA_addr <= innerA_addr
node innerA_dout_0_7 = bits(mem_0_7.innerA_dout, 0, 0)
node innerA_dout_0 = cat(innerA_dout_0_7, cat(innerA_dout_0_6, cat(innerA_dout_0_5, cat(innerA_dout_0_4, cat(innerA_dout_0_3, cat(innerA_dout_0_2, cat(innerA_dout_0_1, innerA_dout_0_0)))))))
innerA_dout <= mux(UInt<1>("h1"), innerA_dout_0, UInt<8>("h0"))
module split_awesome_lib_mem :
input innerA_addr : UInt<10>
input innerA_clk : Clock
output innerA_dout : UInt<1>
input innerB_addr : UInt<10>
input innerB_clk : Clock
input innerB_din : UInt<1>
input innerB_write_en : UInt<1>
input innerB_mask : UInt<1>
mem ram :
data-type => UInt<1>
depth => 1024
read-latency => 1
write-latency => 1
reader => R_0
writer => W_0
read-under-write => undefined
ram.R_0.clk <= innerA_clk
ram.R_0.addr <= innerA_addr
ram.R_0.en <= UInt<1>("h1")
innerA_dout <= ram.R_0.data
ram.W_0.clk <= innerB_clk
ram.W_0.addr <= innerB_addr
ram.W_0.en <= innerB_write_en
ram.W_0.mask <= innerB_mask
ram.W_0.data <= innerB_din
"""
"masked split lib; masked split mem" should "syn flops fine" in {
compileExecuteAndTest(mem, lib, v, output, synflops = true)
}
}

View File

@@ -0,0 +1,104 @@
// SPDX-License-Identifier: Apache-2.0
package barstools.tapeout.transforms
import chisel3._
import chisel3.experimental.ExtModule
import chisel3.stage.ChiselStage
import firrtl.FileUtils
import org.scalatest.freespec.AnyFreeSpec
import org.scalatest.matchers.must.Matchers.be
import org.scalatest.matchers.should.Matchers.convertToAnyShouldWrapper
import java.io.{File, PrintWriter}
class BlackBoxInverter extends ExtModule {
val in = IO(Input(Bool()))
val out = IO(Output(Bool()))
}
class GenerateExampleModule extends Module {
val in = IO(Input(Bool()))
val out = IO(Output(Bool()))
val inverter = Module(new BlackBoxInverter)
inverter.in := in
val inverted = inverter.out
val reg = RegInit(0.U(8.W))
reg := reg + inverted.asUInt
out := reg
}
class ToBeMadeExternal extends Module {
val in = IO(Input(Bool()))
val out = IO(Output(Bool()))
val reg = RegInit(0.U(8.W))
reg := reg + in.asUInt + 2.U
out := reg
}
class GenerateExampleTester extends Module {
val success = IO(Output(Bool()))
val mod = Module(new GenerateExampleModule)
mod.in := 1.U
val mod2 = Module(new ToBeMadeExternal)
mod2.in := 1.U
val reg = RegInit(0.U(8.W))
reg := reg + mod.out + mod2.out
success := reg === 100.U
when(reg === 100.U) {
stop()
}
}
class GenerateSpec extends AnyFreeSpec {
def generateTestData(targetDir: String): Unit = {
FileUtils.makeDirectory(targetDir)
new ChiselStage().emitFirrtl(new GenerateExampleTester, Array("--target-dir", targetDir))
val blackBoxInverterText =
"""
|module BlackBoxInverter(
| input [0:0] in,
| output [0:0] out
|);
| assign out = !in;
|endmodule
|""".stripMargin
val printWriter2 = new PrintWriter(new File(s"$targetDir/BlackBoxInverter.v"))
printWriter2.write(blackBoxInverterText)
printWriter2.close()
}
"generate test data" in {
val targetDir = "test_run_dir/generate_spec_source"
generateTestData(targetDir)
new File(s"$targetDir/GenerateExampleTester.fir").exists() should be(true)
}
"generate top test" in {
val targetDir = "test_run_dir/generate_spec"
generateTestData(targetDir)
GenerateModelStageMain.main(
Array(
"-i",
s"$targetDir/GenerateExampleTester.fir",
"-o",
s"$targetDir/GenerateExampleTester.v"
)
)
new File(s"$targetDir/GenerateExampleTester.v").exists() should be(true)
}
}

View File

@@ -0,0 +1,119 @@
// See LICENSE for license details.
package barstools.tapeout.transforms.retime
import chisel3._
import chisel3.stage.{ChiselGeneratorAnnotation, ChiselStage}
import firrtl.{EmittedFirrtlCircuitAnnotation, EmittedFirrtlModuleAnnotation, FileUtils}
import logger.Logger
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
class RetimeSpec extends AnyFlatSpec with Matchers {
def normalized(s: String): String = {
require(!s.contains("\n"))
s.replaceAll("\\s+", " ").trim
}
def uniqueDirName[T](gen: => T, name: String): String = {
val genClassName = gen.getClass.getName
name + genClassName.hashCode.abs
}
def getLowFirrtl[T <: RawModule](gen: () => T, extraArgs: Array[String] = Array.empty): String = {
// generate low firrtl
(new ChiselStage)
.execute(
Array("-X", "low") ++ extraArgs,
Seq(ChiselGeneratorAnnotation(gen))
)
.collect {
case EmittedFirrtlCircuitAnnotation(a) => a
case EmittedFirrtlModuleAnnotation(a) => a
}
.map(_.value)
.mkString("")
}
behavior.of("retime library")
it should "pass simple retime module annotation" in {
val gen = () => new RetimeModule
val dir = uniqueDirName(gen, "RetimeModule")
Logger.makeScope(Seq.empty) {
val captor = new Logger.OutputCaptor
Logger.setOutput(captor.printStream)
// generate low firrtl
val firrtl = getLowFirrtl(
gen,
Array("-td", s"test_run_dir/$dir", "-foaf", s"test_run_dir/$dir/final", "--log-level", "info")
)
firrtl.nonEmpty should be(true)
//Make sure we got the RetimeTransform scheduled
captor.getOutputAsString should include("barstools.tapeout.transforms.retime.RetimeTransform")
}
val lines = FileUtils
.getLines(s"test_run_dir/$dir/test_run_dir/$dir/final.anno.json")
.map(normalized)
.mkString("\n")
lines should include("barstools.tapeout.transforms.retime.RetimeAnnotation")
lines should include(""""target":"RetimeModule.RetimeModule"""")
}
it should "pass simple retime instance annotation" in {
val gen = () => new RetimeInstance
val dir = uniqueDirName(gen, "RetimeInstance")
Logger.makeScope(Seq.empty) {
val captor = new Logger.OutputCaptor
Logger.setOutput(captor.printStream)
// generate low firrtl
val firrtl = getLowFirrtl(
gen,
Array("-td", s"test_run_dir/$dir", "-foaf", s"test_run_dir/$dir/final", "--log-level", "info")
)
firrtl.nonEmpty should be(true)
//Make sure we got the RetimeTransform scheduled
captor.getOutputAsString should include("barstools.tapeout.transforms.retime.RetimeTransform")
}
val lines = FileUtils
.getLines(s"test_run_dir/$dir/test_run_dir/$dir/final.anno.json")
.map(normalized)
.mkString("\n")
lines should include("barstools.tapeout.transforms.retime.RetimeAnnotation")
lines should include(""""target":"RetimeInstance.MyModule"""")
}
}
class RetimeModule extends Module with RetimeLib {
val io = IO(new Bundle {
val in = Input(UInt(15.W))
val out = Output(UInt(15.W))
})
io.out := io.in
retime(this)
}
class MyModule extends Module with RetimeLib {
val io = IO(new Bundle {
val in = Input(UInt(15.W))
val out = Output(UInt(15.W))
})
io.out := io.in
}
class RetimeInstance extends Module with RetimeLib {
val io = IO(new Bundle {
val in = Input(UInt(15.W))
val out = Output(UInt(15.W))
})
val instance = Module(new MyModule)
retime(instance)
instance.io.in := io.in
io.out := instance.io.out
}

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,15 @@
package mdf.macrolib
import firrtl.FileUtils
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
class FlipChipMacroSpec extends AnyFlatSpec with Matchers {
"Parsing flipchipmacros" should "work" in {
val stream = FileUtils.getLinesResource("/bumps.json")
val mdf = Utils.readMDFFromString(stream.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,15 @@
package mdf.macrolib
import firrtl.FileUtils
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 = FileUtils.getLinesResource("/io_properties.json")
val mdf = Utils.readMDFFromString(stream.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
}
}