Merge remote-tracking branch 'barstools/master' into flatten-barstools
This commit is contained in:
44
.github/workflows/run-ci.yml
vendored
Normal file
44
.github/workflows/run-ci.yml
vendored
Normal 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
27
.scalafmt.conf
Normal 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]
|
||||
11
iocell/src/main/resources/barstools/iocell/vsrc/Analog.v
Normal file
11
iocell/src/main/resources/barstools/iocell/vsrc/Analog.v
Normal 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
|
||||
18
iocell/src/main/scala/barstools/iocell/chisel/Analog.scala
Normal file
18
iocell/src/main/scala/barstools/iocell/chisel/Analog.scala
Normal 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
|
||||
}
|
||||
338
iocell/src/main/scala/barstools/iocell/chisel/IOCell.scala
Normal file
338
iocell/src/main/scala/barstools/iocell/chisel/IOCell.scala
Normal 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.") }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
205
src/main/scala/barstools/macros/CostMetric.scala
Normal file
205
src/main/scala/barstools/macros/CostMetric.scala
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
981
src/main/scala/barstools/macros/MacroCompiler.scala
Normal file
981
src/main/scala/barstools/macros/MacroCompiler.scala
Normal 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)
|
||||
}
|
||||
152
src/main/scala/barstools/macros/SynFlopsPass.scala
Normal file
152
src/main/scala/barstools/macros/SynFlopsPass.scala
Normal 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)
|
||||
}
|
||||
}
|
||||
262
src/main/scala/barstools/macros/Utils.scala
Normal file
262
src/main/scala/barstools/macros/Utils.scala
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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!")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package barstools.tapeout.transforms.utils
|
||||
|
||||
object LowerName {
|
||||
def apply(s: String): String = s.replace(".", "_").replace("[", "_").replace("]", "")
|
||||
}
|
||||
@@ -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
|
||||
}: _*)
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
95
src/main/scala/mdf/macrolib/ConfReader.scala
Normal file
95
src/main/scala/mdf/macrolib/ConfReader.scala
Normal 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(_))
|
||||
}
|
||||
}
|
||||
61
src/main/scala/mdf/macrolib/FillerMacroBase.scala
Normal file
61
src/main/scala/mdf/macrolib/FillerMacroBase.scala
Normal 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"
|
||||
}
|
||||
72
src/main/scala/mdf/macrolib/FlipChipMacro.scala
Normal file
72
src/main/scala/mdf/macrolib/FlipChipMacro.scala
Normal 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))
|
||||
}
|
||||
}
|
||||
147
src/main/scala/mdf/macrolib/IOMacro.scala
Normal file
147
src/main/scala/mdf/macrolib/IOMacro.scala
Normal 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))
|
||||
}
|
||||
}
|
||||
19
src/main/scala/mdf/macrolib/MacroLib.scala
Normal file
19
src/main/scala/mdf/macrolib/MacroLib.scala
Normal 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
|
||||
}
|
||||
444
src/main/scala/mdf/macrolib/SRAM.scala
Normal file
444
src/main/scala/mdf/macrolib/SRAM.scala
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
96
src/main/scala/mdf/macrolib/Utils.scala
Normal file
96
src/main/scala/mdf/macrolib/Utils.scala
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
231
src/test/resources/PadAnnotationVerilogPart.v
Normal file
231
src/test/resources/PadAnnotationVerilogPart.v
Normal 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
|
||||
41
src/test/resources/bumps.json
Normal file
41
src/test/resources/bumps.json
Normal 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", "-"]
|
||||
]
|
||||
}
|
||||
]
|
||||
663
src/test/resources/io_properties.json
Normal file
663
src/test/resources/io_properties.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
1165
src/test/resources/lib-BOOMTest.json
Normal file
1165
src/test/resources/lib-BOOMTest.json
Normal file
File diff suppressed because it is too large
Load Diff
29
src/test/resources/lib-MaskPortTest.json
Normal file
29
src/test/resources/lib-MaskPortTest.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
26
src/test/resources/lib-WriteEnableTest.json
Normal file
26
src/test/resources/lib-WriteEnableTest.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
114
src/test/scala/barstools/macros/CostFunction.scala
Normal file
114
src/test/scala/barstools/macros/CostFunction.scala
Normal 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)
|
||||
}
|
||||
120
src/test/scala/barstools/macros/Functional.scala
Normal file
120
src/test/scala/barstools/macros/Functional.scala
Normal 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)
|
||||
// }
|
||||
// }
|
||||
546
src/test/scala/barstools/macros/MacroCompilerSpec.scala
Normal file
546
src/test/scala/barstools/macros/MacroCompilerSpec.scala
Normal 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() = ""
|
||||
}
|
||||
383
src/test/scala/barstools/macros/Masks.scala
Normal file
383
src/test/scala/barstools/macros/Masks.scala
Normal 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)
|
||||
}
|
||||
500
src/test/scala/barstools/macros/MultiPort.scala
Normal file
500
src/test/scala/barstools/macros/MultiPort.scala
Normal 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)
|
||||
}
|
||||
21
src/test/scala/barstools/macros/SRAMCompiler.scala
Normal file
21
src/test/scala/barstools/macros/SRAMCompiler.scala
Normal 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)
|
||||
}
|
||||
638
src/test/scala/barstools/macros/SimpleSplitDepth.scala
Normal file
638
src/test/scala/barstools/macros/SimpleSplitDepth.scala
Normal 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)
|
||||
}
|
||||
}
|
||||
608
src/test/scala/barstools/macros/SimpleSplitWidth.scala
Normal file
608
src/test/scala/barstools/macros/SimpleSplitWidth.scala
Normal 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)
|
||||
}
|
||||
1762
src/test/scala/barstools/macros/SpecificExamples.scala
Normal file
1762
src/test/scala/barstools/macros/SpecificExamples.scala
Normal file
File diff suppressed because it is too large
Load Diff
455
src/test/scala/barstools/macros/SynFlops.scala
Normal file
455
src/test/scala/barstools/macros/SynFlops.scala
Normal 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)
|
||||
}
|
||||
}
|
||||
104
src/test/scala/barstools/tapeout/transforms/GenerateSpec.scala
Normal file
104
src/test/scala/barstools/tapeout/transforms/GenerateSpec.scala
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
101
src/test/scala/mdf/macrolib/ConfReaderSpec.scala
Normal file
101
src/test/scala/mdf/macrolib/ConfReaderSpec.scala
Normal 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()
|
||||
)
|
||||
}
|
||||
}
|
||||
15
src/test/scala/mdf/macrolib/FlipChipMacroSpec.scala
Normal file
15
src/test/scala/mdf/macrolib/FlipChipMacroSpec.scala
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
67
src/test/scala/mdf/macrolib/IOMacroSpec.scala
Normal file
67
src/test/scala/mdf/macrolib/IOMacroSpec.scala
Normal 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"))
|
||||
)
|
||||
}
|
||||
}
|
||||
15
src/test/scala/mdf/macrolib/IOPropertiesSpec.scala
Normal file
15
src/test/scala/mdf/macrolib/IOPropertiesSpec.scala
Normal 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)) =>
|
||||
}
|
||||
}
|
||||
}
|
||||
270
src/test/scala/mdf/macrolib/MacroLibOutput.scala
Normal file
270
src/test/scala/mdf/macrolib/MacroLibOutput.scala
Normal 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)
|
||||
}
|
||||
}
|
||||
406
src/test/scala/mdf/macrolib/MacroLibSpec.scala
Normal file
406
src/test/scala/mdf/macrolib/MacroLibSpec.scala
Normal 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user