From dfa2b29b8f1ad0915fde124b76500d43b4b540f2 Mon Sep 17 00:00:00 2001 From: jsowash Date: Fri, 12 Jul 2019 10:34:29 -0700 Subject: [PATCH] Begin adding wmask netlist and spice tests. --- compiler/characterizer/functional.py | 66 +++++++-- compiler/characterizer/simulation.py | 34 +++-- compiler/modules/control_logic.py | 2 - compiler/modules/multibank.py | 12 +- compiler/modules/port_data.py | 21 +++ compiler/modules/write_mask_array.py | 158 ++++++++++++++++++++++ compiler/sram/sram_1bank.py | 6 +- compiler/sram/sram_base.py | 14 +- compiler/tests/22_sram_wmask_func_test.py | 62 +++++++++ 9 files changed, 342 insertions(+), 33 deletions(-) create mode 100644 compiler/modules/write_mask_array.py create mode 100755 compiler/tests/22_sram_wmask_func_test.py diff --git a/compiler/characterizer/functional.py b/compiler/characterizer/functional.py index 939ecf96..cf0a30c4 100644 --- a/compiler/characterizer/functional.py +++ b/compiler/characterizer/functional.py @@ -37,13 +37,27 @@ class functional(simulation): #self.set_feasible_period(sram, spfile, corner) self.set_stimulus_variables() self.create_signal_names() + self.initilize_wmask() - # Number of checks can be changed self.num_cycles = 2 self.stored_words = {} self.write_check = [] self.read_check = [] + + def initilize_wmask(self): + self.wmask = [None]*self.num_wmask + self.num_wmask = int(self.word_size/self.write_size) + self.wmask_enabled = False + if self.word_size !=self.write_size: + self.wmask_enabled = True + # initialize first wmask bit to 1, otherwise 0 + for bit in range(self.num_wmask): + if bit == 0: + self.wmask[self.num_wmask-1 - bit] = 1 + else: + self.wmask[self.num_wmask-1 - bit] = 0 + print(self.wmask) def run(self, feasible_period=None): if feasible_period: #period defaults to tech.py feasible period otherwise. @@ -71,23 +85,44 @@ class functional(simulation): check = 0 # First cycle idle - comment = self.gen_cycle_comment("noop", "0"*self.word_size, "0"*self.addr_size, 0, self.t_current) + comment = self.gen_cycle_comment("noop", "0"*self.word_size, "0"*self.addr_size, 0, self.wmask, self.t_current) self.add_noop_all_ports(comment, "0"*self.addr_size, "0"*self.word_size) # Write at least once addr = self.gen_addr() word = self.gen_data() - comment = self.gen_cycle_comment("write", word, addr, 0, self.t_current) - self.add_write(comment, addr, word, 0) - self.stored_words[addr] = word - + comment = self.gen_cycle_comment("write", word, addr, 0, self.wmask, self.t_current) + self.add_write(comment, addr, word, self.wmask, 0) + if self.wmask_enabled: + old_word = "" + if self.stored_words.get(addr) == None: + for i in range(self.word_size): + old_word += "X" + else: + old_word = self.stored_words[addr] + for bit in self.wmask: + # Don't write the bits of the new word to the address + if self.wmask[bit] == 0: + lower = bit * self.write_size + upper = lower + self.write_size - 1 + if bit == self.num_wmask-1: + word = word[0:lower] + old_word[lower:upper+1] + elif bit == 0: + word = old_word[lower:upper+1] + word [upper+1:self.word_size] + else: + word = word[0:lower] + old_word[lower:upper+1] + word [upper+1:self.word_size]cfusms + #word = word.replace(word[lower:upper+1],old_word[lower:upper+1],1) + self.stored_words[addr] = word + else: + self.stored_words[addr] = word + # Read at least once. For multiport, it is important that one read cycle uses all RW and R port to read from the same address simultaniously. # This will test the viablilty of the transistor sizing in the bitcell. for port in self.all_ports: if port in self.write_ports: self.add_noop_one_port("0"*self.addr_size, "0"*self.word_size, port) else: - comment = self.gen_cycle_comment("read", word, addr, port, self.t_current) + comment = self.gen_cycle_comment("read", word, addr, port, self.wmask, self.t_current) self.add_read_one_port(comment, addr, rw_read_din_data, port) self.write_check.append([word, "{0}{1}".format(self.dout_name,port), self.t_current+self.period, check]) check += 1 @@ -95,6 +130,7 @@ class functional(simulation): self.t_current += self.period # Perform a random sequence of writes and reads on random ports, using random addresses and random words + # and random write masks (if applicable) for i in range(self.num_cycles): w_addrs = [] for port in self.all_ports: @@ -112,11 +148,12 @@ class functional(simulation): elif op == "write": addr = self.gen_addr() word = self.gen_data() + wmask = self.gen_wmask() # two ports cannot write to the same address if addr in w_addrs: self.add_noop_one_port("0"*self.addr_size, "0"*self.word_size, port) else: - comment = self.gen_cycle_comment("write", word, addr, port, self.t_current) + comment = self.gen_cycle_comment("write", word, addr, port, wmask, self.t_current) self.add_write_one_port(comment, addr, word, port) self.stored_words[addr] = word w_addrs.append(addr) @@ -126,7 +163,7 @@ class functional(simulation): if addr in w_addrs: self.add_noop_one_port("0"*self.addr_size, "0"*self.word_size, port) else: - comment = self.gen_cycle_comment("read", word, addr, port, self.t_current) + comment = self.gen_cycle_comment("read", word, addr, port, self.wmask, self.t_current) self.add_read_one_port(comment, addr, rw_read_din_data, port) self.write_check.append([word, "{0}{1}".format(self.dout_name,port), self.t_current+self.period, check]) check += 1 @@ -135,7 +172,7 @@ class functional(simulation): self.t_current += self.period # Last cycle idle needed to correctly measure the value on the second to last clock edge - comment = self.gen_cycle_comment("noop", "0"*self.word_size, "0"*self.addr_size, 0, self.t_current) + comment = self.gen_cycle_comment("noop", "0"*self.word_size, "0"*self.addr_size, 0, self.wmask, self.t_current) self.add_noop_all_ports(comment, "0"*self.addr_size, "0"*self.word_size) def read_stim_results(self): @@ -170,7 +207,14 @@ class functional(simulation): self.read_check[i][2]) return(0, error) return(1, "SUCCESS") - + + def gen_wmask(self): + wmask_bits = [None]*self.num_wmask + for bit in range(self.num_wmask): + rand = random.randint(0, 1) + wmask_bits[bit] = rand + return wmask_bits + def gen_data(self): """ Generates a random word to write. """ rand = random.randint(0,(2**self.word_size)-1) diff --git a/compiler/characterizer/simulation.py b/compiler/characterizer/simulation.py index c5e26da1..53e73616 100644 --- a/compiler/characterizer/simulation.py +++ b/compiler/characterizer/simulation.py @@ -34,6 +34,10 @@ class simulation(): self.readwrite_ports = self.sram.readwrite_ports self.read_ports = self.sram.read_ports self.write_ports = self.sram.write_ports + self.num_wmask = int(self.word_size/self.write_size) + self.wmask_enabled = False + if self.word_size !=self.write_size: + self.wmask_enabled = True def set_corner(self,corner): """ Set the corner values """ @@ -127,7 +131,7 @@ class simulation(): debug.error("Non-binary address string",1) bit -= 1 - def add_write(self, comment, address, data, port): + def add_write(self, comment, address, data, wmask, port): """ Add the control values for a write cycle. """ debug.check(port in self.write_ports, "Cannot add write cycle to a read port. Port {0}, Write Ports {1}".format(port, self.write_ports)) debug.info(2, comment) @@ -223,18 +227,28 @@ class simulation(): time_spacing, comment)) - def gen_cycle_comment(self, op, word, addr, port, t_current): + def gen_cycle_comment(self, op, word, addr, port, wmask, t_current): if op == "noop": comment = "\tIdle during cycle {0} ({1}ns - {2}ns)".format(int(t_current/self.period), t_current, t_current+self.period) elif op == "write": - comment = "\tWriting {0} to address {1} (from port {2}) during cycle {3} ({4}ns - {5}ns)".format(word, - addr, - port, - int(t_current/self.period), - t_current, - t_current+self.period) + if (self.wmask_enabled): + comment = "\tWriting {0} to address {1} with mask bit {0} (from port {2}) during cycle {3} ({4}ns - {5}ns)".format(word, + addr, + wmask, + port, + int( + t_current / self.period), + t_current + self.period) + + else: + comment = "\tWriting {0} to address {1} (from port {2}) during cycle {3} ({4}ns - {5}ns)".format(word, + addr, + port, + int(t_current/self.period), + t_current, + t_current+self.period) else: comment = "\tReading {0} from address {1} (from port {2}) during cycle {3} ({4}ns - {5}ns)".format(word, addr, @@ -268,7 +282,9 @@ class simulation(): if (port in read_index) and (port in write_index): pin_names.append("WEB{0}".format(port)) if (self.write_size != self.word_size): - pin_names.append("WMASK{0}".format(port)) + num_wmask = int(self.word_size/self.write_size) + for bit in range(num_wmask): + pin_names.append("WMASK{0}_{1}".format(port,bit)) for port in range(total_ports): pin_names.append("{0}{1}".format(tech.spice["clk"], port)) diff --git a/compiler/modules/control_logic.py b/compiler/modules/control_logic.py index 09528509..e2138396 100644 --- a/compiler/modules/control_logic.py +++ b/compiler/modules/control_logic.py @@ -759,8 +759,6 @@ class control_logic(design.design): def route_dffs(self): if self.port_type == "rw": - #print("hi") - #if (self.word_size == self.write_size): dff_out_map = zip(["dout_bar_0", "dout_bar_1", "dout_1"], ["cs", "we", "we_bar"]) elif self.port_type == "r": dff_out_map = zip(["dout_bar_0", "dout_0"], ["cs", "cs_bar"]) diff --git a/compiler/modules/multibank.py b/compiler/modules/multibank.py index 25cce371..e99dab93 100644 --- a/compiler/modules/multibank.py +++ b/compiler/modules/multibank.py @@ -187,9 +187,15 @@ class multibank(design.design): words_per_row=self.words_per_row) self.add_mod(self.sense_amp_array) - self.write_driver_array = self.mod_write_driver_array(columns=self.num_cols, - word_size=self.word_size) - self.add_mod(self.write_driver_array) + if (self.write_size != self.word_size): + self.write_mask_driver_array = self.mod_write_mask_driver_array(columns=self.num_cols, + word_size=self.word_size, + write_size=self.write_size) + self.add_mod(self.write_mask_driver_array) + else: + self.write_driver_array = self.mod_write_driver_array(columns=self.num_cols, + word_size=self.word_size) + self.add_mod(self.write_driver_array) self.row_decoder = self.mod_decoder(rows=self.num_rows) self.add_mod(self.row_decoder) diff --git a/compiler/modules/port_data.py b/compiler/modules/port_data.py index ab594c09..b0c74a8d 100644 --- a/compiler/modules/port_data.py +++ b/compiler/modules/port_data.py @@ -21,6 +21,7 @@ class port_data(design.design): sram_config.set_local_config(self) self.port = port + self.num_wmask = int(self.word_size/self.write_size) if name == "": name = "port_data_{0}".format(self.port) @@ -53,6 +54,8 @@ class port_data(design.design): if self.write_driver_array: self.create_write_driver_array() + if (self.word_size != self.write_size): + self.create_write_mask_array() else: self.write_driver_array_inst = None @@ -164,6 +167,12 @@ class port_data(design.design): columns=self.num_cols, word_size=self.word_size) self.add_mod(self.write_driver_array) + if (self.word_size != self.write_size): + self.write_mask_array = factory.create(module_type="write_mask_array", + columns=self.num_cols, + word_size=self.word_size, + write_size=self.write_size) + self.add_mod(self.write_driver_array) else: self.write_driver_array = None @@ -279,6 +288,18 @@ class port_data(design.design): temp.extend(["w_en", "vdd", "gnd"]) self.connect_inst(temp) + + def create_write_mask_array(self): + """ Creating Write Masks """ + self.write_mask_array_inst = self.add_inst(name="write_mask_array{}".format(self.port), + mod=self.write_mask_array) + + temp = [] + for bit in range(self.num_wmask): + temp.append("write_mask_".format(bit)) + temp.extend(["w_en", "vdd", "gnd"]) + self.connect_inst(temp) + def place_write_driver_array(self, offset): """ Placing Write Driver """ diff --git a/compiler/modules/write_mask_array.py b/compiler/modules/write_mask_array.py new file mode 100644 index 00000000..1a349289 --- /dev/null +++ b/compiler/modules/write_mask_array.py @@ -0,0 +1,158 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +from math import log +import design +from tech import drc +import debug +from sram_factory import factory +from vector import vector +from globals import OPTS + + +class write_mask_array(design.design): + """ + Array of tristate drivers to write to the bitlines through the column mux. + Dynamically generated write driver array of all bitlines. + """ + + def __init__(self, name, columns, word_size, write_size): + design.design.__init__(self, name) + debug.info(1, "Creating {0}".format(self.name)) + self.add_comment("columns: {0}".format(columns)) + self.add_comment("word_size {0}".format(word_size)) + self.add_comment("write_size {0}".format(write_size)) + + self.columns = columns + self.word_size = word_size + self.write_size = write_size + self.words_per_row = int(columns / word_size) + self.num_wmask = int(word_size / write_size) + + self.create_netlist() + # if not OPTS.netlist_only: + # self.create_layout() + + def create_netlist(self): + self.add_modules() + self.add_pins() + self.create_write_mask_array() + self.create_and2_array() + + + # def create_layout(self): + # + # if self.bitcell.width > self.driver.width: + # self.width = self.columns * self.bitcell.width + # else: + # self.width = self.columns * self.driver.width + # + # self.height = self.driver.height + # + # self.place_write_array() + # self.add_layout_pins() + # self.add_boundary() + # self.DRC_LVS() + + def add_pins(self): + for bit in range(self.num_wmask): + self.add_pin("wdriver_sel_{}".format(bit)) + self.add_pin("en") + self.add_pin("vdd") + self.add_pin("gnd") + + def add_modules(self): + self.wmask = factory.create(module_type="dff_buf") + self.add_mod(self.wmask) + dff_height = self.wmask.height + + self.and2 = factory.create(module_type="pand2", + size=4, + height=dff_height) + self.add_mod(self.and2) + + + def create_write_mask_array(self): + self.wmask_insts = {} + for bit in range(self.num_wmask): + name = "write_mask_{}".format(bit) + self.wmask_insts[bit] = self.add_inst(name=name, + mod=self.wmask) + + self.connect_inst(["wmask_{}".format(bit), + "bank_wmask_{}".format(bit), + "bank_wmask_bar_{}".format(bit), + "clk", "vdd", "gnd"]) + + def create_and2_array(self): + self.and2_insts = {} + for bit in range(self.num_wmask): + name = "and2_{}".format(bit) + self.and2_insts[bit] = self.add_inst(name=name, + mod=self.and2) + self.connect_inst(["bank_wmask_{}".format(bit), + "en", + "wdriver_sel_{}".format(bit), + "vdd", "gnd"]) + + + # def place_write_array(self): + # if self.bitcell.width > self.driver.width: + # driver_spacing = self.bitcell.width + # else: + # driver_spacing = self.driver.width + # + # for i in range(0, self.columns, self.words_per_row): + # index = int(i / self.words_per_row) + # base = vector(i * driver_spacing, 0) + # self.driver_insts[index].place(base) + + # def add_layout_pins(self): + # for i in range(self.word_size): + # din_pin = self.driver_insts[i].get_pin("din") + # self.add_layout_pin(text="data_{0}".format(i), + # layer="metal2", + # offset=din_pin.ll(), + # width=din_pin.width(), + # height=din_pin.height()) + # bl_pin = self.driver_insts[i].get_pin("bl") + # self.add_layout_pin(text="bl_{0}".format(i), + # layer="metal2", + # offset=bl_pin.ll(), + # width=bl_pin.width(), + # height=bl_pin.height()) + # + # br_pin = self.driver_insts[i].get_pin("br") + # self.add_layout_pin(text="br_{0}".format(i), + # layer="metal2", + # offset=br_pin.ll(), + # width=br_pin.width(), + # height=br_pin.height()) + # + # for n in ["vdd", "gnd"]: + # pin_list = self.driver_insts[i].get_pins(n) + # for pin in pin_list: + # pin_pos = pin.center() + # # Add the M2->M3 stack + # self.add_via_center(layers=("metal2", "via2", "metal3"), + # offset=pin_pos) + # self.add_layout_pin_rect_center(text=n, + # layer="metal3", + # offset=pin_pos) + # + # self.add_layout_pin(text="en", + # layer="metal1", + # offset=self.driver_insts[0].get_pin("en").ll().scale(0, 1), + # width=self.width, + # height=drc('minwidth_metal1')) + + # def get_w_en_cin(self): + # """Get the relative capacitance of all the enable connections in the bank""" + # # The enable is connected to a nand2 for every row. + # return self.driver.get_w_en_cin() * len(self.driver_insts) + + diff --git a/compiler/sram/sram_1bank.py b/compiler/sram/sram_1bank.py index c51e51fd..1d749e16 100644 --- a/compiler/sram/sram_1bank.py +++ b/compiler/sram/sram_1bank.py @@ -128,9 +128,9 @@ class sram_1bank(sram_base): # Add the write mask flops to the left of the din flops. if (self.write_size != self.word_size): if port in self.write_ports: - wmask_pos[port] = vector(self.bank.bank_array_ur.x - self.data_dff_insts[port].width, - self.bank.height + max_gap_size + self.data_dff_insts[port].height) - self.wmask_dff_insts[port].place(wmask_pos[port], mirror="MX") + wmask_pos[port] = vector(self.bank.bank_array_ll.x - self.control_logic_insts[port].width, + -max_gap_size - self.wmask_dff_insts[port].height) + self.wmask_dff_insts[port].place(wmask_pos[port]) if len(self.all_ports)>1: diff --git a/compiler/sram/sram_base.py b/compiler/sram/sram_base.py index 821c75d4..6295f587 100644 --- a/compiler/sram/sram_base.py +++ b/compiler/sram/sram_base.py @@ -40,6 +40,7 @@ class sram_base(design, verilog, lef): def add_pins(self): """ Add pins for entire SRAM. """ + self.num_masks = int(self.word_size/self.write_size) for port in self.write_ports: for bit in range(self.word_size): self.add_pin("DIN{0}[{1}]".format(port,bit),"INPUT") @@ -71,7 +72,8 @@ class sram_base(design, verilog, lef): # add the optional write mask pins if self.word_size != self.write_size: for port in self.write_ports: - self.add_pin("wmask{}".format(port),"INPUT") + for bit in range(self.num_masks): + self.add_pin("wmask{0}[{1}]".format(port,bit),"INPUT") for port in self.read_ports: for bit in range(self.word_size): self.add_pin("DOUT{0}[{1}]".format(port,bit),"OUTPUT") @@ -149,7 +151,7 @@ class sram_base(design, verilog, lef): elif "metal3" in tech.layer: from supply_tree_router import supply_tree_router as router rtr=router(("metal3",), self) - + rtr.route() @@ -278,8 +280,10 @@ class sram_base(design, verilog, lef): self.data_dff = dff_array(name="data_dff", rows=1, columns=self.word_size) self.add_mod(self.data_dff) - self.wmask_dff = dff_array(name="wmask_dff", rows=1, columns=int(self.word_size/self.write_size)) - self.add_mod(self.wmask_dff) + if (self.write_size != self.word_size): + num_wmask = int(self.word_size/self.write_size) + self.wmask_dff = dff_array(name="wmask_dff", rows=1, columns=num_wmask) + self.add_mod(self.wmask_dff) # Create the bank module (up to four are instantiated) @@ -469,7 +473,7 @@ class sram_base(design, verilog, lef): outputs = [] for bit in range(num_wmask): inputs.append("wmask{}[{}]".format(port, bit)) - outputs.append("BANK_WMASK{}[{}]".format(port, bit)) + outputs.append("bank_wmask{}[{}]".format(port, bit)) self.connect_inst(inputs + outputs + ["clk_buf{}".format(port), "vdd", "gnd"]) diff --git a/compiler/tests/22_sram_wmask_func_test.py b/compiler/tests/22_sram_wmask_func_test.py new file mode 100755 index 00000000..ebf2137f --- /dev/null +++ b/compiler/tests/22_sram_wmask_func_test.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +#@unittest.skip("SKIPPING sram_wmask_func_test") +class sram_wmask_func_test(openram_test): + + def runTest(self): + globals.init_openram("config_{0}".format(OPTS.tech_name)) + OPTS.analytical_delay = False + OPTS.netlist_only = True + OPTS.trim_netlist = False + + # This is a hack to reload the characterizer __init__ with the spice version + from importlib import reload + import characterizer + reload(characterizer) + from characterizer import functional, delay + from sram_config import sram_config + c = sram_config(word_size=8, + num_words=16, + write_size=4, + num_banks=1) + c.words_per_row=4 + c.recompute_sizes() + debug.info(1, "Functional test for sram with {} bit words, {} words, {} words per row, {} bit writes, {} banks".format(c.word_size, + c.num_words, + c.words_per_row, + c.write_size, + c.num_banks)) + s = factory.create(module_type="sram", sram_config=c) + tempspice = OPTS.openram_temp + "temp.sp" + s.sp_write(tempspice) + + corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) + + f = functional(s.s, tempspice, corner) + f.num_cycles = 10 + (fail, error) = f.run() + self.assertTrue(fail, error) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner())