package barstools.macros import firrtl.Utils.ceilLog2 import mdf.macrolib._ // 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 { 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 width: Int def mem_depth: Int def lib_depth: Int def mem_maskGran: Option[Int] = None def lib_maskGran: Option[Int] = None def extraPorts: Seq[mdf.macrolib.MacroExtraPort] = List() def extraTag: String = "" require (mem_depth >= lib_depth) override val memPrefix = testDir override val libPrefix = testDir // Convenience variables to check if a mask exists. val memHasMask = mem_maskGran != None val libHasMask = lib_maskGran != None // We need to figure out how many mask bits there are in the mem. val memMaskBits = if (memHasMask) width / mem_maskGran.get else 0 val libMaskBits = if (libHasMask) width / lib_maskGran.get else 0 // Generate "mrw" vs "rw" tags. val memTag = (if (memHasMask) "m" else "") + "rw" + (if (mem_maskGran.nonEmpty) s"_gran${mem_maskGran.get}" else "") val libTag = (if (libHasMask) "m" else "") + "rw" + (if (lib_maskGran.nonEmpty) s"_gran${lib_maskGran.get}" else "") val extraTagPrefixed = if (extraTag == "") "" else ("-" + extraTag) val mem = s"mem-${mem_depth}x${width}-${memTag}${extraTagPrefixed}.json" val lib = s"lib-${lib_depth}x${width}-${libTag}${extraTagPrefixed}.json" val v = s"split_depth_${mem_depth}x${width}_${memTag}${extraTagPrefixed}.v" val mem_name = "target_memory" val mem_addr_width = ceilLog2(mem_depth) val lib_name = "awesome_lib_mem" val lib_addr_width = ceilLog2(lib_depth) writeToLib(lib, Seq(generateSRAM(lib_name, "lib", width, lib_depth, lib_maskGran, extraPorts))) writeToMem(mem, Seq(generateSRAM(mem_name, "outer", width, mem_depth, mem_maskGran))) // Number of lib instances needed to hold the mem. // Round up (e.g. 1.5 instances = effectively 2 instances) val expectedInstances = math.ceil(mem_depth.toFloat / lib_depth).toInt val selectBits = mem_addr_width - lib_addr_width val headerMask = if (memHasMask) s"input outer_mask : UInt<${memMaskBits}>" else "" val header = s""" circuit $mem_name : module $mem_name : input outer_clk : Clock input outer_addr : UInt<$mem_addr_width> input outer_din : UInt<$width> output outer_dout : UInt<$width> input outer_write_en : UInt<1> ${headerMask} """ val footerMask = if (libHasMask) s"input lib_mask : UInt<${libMaskBits}>" else "" val footer = s""" extmodule $lib_name : input lib_clk : Clock input lib_addr : UInt<$lib_addr_width> input lib_din : UInt<$width> output lib_dout : UInt<$width> input lib_write_en : UInt<1> ${footerMask} defname = $lib_name """ var output = header if (selectBits > 0) { output += s""" node outer_addr_sel = bits(outer_addr, ${mem_addr_width - 1}, $lib_addr_width) """ } for (i <- 0 to expectedInstances - 1) { // We only support simple masks for now (either libMask == memMask or libMask == 1) val maskStatement = if (libHasMask) { if (lib_maskGran.get == mem_maskGran.get) { s"""mem_${i}_0.lib_mask <= bits(outer_mask, 0, 0)""" } else if (lib_maskGran.get == 1) { // Construct a mask string. // Each bit gets the # of bits specified in maskGran. // Specify in descending order (MSB first) // This builds an array like m[1], m[1], m[0], m[0] val maskBitsArr: Seq[String] = ((memMaskBits - 1 to 0 by -1) flatMap (maskBit => { ((0 to mem_maskGran.get - 1) map (_ => s"bits(outer_mask, ${maskBit}, ${maskBit})")) })) // Now build it into a recursive string like // cat(m[1], cat(m[1], cat(m[0], m[0]))) val maskBitsStr: String = maskBitsArr.reverse.tail.foldLeft(maskBitsArr.reverse.head)((prev: String, next: String) => s"cat(${next}, ${prev})") s"""mem_${i}_0.lib_mask <= ${maskBitsStr}""" } else "" // TODO: implement when non-bitmasked memories are supported } else "" // No mask val enableIdentifier = if (selectBits > 0) s"""eq(outer_addr_sel, UInt<${selectBits}>("h${i.toHexString}"))""" else "UInt<1>(\"h1\")" output += 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) ${maskStatement} mem_${i}_0.lib_write_en <= and(and(outer_write_en, UInt<1>("h1")), ${enableIdentifier}) node outer_dout_${i} = outer_dout_${i}_0 """ } def generate_outer_dout_tree(i:Int, expectedInstances: Int): String = { if (i > expectedInstances - 1) { "UInt<1>(\"h0\")" } else { "mux(eq(outer_addr_sel, UInt<%d>(\"h%s\")), outer_dout_%d, %s)".format( selectBits, i.toHexString, i, generate_outer_dout_tree(i + 1, expectedInstances) ) } } output += " outer_dout <= " if (selectBits > 0) { output += generate_outer_dout_tree(0, expectedInstances) } else { output += """mux(UInt<1>("h1"), outer_dout_0, UInt<1>("h0"))""" } output += footer } // Try different widths class SplitDepth4096x32_rw extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleDepthTestGenerator { override lazy val width = 32 override lazy val mem_depth = 4096 override lazy val lib_depth = 1024 compile(mem, lib, v, false) execute(mem, lib, false, output) } class SplitDepth4096x16_rw extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleDepthTestGenerator { override lazy val width = 16 override lazy val mem_depth = 4096 override lazy val lib_depth = 1024 compile(mem, lib, v, false) execute(mem, lib, false, output) } class SplitDepth32768x8_rw extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleDepthTestGenerator { override lazy val width = 8 override lazy val mem_depth = 32768 override lazy val lib_depth = 1024 compile(mem, lib, v, false) execute(mem, lib, false, output) } class SplitDepth4096x8_rw extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleDepthTestGenerator { override lazy val width = 8 override lazy val mem_depth = 4096 override lazy val lib_depth = 1024 compile(mem, lib, v, false) execute(mem, lib, false, output) } class SplitDepth2048x8_rw extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleDepthTestGenerator { override lazy val width = 8 override lazy val mem_depth = 2048 override lazy val lib_depth = 1024 compile(mem, lib, v, false) execute(mem, lib, false, output) } class SplitDepth1024x8_rw extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleDepthTestGenerator { override lazy val width = 8 override lazy val mem_depth = 1024 override lazy val lib_depth = 1024 compile(mem, lib, v, false) execute(mem, lib, false, output) } // Non power of two class SplitDepth2000x8_rw extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleDepthTestGenerator { override lazy val width = 8 override lazy val mem_depth = 2000 override lazy val lib_depth = 1024 compile(mem, lib, v, false) execute(mem, lib, false, output) } class SplitDepth2049x8_rw extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleDepthTestGenerator { override lazy val width = 8 override lazy val mem_depth = 2049 override lazy val lib_depth = 1024 compile(mem, lib, v, false) execute(mem, lib, false, 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 mem_depth = 2048 override lazy val lib_depth = 1024 override lazy val mem_maskGran = Some(32) override lazy val lib_maskGran = Some(32) compile(mem, lib, v, false) execute(mem, lib, false, output) } class SplitDepth2048x8_mrw_lib8 extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleDepthTestGenerator { override lazy val width = 8 override lazy val mem_depth = 2048 override lazy val lib_depth = 1024 override lazy val mem_maskGran = Some(8) override lazy val lib_maskGran = Some(8) compile(mem, lib, v, false) execute(mem, lib, false, output) } // Non-bit level mask class SplitDepth2048x64_mrw_mem32_lib8 extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleDepthTestGenerator { override lazy val width = 64 override lazy val mem_depth = 2048 override lazy val lib_depth = 1024 override lazy val mem_maskGran = Some(32) override lazy val lib_maskGran = Some(8) it should "be enabled when non-bitmasked memories are supported" is (pending) //compile(mem, lib, v, false) //execute(mem, lib, false, output) } // Bit level mask class SplitDepth2048x32_mrw_mem16_lib1 extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleDepthTestGenerator { override lazy val width = 32 override lazy val mem_depth = 2048 override lazy val lib_depth = 1024 override lazy val mem_maskGran = Some(16) override lazy val lib_maskGran = Some(1) compile(mem, lib, v, false) execute(mem, lib, false, output) } class SplitDepth2048x32_mrw_mem8_lib1 extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleDepthTestGenerator { override lazy val width = 32 override lazy val mem_depth = 2048 override lazy val lib_depth = 1024 override lazy val mem_maskGran = Some(8) override lazy val lib_maskGran = Some(1) compile(mem, lib, v, false) execute(mem, lib, false, output) } class SplitDepth2048x32_mrw_mem4_lib1 extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleDepthTestGenerator { override lazy val width = 32 override lazy val mem_depth = 2048 override lazy val lib_depth = 1024 override lazy val mem_maskGran = Some(4) override lazy val lib_maskGran = Some(1) compile(mem, lib, v, false) execute(mem, lib, false, output) } class SplitDepth2048x32_mrw_mem2_lib1 extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleDepthTestGenerator { override lazy val width = 32 override lazy val mem_depth = 2048 override lazy val lib_depth = 1024 override lazy val mem_maskGran = Some(2) override lazy val lib_maskGran = Some(1) compile(mem, lib, v, false) execute(mem, lib, false, 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 mem_depth = 2048 override lazy val lib_depth = 1024 override lazy val mem_maskGran = Some(3) override lazy val lib_maskGran = 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) } class SplitDepth2048x32_mrw_mem7_lib1 extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleDepthTestGenerator { override lazy val width = 32 override lazy val mem_depth = 2048 override lazy val lib_depth = 1024 override lazy val mem_maskGran = Some(7) override lazy val lib_maskGran = 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) } class SplitDepth2048x32_mrw_mem9_lib1 extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleDepthTestGenerator { override lazy val width = 32 override lazy val mem_depth = 2048 override lazy val lib_depth = 1024 override lazy val mem_maskGran = Some(9) override lazy val lib_maskGran = 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) } // Try an extra port class SplitDepth2048x8_extraPort extends MacroCompilerSpec with HasSRAMGenerator with HasSimpleDepthTestGenerator { import mdf.macrolib._ override lazy val width = 8 override lazy val mem_depth = 2048 override lazy val lib_depth = 1024 override lazy val extraPorts = List( MacroExtraPort(name="extra_port", width=8, portType=Constant, value=0xff) ) override lazy val extraTag = "extraPort" val outputCustom = """ circuit target_memory : module target_memory : input outer_clk : Clock input outer_addr : UInt<11> 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) 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(outer_write_en, 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(outer_write_en, 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, UInt<1>("h0")), outer_dout_0, mux(eq(outer_addr_sel, UInt<1>("h1")), outer_dout_1, UInt<1>("h0"))) extmodule awesome_lib_mem : input lib_clk : Clock input lib_addr : UInt<10> 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 """ compile(mem, lib, v, false) execute(mem, lib, false, outputCustom) } // Split read and (masked) write ports (r+w). class SplitDepth_SplitPorts extends MacroCompilerSpec with HasSRAMGenerator { lazy val width = 8 lazy val mem_depth = 2048 lazy val lib_depth = 1024 override val memPrefix = testDir override val libPrefix = testDir import mdf.macrolib._ "Non-masked split lib; split mem" should "split fine" in { val lib = "lib-split_depth-r-mw-lib-regular-mem.json" val mem = "mem-split_depth-r-mw-lib-regular-mem.json" val v = "split_depth-r-mw-lib-regular-mem.v" val libMacro = SRAMMacro( macroType=SRAM, name="awesome_lib_mem", width=width, depth=lib_depth, family="1r1w", ports=Seq( generateReadPort("innerA", width, lib_depth), generateWritePort("innerB", width, lib_depth) ) ) val memMacro = SRAMMacro( macroType=SRAM, name="target_memory", width=width, depth=mem_depth, family="1r1w", ports=Seq( generateReadPort("outerB", width, mem_depth), generateWritePort("outerA", width, mem_depth) ) ) writeToLib(mem, Seq(memMacro)) writeToLib(lib, Seq(libMacro)) val output = """ circuit target_memory : module target_memory : input outerB_clk : Clock input outerB_addr : UInt<11> output outerB_dout : UInt<8> input outerA_clk : Clock input outerA_addr : UInt<11> input outerA_din : UInt<8> input outerA_write_en : UInt<1> node outerB_addr_sel = bits(outerB_addr, 10, 10) node outerA_addr_sel = bits(outerA_addr, 10, 10) inst mem_0_0 of awesome_lib_mem 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 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(outerA_write_en, UInt<1>("h1")), eq(outerA_addr_sel, UInt<1>("h0"))) inst mem_1_0 of awesome_lib_mem 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 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(outerA_write_en, UInt<1>("h1")), eq(outerA_addr_sel, UInt<1>("h1"))) outerB_dout <= mux(eq(outerB_addr_sel, UInt<1>("h0")), outerB_dout_0, mux(eq(outerB_addr_sel, UInt<1>("h1")), outerB_dout_1, UInt<1>("h0"))) extmodule awesome_lib_mem : input innerA_clk : Clock input innerA_addr : UInt<10> output innerA_dout : UInt<8> input innerB_clk : Clock input innerB_addr : UInt<10> input innerB_din : UInt<8> input innerB_write_en : UInt<1> defname = awesome_lib_mem """ compile(mem, lib, v, false) execute(mem, lib, false, 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-lib-regular-mem.json" val mem = "mem-split_depth-r-mw-lib-regular-mem.json" val v = "split_depth-r-mw-lib-regular-mem.v" val libMacro = SRAMMacro( macroType=SRAM, name="awesome_lib_mem", width=width, depth=lib_depth, family="1rw", ports=Seq( generateReadPort("innerA", width, lib_depth), generateWritePort("innerB", width, lib_depth) ) ) writeToLib(mem, Seq(generateSRAM("target_memory", "outer", width, mem_depth))) writeToLib(lib, Seq(libMacro)) val output = """ TODO """ compile(mem, lib, v, false) execute(mem, lib, false, output) } }