Add memory compiler to macros (#29)

* Add memory compiler to macros

* Removed weird spacing

* Make sramcompiler width/depth range inclusive

* Added sramcompiler test
This commit is contained in:
Adam Izraelevitz
2018-02-16 16:01:10 -08:00
committed by GitHub
parent 8a30579a3e
commit 79c8c283cc
7 changed files with 178 additions and 100 deletions

View File

@@ -7,6 +7,8 @@ import firrtl.Utils.ceilLog2
import java.io.{File, StringWriter}
abstract class MacroCompilerSpec extends org.scalatest.FlatSpec with org.scalatest.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
@@ -32,11 +34,12 @@ abstract class MacroCompilerSpec extends org.scalatest.FlatSpec with org.scalate
}
}
private def args(mem: String, lib: Option[String], v: String, synflops: Boolean) =
private def args(mem: String, lib: Option[String], v: String, synflops: Boolean, useCompiler: Boolean) =
List("-m", mem.toString, "-v", v) ++
(lib match { case None => Nil case Some(l) => List("-l", l.toString) }) ++
costMetricCmdLine ++
(if (synflops) List("--mode", "synflops") else Nil)
(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
@@ -44,12 +47,12 @@ abstract class MacroCompilerSpec extends org.scalatest.FlatSpec with org.scalate
def compile(mem: String, lib: String, v: String, synflops: Boolean) {
compile(mem, Some(lib), v, synflops)
}
def compile(mem: String, lib: Option[String], v: String, synflops: Boolean) {
def compile(mem: String, lib: Option[String], v: String, synflops: Boolean, useCompiler: Boolean = false) {
var mem_full = concat(memPrefix, mem)
var lib_full = concat(libPrefix, lib)
var v_full = concat(vPrefix, v)
MacroCompiler.run(args(mem_full, lib_full, v_full, synflops))
MacroCompiler.run(args(mem_full, lib_full, v_full, synflops, useCompiler))
}
// Helper functions to write macro libraries to the given files.
@@ -62,14 +65,11 @@ abstract class MacroCompilerSpec extends org.scalatest.FlatSpec with org.scalate
}
// Convenience function for running both compile, execute, and test at once.
def compileExecuteAndTest(mem: String, lib: Option[String], v: String, output: String, synflops: Boolean): Unit = {
compile(mem, lib, v, synflops)
val result = execute(mem, lib, synflops)
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)
}
def compileExecuteAndTest(mem: String, lib: String, v: String, output: String, synflops: Boolean = false): Unit = {
compileExecuteAndTest(mem, Some(lib), v, output, synflops)
}
// Compare FIRRTL outputs after reparsing output with ScalaTest ("should be").
def test(result: Circuit, output: String): Unit = {
@@ -79,21 +79,20 @@ abstract class MacroCompilerSpec extends org.scalatest.FlatSpec with org.scalate
// Execute the macro compiler and returns a Circuit containing the output of
// the memory compiler.
def execute(memFile: String, libFile: Option[String], synflops: Boolean): Circuit = {
execute(Some(memFile), libFile, synflops)
}
def execute(memFile: String, libFile: String, synflops: Boolean): Circuit = {
execute(Some(memFile), Some(libFile), synflops)
}
def execute(memFile: Option[String], libFile: Option[String], synflops: Boolean): Circuit = {
def execute(memFile: Option[String], libFile: Option[String], synflops: Boolean): Circuit = execute(memFile, libFile, synflops, false)
def execute(memFile: Option[String], libFile: Option[String], synflops: Boolean, useCompiler: Boolean): Circuit = {
var mem_full = concat(memPrefix, memFile)
var 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]] = Utils.filterForSRAM(mdf.macrolib.Utils.readMDFFromPath(lib_full)) match {
case Some(x) => Some(x map (new Macro(_)))
case None => None
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)
@@ -105,6 +104,7 @@ abstract class MacroCompilerSpec extends org.scalatest.FlatSpec with org.scalate
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] = {
@@ -118,12 +118,16 @@ abstract class MacroCompilerSpec extends org.scalatest.FlatSpec with org.scalate
// A collection of standard SRAM generators.
trait HasSRAMGenerator {
import mdf.macrolib._
import scala.language.implicitConversions
implicit def Int2SomeInt(i: Int): Option[Int] = Some(i)
// Generate a standard (read/write/combo) port for testing.
// Helper methods for optional width argument
def generateTestPort(
prefix: String,
width: Int,
depth: Int,
width: Option[Int],
depth: Option[Int],
maskGran: Option[Int] = None,
read: Boolean,
readEnable: Boolean = false,
@@ -133,55 +137,69 @@ trait HasSRAMGenerator {
val realPrefix = if (prefix == "") "" else prefix + "_"
MacroPort(
address=PolarizedPort(name=realPrefix + "addr", polarity=ActiveHigh),
clock=PolarizedPort(name=realPrefix + "clk", polarity=PositiveEdge),
address = PolarizedPort(name = realPrefix + "addr", polarity = ActiveHigh),
clock = 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,
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,
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(x:Int) => Some(PolarizedPort(name=realPrefix + "mask", polarity=ActiveHigh))
maskPort = maskGran match {
case Some(x: Int) => Some(PolarizedPort(name = realPrefix + "mask", polarity = ActiveHigh))
case _ => None
},
maskGran=maskGran,
maskGran = maskGran,
width=width, depth=depth // These numbers don't matter here.
width = width, depth = depth // These numbers don't matter here.
)
}
// Generate a read port for testing.
def generateReadPort(prefix: String, width: Int, depth: Int, readEnable: Boolean = false): MacroPort = {
generateTestPort(prefix, width, depth, write=false, read=true, readEnable=readEnable)
def generateReadPort(prefix: String, width: Option[Int], depth: Option[Int], 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: Int, depth: Int, maskGran: Option[Int] = None, writeEnable: Boolean = true): MacroPort = {
generateTestPort(prefix, width, depth, maskGran=maskGran, write=true, read=false, writeEnable=writeEnable)
def generateWritePort(prefix: String, width: Option[Int], depth: Option[Int], 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: Int, depth: Int, maskGran: Option[Int] = None): MacroPort = {
def generateReadWritePort(prefix: String, width: Option[Int], depth: Option[Int], maskGran: Option[Int] = None): MacroPort = {
generateTestPort(
prefix, width, depth, maskGran=maskGran,
write=true, writeEnable=true,
read=true, readEnable=false
prefix, width, depth, maskGran = maskGran,
write = true, writeEnable = true,
read = true, readEnable = false
)
}
// Generate a "simple" SRAM (active high/positive edge, 1 read-write port).
def generateSRAM(name: String, prefix: String, width: Int, depth: Int, 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
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, extraPorts: Seq[MacroExtraPort] = List()): 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.
@@ -192,6 +210,7 @@ trait HasSimpleTestGenerator {
// 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: Int
@@ -224,10 +243,10 @@ trait HasSimpleTestGenerator {
val lib = s"lib-${generatorType}${extraTagPrefixed}.json"
val v = s"${generatorType}${extraTagPrefixed}.v"
val mem_name = "target_memory"
lazy val mem_name = "target_memory"
val mem_addr_width = ceilLog2(memDepth)
val lib_name = "awesome_lib_mem"
lazy val lib_name = "awesome_lib_mem"
val lib_addr_width = ceilLog2(libDepth)
// Override these to change the port prefixes if needed.
@@ -258,8 +277,8 @@ trait HasSimpleTestGenerator {
// 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.
val lastWidthBits = if (memWidth % usableLibWidth == 0) usableLibWidth else (memWidth % usableLibWidth)
val selectBits = mem_addr_width - lib_addr_width
lazy val lastWidthBits = if (memWidth % usableLibWidth == 0) usableLibWidth else (memWidth % usableLibWidth)
lazy val selectBits = mem_addr_width - lib_addr_width
/**
* Convenience function to generate a mask statement.
@@ -410,3 +429,4 @@ trait HasNoLibTestGenerator extends HasSimpleTestGenerator {
// If there is no lib, don't generate a body.
override def generateBody = ""
}

View File

@@ -0,0 +1,22 @@
package barstools.macros
import mdf.macrolib._
class SRAMCompiler extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleWidthTestGenerator {
val compiler = generateSRAMCompiler("awesome", "A")
val verilog = s"v-SRAMCompiler.v"
override lazy val depth = 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, false, true)
}

View File

@@ -20,27 +20,27 @@ trait HasSimpleDepthTestGenerator extends HasSimpleTestGenerator {
if (selectBits > 0) {
output.append (
s"""
node outer_addr_sel = bits(outer_addr, ${mem_addr_width - 1}, $lib_addr_width)
reg outer_addr_sel_reg : UInt<${selectBits}>, 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)
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 to depthInstances - 1) {
val maskStatement = generateMaskStatement(0, i)
val enableIdentifier = if (selectBits > 0) s"""eq(outer_addr_sel, UInt<${selectBits}>("h${i.toHexString}"))""" else "UInt<1>(\"h1\")"
val enableIdentifier = if (selectBits > 0) s"""eq(${memPortPrefix}_addr_sel, UInt<${selectBits}>("h${i.toHexString}"))""" else "UInt<1>(\"h1\")"
output.append(
s"""
inst mem_${i}_0 of awesome_lib_mem
mem_${i}_0.lib_clk <= outer_clk
mem_${i}_0.lib_addr <= outer_addr
node outer_dout_${i}_0 = bits(mem_${i}_0.lib_dout, ${width - 1}, 0)
mem_${i}_0.lib_din <= bits(outer_din, ${width - 1}, 0)
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.lib_write_en <= and(and(outer_write_en, UInt<1>("h1")), ${enableIdentifier})
node outer_dout_${i} = outer_dout_${i}_0
mem_${i}_0.${libPortPrefix}_write_en <= and(and(${memPortPrefix}_write_en, UInt<1>("h1")), ${enableIdentifier})
node ${memPortPrefix}_dout_${i} = ${memPortPrefix}_dout_${i}_0
"""
)
}
@@ -48,16 +48,16 @@ s"""
if (i > depthInstances - 1) {
"UInt<1>(\"h0\")"
} else {
"mux(eq(outer_addr_sel_reg, UInt<%d>(\"h%s\")), outer_dout_%d, %s)".format(
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 " outer_dout <= "
output append s" ${memPortPrefix}_dout <= "
if (selectBits > 0) {
output append generate_outer_dout_tree(0, depthInstances)
} else {
output append """mux(UInt<1>("h1"), outer_dout_0, UInt<1>("h0"))"""
output append s"""mux(UInt<1>("h1"), ${memPortPrefix}_dout_0, UInt<1>("h0"))"""
}
output.toString

View File

@@ -40,28 +40,28 @@ trait HasSimpleWidthTestGenerator extends HasSimpleTestGenerator {
} else """UInt<1>("h1")"""
s"""
mem_0_${i}.lib_clk <= outer_clk
mem_0_${i}.lib_addr <= outer_addr
node outer_dout_0_${i} = bits(mem_0_${i}.lib_dout, ${myMemWidth - 1}, 0)
mem_0_${i}.lib_din <= bits(outer_din, ${myBaseBit + myMemWidth - 1}, ${myBaseBit})
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}.lib_write_en <= and(and(outer_write_en, ${writeEnableBit}), UInt<1>("h1"))
mem_0_${i}.${libPortPrefix}_write_en <= and(and(${memPortPrefix}_write_en, ${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"outer_dout_0_${i}"))
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 outer_dout_0 = ${catStmt}
node ${memPortPrefix}_dout_0 = ${catStmt}
"""
}
output append
"""
outer_dout <= mux(UInt<1>("h1"), outer_dout_0, UInt<1>("h0"))
s"""
${memPortPrefix}_dout <= mux(UInt<1>("h1"), ${memPortPrefix}_dout_0, UInt<1>("h0"))
"""
output.toString
}
@@ -398,7 +398,7 @@ class SplitWidth1024x32_readEnable_Lib extends MacroCompilerSpec with HasSRAMGen
depth=libDepth,
family="1rw",
ports=Seq(generateTestPort(
"lib", libWidth, libDepth, maskGran=libMaskGran,
"lib", Some(libWidth), Some(libDepth), maskGran=libMaskGran,
write=true, writeEnable=true,
read=true, readEnable=true
))
@@ -456,7 +456,7 @@ class SplitWidth1024x32_readEnable_Mem extends MacroCompilerSpec with HasSRAMGen
depth=memDepth,
family="1rw",
ports=Seq(generateTestPort(
"outer", memWidth, memDepth, maskGran=memMaskGran,
"outer", Some(memWidth), Some(memDepth), maskGran=memMaskGran,
write=true, writeEnable=true,
read=true, readEnable=true
))
@@ -482,7 +482,7 @@ class SplitWidth1024x32_readEnable_LibMem extends MacroCompilerSpec with HasSRAM
depth=libDepth,
family="1rw",
ports=Seq(generateTestPort(
"lib", libWidth, libDepth, maskGran=libMaskGran,
"lib", Some(libWidth), Some(libDepth), maskGran=libMaskGran,
write=true, writeEnable=true,
read=true, readEnable=true
))
@@ -496,7 +496,7 @@ class SplitWidth1024x32_readEnable_LibMem extends MacroCompilerSpec with HasSRAM
depth=memDepth,
family="1rw",
ports=Seq(generateTestPort(
"outer", memWidth, memDepth, maskGran=memMaskGran,
"outer", Some(memWidth), Some(memDepth), maskGran=memMaskGran,
write=true, writeEnable=true,
read=true, readEnable=true
))