diff --git a/.gitignore b/.gitignore index 71c19b47..7a6ad08b 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,6 @@ technology/freepdk45/ncsu_basekit technology/sky130/*_lib technology/sky130/tech/.magicrc .idea +compiler/tests/results/ +sky*/ +open_pdks/ diff --git a/compiler/base/design.py b/compiler/base/design.py index ccd9cec1..89f7ba82 100644 --- a/compiler/base/design.py +++ b/compiler/base/design.py @@ -78,7 +78,7 @@ class design(hierarchy_design): for pin_name in self.pins: pins = self.get_pins(pin_name) for pin in pins: - print(pin_name, pin) + debug.info(0, "{0} {1}".format(pin_name, pin)) def setup_multiport_constants(self): """ diff --git a/compiler/base/hierarchy_layout.py b/compiler/base/hierarchy_layout.py index fc22f3c0..c0da0244 100644 --- a/compiler/base/hierarchy_layout.py +++ b/compiler/base/hierarchy_layout.py @@ -141,30 +141,28 @@ class layout(): layout.active_space) # These are for debugging previous manual rules - if False: - print("poly_width", layout.poly_width) - print("poly_space", layout.poly_space) - print("m1_width", layout.m1_width) - print("m1_space", layout.m1_space) - print("m2_width", layout.m2_width) - print("m2_space", layout.m2_space) - print("m3_width", layout.m3_width) - print("m3_space", layout.m3_space) - print("m4_width", layout.m4_width) - print("m4_space", layout.m4_space) - print("active_width", layout.active_width) - print("active_space", layout.active_space) - print("contact_width", layout.contact_width) - print("poly_to_active", layout.poly_to_active) - print("poly_extend_active", layout.poly_extend_active) - print("poly_to_contact", layout.poly_to_contact) - print("active_contact_to_gate", layout.active_contact_to_gate) - print("poly_contact_to_gate", layout.poly_contact_to_gate) - print("well_enclose_active", layout.well_enclose_active) - print("implant_enclose_active", layout.implant_enclose_active) - print("implant_space", layout.implant_space) - import sys - sys.exit(1) + level=99 + debug.info(level, "poly_width".format(layout.poly_width)) + debug.info(level, "poly_space".format(layout.poly_space)) + debug.info(level, "m1_width".format(layout.m1_width)) + debug.info(level, "m1_space".format(layout.m1_space)) + debug.info(level, "m2_width".format(layout.m2_width)) + debug.info(level, "m2_space".format(layout.m2_space)) + debug.info(level, "m3_width".format(layout.m3_width)) + debug.info(level, "m3_space".format(layout.m3_space)) + debug.info(level, "m4_width".format(layout.m4_width)) + debug.info(level, "m4_space".format(layout.m4_space)) + debug.info(level, "active_width".format(layout.active_width)) + debug.info(level, "active_space".format(layout.active_space)) + debug.info(level, "contact_width".format(layout.contact_width)) + debug.info(level, "poly_to_active".format(layout.poly_to_active)) + debug.info(level, "poly_extend_active".format(layout.poly_extend_active)) + debug.info(level, "poly_to_contact".format(layout.poly_to_contact)) + debug.info(level, "active_contact_to_gate".format(layout.active_contact_to_gate)) + debug.info(level, "poly_contact_to_gate".format(layout.poly_contact_to_gate)) + debug.info(level, "well_enclose_active".format(layout.well_enclose_active)) + debug.info(level, "implant_enclose_active".format(layout.implant_enclose_active)) + debug.info(level, "implant_space".format(layout.implant_space)) @classmethod def setup_layer_constants(layout): @@ -202,21 +200,19 @@ class layout(): "{}_nonpref_pitch".format(layer_id), layout.compute_pitch(layer_id, False)) - if False: - for name in tech_layer_indices: - if name == "active": - continue - try: - print("{0} width {1} space {2}".format(name, - getattr(layout, "{}_width".format(name)), - getattr(layout, "{}_space".format(name)))) + level=99 + for name in tech_layer_indices: + if name == "active": + continue + try: + debug.info(level, "{0} width {1} space {2}".format(name, + getattr(layout, "{}_width".format(name)), + getattr(layout, "{}_space".format(name)))) - print("pitch {0} nonpref {1}".format(getattr(layout, "{}_pitch".format(name)), - getattr(layout, "{}_nonpref_pitch".format(name)))) - except AttributeError: - pass - import sys - sys.exit(1) + debug.info(level, "pitch {0} nonpref {1}".format(getattr(layout, "{}_pitch".format(name)), + getattr(layout, "{}_nonpref_pitch".format(name)))) + except AttributeError: + pass @staticmethod def compute_pitch(layer, preferred=True): diff --git a/compiler/base/verilog.py b/compiler/base/verilog.py index 414b4491..b93b52e9 100644 --- a/compiler/base/verilog.py +++ b/compiler/base/verilog.py @@ -24,7 +24,7 @@ class verilog: self.vf.write("// OpenRAM SRAM model\n") self.vf.write("// Words: {0}\n".format(self.num_words)) self.vf.write("// Word size: {0}\n".format(self.word_size)) - if self.write_size: + if self.write_size != self.word_size: self.vf.write("// Write size: {0}\n\n".format(self.write_size)) else: self.vf.write("\n") @@ -38,7 +38,10 @@ class verilog: except KeyError: self.gnd_name = "gnd" - self.vf.write("module {0}(\n".format(self.name)) + if self.num_banks > 1: + self.vf.write("module {0}(\n".format(self.name)) + else: + self.vf.write("module {0}(\n".format(self.name)) self.vf.write("`ifdef USE_POWER_PINS\n") self.vf.write(" {},\n".format(self.vdd_name)) self.vf.write(" {},\n".format(self.gnd_name)) @@ -53,14 +56,14 @@ class verilog: self.vf.write("// Port {0}: W\n".format(port)) if port in self.readwrite_ports: self.vf.write(" clk{0},csb{0},web{0},".format(port)) - if self.write_size: + if self.write_size != self.word_size: self.vf.write("wmask{},".format(port)) if self.num_spare_cols > 0: self.vf.write("spare_wen{0},".format(port)) self.vf.write("addr{0},din{0},dout{0}".format(port)) elif port in self.write_ports: self.vf.write(" clk{0},csb{0},".format(port)) - if self.write_size: + if self.write_size != self.word_size: self.vf.write("wmask{},".format(port)) if self.num_spare_cols > 0: self.vf.write("spare_wen{0},".format(port)) @@ -72,11 +75,11 @@ class verilog: self.vf.write(",\n") self.vf.write("\n );\n\n") - if self.write_size: + if self.write_size != self.word_size: self.num_wmasks = int(math.ceil(self.word_size / self.write_size)) self.vf.write(" parameter NUM_WMASKS = {0} ;\n".format(self.num_wmasks)) self.vf.write(" parameter DATA_WIDTH = {0} ;\n".format(self.word_size + self.num_spare_cols)) - self.vf.write(" parameter ADDR_WIDTH = {0} ;\n".format(self.addr_size)) + self.vf.write(" parameter ADDR_WIDTH = {0} ;\n".format(self.bank_addr_size)) self.vf.write(" parameter RAM_DEPTH = 1 << ADDR_WIDTH;\n") self.vf.write(" // FIXME: This delay is arbitrary.\n") self.vf.write(" parameter DELAY = 3 ;\n") @@ -125,7 +128,7 @@ class verilog: if port in self.readwrite_ports: self.vf.write(" reg web{0}_reg;\n".format(port)) if port in self.write_ports: - if self.write_size: + if self.write_size != self.word_size: self.vf.write(" reg [NUM_WMASKS-1:0] wmask{0}_reg;\n".format(port)) if self.num_spare_cols > 1: self.vf.write(" reg [{1}:0] spare_wen{0}_reg;".format(port, self.num_spare_cols - 1)) @@ -149,7 +152,7 @@ class verilog: if port in self.readwrite_ports: self.vf.write(" web{0}_reg = web{0};\n".format(port)) if port in self.write_ports: - if self.write_size: + if self.write_size != self.word_size: self.vf.write(" wmask{0}_reg = wmask{0};\n".format(port)) if self.num_spare_cols: self.vf.write(" spare_wen{0}_reg = spare_wen{0};\n".format(port)) @@ -169,13 +172,13 @@ class verilog: self.vf.write(" $display($time,\" Reading %m addr{0}=%b dout{0}=%b\",addr{0}_reg,mem[addr{0}_reg]);\n".format(port)) if port in self.readwrite_ports: self.vf.write(" if ( !csb{0}_reg && !web{0}_reg && VERBOSE )\n".format(port)) - if self.write_size: + if self.write_size != self.word_size: self.vf.write(" $display($time,\" Writing %m addr{0}=%b din{0}=%b wmask{0}=%b\",addr{0}_reg,din{0}_reg,wmask{0}_reg);\n".format(port)) else: self.vf.write(" $display($time,\" Writing %m addr{0}=%b din{0}=%b\",addr{0}_reg,din{0}_reg);\n".format(port)) elif port in self.write_ports: self.vf.write(" if ( !csb{0}_reg && VERBOSE )\n".format(port)) - if self.write_size: + if self.write_size != self.word_size: self.vf.write(" $display($time,\" Writing %m addr{0}=%b din{0}=%b wmask{0}=%b\",addr{0}_reg,din{0}_reg,wmask{0}_reg);\n".format(port)) else: self.vf.write(" $display($time,\" Writing %m addr{0}=%b din{0}=%b\",addr{0}_reg,din{0}_reg);\n".format(port)) @@ -193,7 +196,7 @@ class verilog: self.vf.write(" input [ADDR_WIDTH-1:0] addr{0};\n".format(port)) if port in self.write_ports: - if self.write_size: + if self.write_size != self.word_size: self.vf.write(" input [NUM_WMASKS-1:0] wmask{0}; // write mask\n".format(port)) if self.num_spare_cols == 1: self.vf.write(" input spare_wen{0}; // spare mask\n".format(port)) @@ -218,7 +221,7 @@ class verilog: else: self.vf.write(" if (!csb{0}_reg) begin\n".format(port)) - if self.write_size: + if self.write_size != self.word_size: for mask in range(0, self.num_wmasks): lower = mask * self.write_size upper = lower + self.write_size - 1 diff --git a/compiler/characterizer/cacti.py b/compiler/characterizer/cacti.py index 5dab9618..f1af8fbd 100644 --- a/compiler/characterizer/cacti.py +++ b/compiler/characterizer/cacti.py @@ -24,7 +24,7 @@ class cacti(simulation): # self.targ_read_ports = [] # self.targ_write_ports = [] # self.period = 0 - # if self.write_size: + # if self.write_size != self.word_size: # self.num_wmasks = int(math.ceil(self.word_size / self.write_size)) # else: # self.num_wmasks = 0 @@ -51,7 +51,7 @@ class cacti(simulation): debug.warning("In analytical mode, all ports have the timing of the first read port.") # Probe set to 0th bit, does not matter for analytical delay. - self.set_probe('0' * self.addr_size, 0) + self.set_probe('0' * self.bank_addr_size, 0) self.create_graph() self.set_internal_spice_names() self.create_measurement_names() diff --git a/compiler/characterizer/delay.py b/compiler/characterizer/delay.py index 40a623c6..8d0aa536 100644 --- a/compiler/characterizer/delay.py +++ b/compiler/characterizer/delay.py @@ -43,7 +43,7 @@ class delay(simulation): self.targ_read_ports = [] self.targ_write_ports = [] self.period = 0 - if self.write_size: + if self.write_size != self.word_size: self.num_wmasks = int(math.ceil(self.word_size / self.write_size)) else: self.num_wmasks = 0 @@ -342,7 +342,7 @@ class delay(simulation): except ValueError: debug.error("Probe Address is not of binary form: {0}".format(self.probe_address), 1) - if len(self.probe_address) != self.addr_size: + if len(self.probe_address) != self.bank_addr_size: debug.error("Probe Address's number of bits does not correspond to given SRAM", 1) if not isinstance(self.probe_data, int) or self.probe_data>self.word_size or self.probe_data<0: @@ -455,7 +455,7 @@ class delay(simulation): self.stim.gen_constant(sig_name="{0}{1}_{2} ".format(self.din_name, write_port, i), v_val=0) for port in self.all_ports: - for i in range(self.addr_size): + for i in range(self.bank_addr_size): self.stim.gen_constant(sig_name="{0}{1}_{2}".format(self.addr_name, port, i), v_val=0) @@ -1391,7 +1391,7 @@ class delay(simulation): """ for port in self.all_ports: - for i in range(self.addr_size): + for i in range(self.bank_addr_size): sig_name = "{0}{1}_{2}".format(self.addr_name, port, i) self.stim.gen_pwl(sig_name, self.cycle_times, self.addr_values[port][i], self.period, self.slew, 0.05) diff --git a/compiler/characterizer/elmore.py b/compiler/characterizer/elmore.py index 5fee4223..5f9eabfd 100644 --- a/compiler/characterizer/elmore.py +++ b/compiler/characterizer/elmore.py @@ -21,7 +21,7 @@ class elmore(simulation): # self.targ_read_ports = [] # self.targ_write_ports = [] # self.period = 0 - # if self.write_size: + # if self.write_size != self.word_size: # self.num_wmasks = int(math.ceil(self.word_size / self.write_size)) # else: # self.num_wmasks = 0 @@ -45,7 +45,7 @@ class elmore(simulation): debug.warning("In analytical mode, all ports have the timing of the first read port.") # Probe set to 0th bit, does not matter for analytical delay. - self.set_probe('0' * self.addr_size, 0) + self.set_probe('0' * self.bank_addr_size, 0) self.create_graph() self.set_internal_spice_names() self.create_measurement_names() @@ -106,4 +106,4 @@ class elmore(simulation): power.leakage /= 1e6 debug.info(1, "Dynamic Power: {0} mW".format(power.dynamic)) debug.info(1, "Leakage Power: {0} mW".format(power.leakage)) - return power \ No newline at end of file + return power diff --git a/compiler/characterizer/functional.py b/compiler/characterizer/functional.py index e7d03f25..0aa9ebcd 100644 --- a/compiler/characterizer/functional.py +++ b/compiler/characterizer/functional.py @@ -44,7 +44,7 @@ class functional(simulation): else: self.output_path = output_path - if self.write_size: + if self.write_size != self.word_size: self.num_wmasks = int(math.ceil(self.word_size / self.write_size)) else: self.num_wmasks = 0 @@ -60,7 +60,7 @@ class functional(simulation): self.addr_spare_index = -int(math.log(self.words_per_row) / math.log(2)) else: # This will select the entire address when one word per row - self.addr_spare_index = self.addr_size + self.addr_spare_index = self.bank_addr_size # If trim is set, specify the valid addresses self.valid_addresses = set() self.max_address = self.num_rows * self.words_per_row - 1 @@ -68,7 +68,7 @@ class functional(simulation): for i in range(self.words_per_row): self.valid_addresses.add(i) self.valid_addresses.add(self.max_address - i - 1) - self.probe_address, self.probe_data = '0' * self.addr_size, 0 + self.probe_address, self.probe_data = '0' * self.bank_addr_size, 0 self.set_corner(corner) self.set_spice_constants() self.set_stimulus_variables() @@ -133,7 +133,7 @@ class functional(simulation): def create_random_memory_sequence(self): # Select randomly, but have 3x more reads to increase probability - if self.write_size: + if self.write_size != self.word_size: rw_ops = ["noop", "write", "partial_write", "read", "read"] w_ops = ["noop", "write", "partial_write"] else: @@ -142,7 +142,7 @@ class functional(simulation): r_ops = ["noop", "read"] # First cycle idle is always an idle cycle - comment = self.gen_cycle_comment("noop", "0" * self.word_size, "0" * self.addr_size, "0" * self.num_wmasks, 0, self.t_current) + comment = self.gen_cycle_comment("noop", "0" * self.word_size, "0" * self.bank_addr_size, "0" * self.num_wmasks, 0, self.t_current) self.add_noop_all_ports(comment) @@ -244,7 +244,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.num_wmasks, 0, self.t_current) + comment = self.gen_cycle_comment("noop", "0" * self.word_size, "0" * self.bank_addr_size, "0" * self.num_wmasks, 0, self.t_current) self.add_noop_all_ports(comment) def gen_masked_data(self, old_word, word, wmask): @@ -366,7 +366,7 @@ class functional(simulation): random_value = random.sample(self.valid_addresses, 1)[0] else: random_value = random.randint(0, self.max_address) - addr_bits = binary_repr(random_value, self.addr_size) + addr_bits = binary_repr(random_value, self.bank_addr_size) return addr_bits def get_data(self): @@ -426,7 +426,7 @@ class functional(simulation): # Generate address bits for port in self.all_ports: - for bit in range(self.addr_size): + for bit in range(self.bank_addr_size): sig_name="{0}{1}_{2} ".format(self.addr_name, port, bit) self.stim.gen_pwl(sig_name, self.cycle_times, self.addr_values[port][bit], self.period, self.slew, 0.05) @@ -440,7 +440,7 @@ class functional(simulation): # Generate wmask bits for port in self.write_ports: - if self.write_size: + if self.write_size != self.word_size: self.sf.write("\n* Generation of wmask signals\n") for bit in range(self.num_wmasks): sig_name = "WMASK{0}_{1} ".format(port, bit) diff --git a/compiler/characterizer/lib.py b/compiler/characterizer/lib.py index 65089297..8d8674b9 100644 --- a/compiler/characterizer/lib.py +++ b/compiler/characterizer/lib.py @@ -183,7 +183,8 @@ class lib: # set the read and write port as inputs. self.write_data_bus(port) self.write_addr_bus(port) - if self.sram.write_size and port in self.write_ports: + if self.sram.write_size != self.sram.word_size and \ + port in self.write_ports: self.write_wmask_bus(port) # need to split this into sram and port control signals self.write_control_pins(port) @@ -193,8 +194,8 @@ class lib: def write_footer(self): """ Write the footer """ - self.lib.write(" }\n") #Closing brace for the cell - self.lib.write("}\n") #Closing brace for the library + self.lib.write(" }\n") # Closing brace for the cell + self.lib.write("}\n") # Closing brace for the library def write_header(self): """ Write the header information """ @@ -378,7 +379,7 @@ class lib: self.lib.write(" bit_to : 0;\n") self.lib.write(" }\n\n") - if self.sram.write_size: + if self.sram.write_size != self.sram.word_size: self.lib.write(" type (wmask){\n") self.lib.write(" base_type : array;\n") self.lib.write(" data_type : bit;\n") diff --git a/compiler/characterizer/simulation.py b/compiler/characterizer/simulation.py index b2ccd79e..5649099c 100644 --- a/compiler/characterizer/simulation.py +++ b/compiler/characterizer/simulation.py @@ -20,7 +20,7 @@ class simulation(): self.name = self.sram.name self.word_size = self.sram.word_size - self.addr_size = self.sram.addr_size + self.bank_addr_size = self.sram.bank_addr_size self.write_size = self.sram.write_size self.num_spare_rows = self.sram.num_spare_rows if not self.sram.num_spare_cols: @@ -39,7 +39,7 @@ class simulation(): self.words_per_row = self.sram.words_per_row self.num_rows = self.sram.num_rows self.num_cols = self.sram.num_cols - if self.write_size: + if self.write_size != self.word_size: self.num_wmasks = int(math.ceil(self.word_size / self.write_size)) else: self.num_wmasks = 0 @@ -80,7 +80,7 @@ class simulation(): self.dout_name = "dout" self.pins = self.gen_pin_names(port_signal_names=(self.addr_name, self.din_name, self.dout_name), port_info=(len(self.all_ports), self.write_ports, self.read_ports), - abits=self.addr_size, + abits=self.bank_addr_size, dbits=self.word_size + self.num_spare_cols) debug.check(len(self.sram.pins) == len(self.pins), "Number of pins generated for characterization \ @@ -103,7 +103,7 @@ class simulation(): self.spare_wen_value = {port: [] for port in self.write_ports} # Three dimensional list to handle each addr and data bits for each port over the number of checks - self.addr_values = {port: [[] for bit in range(self.addr_size)] for port in self.all_ports} + self.addr_values = {port: [[] for bit in range(self.bank_addr_size)] for port in self.all_ports} self.data_values = {port: [[] for bit in range(self.word_size + self.num_spare_cols)] for port in self.write_ports} self.wmask_values = {port: [[] for bit in range(self.num_wmasks)] for port in self.write_ports} self.spare_wen_values = {port: [[] for bit in range(self.num_spare_cols)] for port in self.write_ports} @@ -174,10 +174,10 @@ class simulation(): def add_address(self, address, port): """ Add the array of address values """ - debug.check(len(address)==self.addr_size, "Invalid address size.") + debug.check(len(address)==self.bank_addr_size, "Invalid address size.") self.addr_value[port].append(address) - bit = self.addr_size - 1 + bit = self.bank_addr_size - 1 for c in address: if c=="0": self.addr_values[port][bit].append(0) @@ -330,7 +330,7 @@ class simulation(): try: self.add_address(self.addr_value[port][-1], port) except: - self.add_address("0" * self.addr_size, port) + self.add_address("0" * self.bank_addr_size, port) # If the port is also a readwrite then add # the same value as previous cycle @@ -464,7 +464,7 @@ class simulation(): for port in range(total_ports): pin_names.append("{0}{1}".format("clk", port)) - if self.write_size: + if self.write_size != self.word_size: for port in write_index: for bit in range(self.num_wmasks): pin_names.append("WMASK{0}_{1}".format(port, bit)) diff --git a/compiler/debug.py b/compiler/debug.py index 11da4b3c..4052e713 100644 --- a/compiler/debug.py +++ b/compiler/debug.py @@ -97,6 +97,10 @@ log.create_file = True def info(lev, str): from globals import OPTS + # 99 is a special never print level + if lev == 99: + return + if (OPTS.verbose_level >= lev): frm = inspect.stack()[1] mod = inspect.getmodule(frm[0]) diff --git a/compiler/globals.py b/compiler/globals.py index 3be911c5..9a185052 100644 --- a/compiler/globals.py +++ b/compiler/globals.py @@ -378,6 +378,11 @@ def read_config(config_file, is_unit_test=True): ports, OPTS.tech_name) + # If write size is not defined, set it equal to word size + if OPTS.write_size is None: + OPTS.write_size = OPTS.word_size + + def end_openram(): """ Clean up openram for a proper exit """ cleanup_paths() diff --git a/compiler/modules/bank.py b/compiler/modules/bank.py index 88d8abc7..c9279d2a 100644 --- a/compiler/modules/bank.py +++ b/compiler/modules/bank.py @@ -27,7 +27,7 @@ class bank(design): self.sram_config = sram_config sram_config.set_local_config(self) - if self.write_size: + if self.write_size != self.word_size: self.num_wmasks = int(ceil(self.word_size / self.write_size)) else: self.num_wmasks = 0 @@ -44,10 +44,7 @@ class bank(design): # The local control signals are gated when we have bank select logic, # so this prefix will be added to all of the input signals to create # the internal gated signals. - if self.num_banks>1: - self.prefix="gated_" - else: - self.prefix="" + self.prefix="" self.create_netlist() if not OPTS.netlist_only: @@ -93,14 +90,11 @@ class bank(design): for bit in range(self.word_size + self.num_spare_cols): self.add_pin("din{0}_{1}".format(port, bit), "INPUT") for port in self.all_ports: - for bit in range(self.addr_size): + for bit in range(self.bank_addr_size): self.add_pin("addr{0}_{1}".format(port, bit), "INPUT") # For more than one bank, we have a bank select and name # the signals gated_*. - if self.num_banks > 1: - for port in self.all_ports: - self.add_pin("bank_sel{}".format(port), "INPUT") for port in self.read_ports: self.add_pin("s_en{0}".format(port), "INPUT") for port in self.all_ports: @@ -128,8 +122,6 @@ class bank(design): self.route_port_address(port) self.route_column_address_lines(port) self.route_control_lines(port) - if self.num_banks > 1: - self.route_bank_select(port) self.route_supplies() @@ -171,7 +163,6 @@ class bank(design): self.create_port_data() self.create_port_address() self.create_column_decoder() - self.create_bank_select() def compute_instance_offsets(self): """ @@ -252,8 +243,6 @@ class bank(design): y_offset = min(self.column_decoder_offsets[port].y, self.port_data[port].column_mux_offset.y) else: y_offset = self.port_address_offsets[port].y - if self.num_banks > 1: - y_offset += self.bank_select.height + drc("well_to_well") self.bank_select_offsets[port] = vector(-x_offset, -y_offset) def compute_instance_port1_offsets(self): @@ -311,22 +300,25 @@ class bank(design): self.place_port_address(self.port_address_offsets) self.place_column_decoder(self.column_decoder_offsets) - self.place_bank_select(self.bank_select_offsets) + # self.place_bank_select(self.bank_select_offsets) def compute_sizes(self): """ Computes the required sizes to create the bank """ + self.num_words_per_bank = self.num_words / self.num_banks + self.num_bits_per_bank = self.word_size * self.num_words_per_bank + self.num_cols = int(self.words_per_row * self.word_size) - self.num_rows_temp = int(self.num_words / self.words_per_row) + self.num_rows_temp = int(self.num_words_per_bank / self.words_per_row) self.num_rows = self.num_rows_temp + self.num_spare_rows self.row_addr_size = ceil(log(self.num_rows, 2)) self.col_addr_size = int(log(self.words_per_row, 2)) - self.addr_size = self.col_addr_size + self.row_addr_size + self.bank_addr_size = self.col_addr_size + self.row_addr_size - debug.check(self.num_rows_temp * self.num_cols==self.word_size * self.num_words, + debug.check(self.num_rows_temp * self.num_cols * self.num_banks ==self.word_size * self.num_words, "Invalid bank sizes.") - debug.check(self.addr_size==self.col_addr_size + self.row_addr_size, + debug.check(self.bank_addr_size==self.col_addr_size + self.row_addr_size, "Invalid address break down.") # The order of the control signals on the control bus: @@ -351,11 +343,7 @@ class bank(design): # These will be outputs of the gaters if this is multibank, if not, normal signals. self.control_signals = [] for port in self.all_ports: - if self.num_banks > 1: - self.control_signals.append(["gated_" + str for str in self.input_control_signals[port]]) - else: - self.control_signals.append(self.input_control_signals[port]) - + self.control_signals.append(self.input_control_signals[port]) # The central bus is the column address (one hot) and row address (binary) if self.col_addr_size>0: @@ -366,8 +354,8 @@ class bank(design): # Gap between decoder and array self.decoder_gap = max(2 * drc("pwell_to_nwell") + drc("nwell_enclose_active"), - 2 * self.m2_pitch, - drc("nwell_to_nwell")) + 2 * self.m2_pitch, + drc("nwell_to_nwell")) def add_modules(self): """ Add all the modules using the class loader """ @@ -405,9 +393,6 @@ class bank(design): port=port, bit_offsets=self.bit_offsets)) - if(self.num_banks > 1): - self.bank_select = factory.create(module_type="bank_select") - def create_bitcell_array(self): """ Creating Bitcell Array """ self.bitcell_array_inst=self.add_inst(name="bitcell_array", @@ -560,7 +545,7 @@ class bank(design): def create_bank_select(self): """ Create the bank select logic. """ - if not self.num_banks > 1: + if not self.num_banks < 2: return self.bank_select_inst = [None] * len(self.all_ports) @@ -578,7 +563,7 @@ class bank(design): def place_bank_select(self, offsets): """ Place the bank select logic. """ - if not self.num_banks > 1: + if not self.num_banks < 2: return debug.check(len(offsets)>=len(self.all_ports), @@ -603,7 +588,7 @@ class bank(design): self.copy_layout_pin(inst, "gnd") if 'vpb' in self.bitcell_array_inst.mod.pins and 'vnb' in self.bitcell_array_inst.mod.pins: - for pin_name, supply_name in zip(['vnb','vpb'],['gnd','vdd']): + for pin_name, supply_name in zip(['vnb', 'vpb'], ['gnd', 'vdd']): self.copy_layout_pin(self.bitcell_array_inst, pin_name, new_name=supply_name) # If we use the pinvbuf as the decoder, we need to add power pins. @@ -684,7 +669,7 @@ class bank(design): names=self.control_signals[0], length=control_bus_length, vertical=True, - make_pins=(self.num_banks==1), + make_pins=(True), pitch=self.m3_pitch) self.copy_layout_pin(self.port_address_inst[0], "wl_en", self.prefix + "wl_en0") @@ -701,7 +686,7 @@ class bank(design): names=list(reversed(self.control_signals[1])), length=control_bus_length, vertical=True, - make_pins=(self.num_banks==1), + make_pins=(True), pitch=self.m3_pitch) self.copy_layout_pin(self.port_address_inst[1], "wl_en", self.prefix + "wl_en1") @@ -763,7 +748,7 @@ class bank(design): din_name = "din{0}_{1}".format(port, row) self.copy_layout_pin(self.port_data_inst[port], data_name, din_name) - if self.write_size: + if self.write_size != self.word_size: for row in range(self.num_wmasks): wmask_name = "bank_wmask_{}".format(row) bank_wmask_name = "bank_wmask{0}_{1}".format(port, row) diff --git a/compiler/modules/bank_select.py b/compiler/modules/bank_select.py deleted file mode 100644 index 4d299c6e..00000000 --- a/compiler/modules/bank_select.py +++ /dev/null @@ -1,313 +0,0 @@ -# See LICENSE for licensing information. -# -# Copyright (c) 2016-2021 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 tech import drc -from base import design -from base import vector -from pgates import pinv -from pgates import pnand2 -from pgates import pnor2 -from sram_factory import factory -from globals import OPTS - -class bank_select(design): - """Create a bank select signal that is combined with an array of - NOR+INV gates to gate the control signals in case of multiple - banks are created in upper level SRAM module - """ - - def __init__(self, name="bank_select", port="rw"): - super().__init__(name) - - self.port = port - - self.create_netlist() - if not OPTS.netlist_only: - self.create_layout() - - def create_netlist(self): - self.add_pins() - self.add_modules() - self.create_instances() - - def create_layout(self): - self.calculate_module_offsets() - self.place_instances() - self.route_instances() - - self.height = max([x.uy() for x in self.inv_inst]) + self.m1_width - self.width = max([x.rx() for x in self.inv_inst]) - - self.add_boundary() - self.DRC_LVS() - - def add_pins(self): - - # Number of control lines in the bus - if self.port == "rw": - self.num_control_lines = 4 - else: - self.num_control_lines = 3 - # The order of the control signals on the control bus: - # FIXME: Update for multiport (these names are not right) - self.input_control_signals = ["clk_buf", "clk_buf_bar"] - if (self.port == "rw") or (self.port == "w"): - self.input_control_signals.append("w_en") - if (self.port == "rw") or (self.port == "r"): - self.input_control_signals.append("s_en") - # These will be outputs of the gaters if this is multibank - self.control_signals = ["gated_" + str for str in self.input_control_signals] - - self.add_pin_list(self.input_control_signals, "INPUT") - self.add_pin("bank_sel") - self.add_pin_list(self.control_signals, "OUTPUT") - self.add_pin("vdd", "POWER") - self.add_pin("gnd", "GROUND") - - def add_modules(self): - """ Create modules for later instantiation """ - self.dff = factory.create(module_type="dff") - height = self.dff.height + drc("poly_to_active") - - # 1x Inverter - self.inv_sel = factory.create(module_type="pinv", height=height) - - # 4x Inverter - self.inv4x = factory.create(module_type="pinv", height=height, size=4) - - self.nor2 = factory.create(module_type="pnor2", height=height) - - self.inv4x_nor = factory.create(module_type="pinv", height=height, size=4) - - self.nand2 = factory.create(module_type="pnand2", height=height) - - def calculate_module_offsets(self): - - self.xoffset_nand = self.inv4x.width + 3 * self.m2_pitch + drc("pwell_to_nwell") - self.xoffset_nor = self.inv4x.width + 3 * self.m2_pitch + drc("pwell_to_nwell") - self.xoffset_bank_sel_inv = 0 - self.xoffset_inputs = 0 - self.yoffset_maxpoint = self.num_control_lines * self.inv4x.height - - def create_instances(self): - - self.bank_sel_inv=self.add_inst(name="bank_sel_inv", - mod=self.inv_sel) - self.connect_inst(["bank_sel", "bank_sel_bar", "vdd", "gnd"]) - - self.logic_inst = [] - self.inv_inst = [] - for i in range(self.num_control_lines): - input_name = self.input_control_signals[i] - gated_name = self.control_signals[i] - name_nand = "nand_{}".format(input_name) - name_nor = "nor_{}".format(input_name) - name_inv = "inv_{}".format(input_name) - - # These require OR (nor2+inv) gates since they are active low. - # (writes occur on clk low) - if input_name in ("clk_buf"): - - self.logic_inst.append(self.add_inst(name=name_nor, - mod=self.nor2)) - self.connect_inst([input_name, - "bank_sel_bar", - gated_name + "_temp_bar", - "vdd", - "gnd"]) - - # They all get inverters on the output - self.inv_inst.append(self.add_inst(name=name_inv, - mod=self.inv4x_nor)) - self.connect_inst([gated_name + "_temp_bar", - gated_name, - "vdd", - "gnd"]) - - # the rest are AND (nand2+inv) gates - else: - self.logic_inst.append(self.add_inst(name=name_nand, - mod=self.nand2)) - self.connect_inst([input_name, - "bank_sel", - gated_name + "_temp_bar", - "vdd", - "gnd"]) - - # They all get inverters on the output - self.inv_inst.append(self.add_inst(name=name_inv, - mod=self.inv4x)) - self.connect_inst([gated_name + "_temp_bar", - gated_name, - "vdd", - "gnd"]) - - def place_instances(self): - - # bank select inverter - self.bank_select_inv_position = vector(self.xoffset_bank_sel_inv, 0) - - # bank select inverter (must be made unique if more than one OR) - self.bank_sel_inv.place(vector(self.xoffset_bank_sel_inv, 0)) - - for i in range(self.num_control_lines): - - logic_inst = self.logic_inst[i] - inv_inst = self.inv_inst[i] - - input_name = self.input_control_signals[i] - - if i == 0: - y_offset = 0 - else: - y_offset = self.inv4x_nor.height + self.inv4x.height * (i - 1) - - if i % 2: - y_offset += self.inv4x.height - mirror = "MX" - else: - mirror = "" - - # These require OR (nor2+inv) gates since they are active low. - # (writes occur on clk low) - if input_name in ("clk_buf"): - - logic_inst.place(offset=[self.xoffset_nor, y_offset], - mirror=mirror) - - # the rest are AND (nand2+inv) gates - else: - logic_inst.place(offset=[self.xoffset_nand, y_offset], - mirror=mirror) - - # They all get inverters on the output - inv_inst.place(offset=[logic_inst.rx(), y_offset], - mirror=mirror) - - def route_instances(self): - - # bank_sel is vertical wire - bank_sel_inv_pin = self.bank_sel_inv.get_pin("A") - xoffset_bank_sel = bank_sel_inv_pin.lx() - bank_sel_line_pos = vector(xoffset_bank_sel, 0) - bank_sel_line_end = vector(xoffset_bank_sel, self.yoffset_maxpoint) - self.add_path("m2", [bank_sel_line_pos, bank_sel_line_end]) - self.add_via_center(layers=self.m1_stack, - offset=bank_sel_inv_pin.center()) - - # Route the pin to the left edge as well - bank_sel_pin_pos=vector(0, 0) - bank_sel_pin_end=vector(bank_sel_line_pos.x, bank_sel_pin_pos.y) - self.add_layout_pin_segment_center(text="bank_sel", - layer="m3", - start=bank_sel_pin_pos, - end=bank_sel_pin_end) - self.add_via_center(layers=self.m2_stack, - offset=bank_sel_pin_end, - directions=("H", "H")) - - # bank_sel_bar is vertical wire - bank_sel_bar_pin = self.bank_sel_inv.get_pin("Z") - xoffset_bank_sel_bar = bank_sel_bar_pin.rx() - self.add_label_pin(text="bank_sel_bar", - layer="m2", - offset=vector(xoffset_bank_sel_bar, 0), - height=self.inv4x.height) - self.add_via_center(layers=self.m1_stack, - offset=bank_sel_bar_pin.rc()) - - for i in range(self.num_control_lines): - - logic_inst = self.logic_inst[i] - inv_inst = self.inv_inst[i] - - input_name = self.input_control_signals[i] - gated_name = self.control_signals[i] - if input_name in ("clk_buf"): - xoffset_bank_signal = xoffset_bank_sel_bar - else: - xoffset_bank_signal = xoffset_bank_sel - - # Connect the logic output to inverter input - out_pin = logic_inst.get_pin("Z") - out_pos = out_pin.center() - in_pin = inv_inst.get_pin("A") - in_pos = in_pin.center() - mid1_pos = vector(0.5 * (out_pos.x + in_pos.x), out_pos.y) - mid2_pos = vector(0.5 * (out_pos.x + in_pos.x), in_pos.y) - self.add_path("m1", [out_pos, mid1_pos, mid2_pos, in_pos]) - - # Connect the logic B input to bank_sel / bank_sel_bar - logic_pin = logic_inst.get_pin("B") - logic_pos = logic_pin.center() - input_pos = vector(xoffset_bank_signal, logic_pos.y) - self.add_path("m3", [logic_pos, input_pos]) - self.add_via_center(self.m2_stack, - input_pos) - self.add_via_stack_center(from_layer=logic_pin.layer, - to_layer="m3", - offset=logic_pos) - - # Connect the logic A input to the input pin - logic_pin = logic_inst.get_pin("A") - logic_pos = logic_pin.center() - input_pos = vector(0, logic_pos.y) - self.add_via_stack_center(from_layer=logic_pin.layer, - to_layer="m3", - offset=logic_pos) - self.add_layout_pin_segment_center(text=input_name, - layer="m3", - start=input_pos, - end=logic_pos) - - # Add output pins - out_pin = inv_inst.get_pin("Z") - self.add_layout_pin(text=gated_name, - layer=out_pin.layer, - offset=out_pin.ll(), - width=inv_inst.rx() - out_pin.lx(), - height=out_pin.height()) - - # Find the x offsets for where the vias/pins should be placed - a_xoffset = self.logic_inst[0].lx() - b_xoffset = self.inv_inst[0].lx() - for num in range(self.num_control_lines): - # Route both supplies - for n in ["vdd", "gnd"]: - supply_pin = self.inv_inst[num].get_pin(n) - supply_offset = supply_pin.ll().scale(0, 1) - self.add_rect(layer="m1", - offset=supply_offset, - width=self.width) - - # Add pins in two locations - for xoffset in [a_xoffset, b_xoffset]: - pin_pos = vector(xoffset, supply_pin.cy()) - self.add_via_center(layers=self.m1_stack, - offset=pin_pos, - directions=("H", "H")) - self.add_via_center(layers=self.m2_stack, - offset=pin_pos, - directions=("H", "H")) - self.add_layout_pin_rect_center(text=n, - layer="m3", - offset=pin_pos) - - # Add vdd/gnd supply rails - gnd_pin = self.inv_inst[num].get_pin("gnd") - left_gnd_pos = vector(0, gnd_pin.cy()) - self.add_layout_pin_segment_center(text="gnd", - layer="m1", - start=left_gnd_pos, - end=gnd_pin.rc()) - - vdd_pin = self.inv_inst[num].get_pin("vdd") - left_vdd_pos = vector(0, vdd_pin.cy()) - self.add_layout_pin_segment_center(text="vdd", - layer="m1", - start=left_vdd_pos, - end=vdd_pin.rc()) diff --git a/compiler/modules/control_logic.py b/compiler/modules/control_logic.py index 05663b6f..7d265c9c 100644 --- a/compiler/modules/control_logic.py +++ b/compiler/modules/control_logic.py @@ -5,74 +5,22 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -from base import design import debug from sram_factory import factory import math from base import vector from globals import OPTS -from base import logical_effort +from .control_logic_base import control_logic_base -class control_logic(design): +class control_logic(control_logic_base): """ Dynamically generated Control logic for the total SRAM circuit. """ def __init__(self, num_rows, words_per_row, word_size, spare_columns=None, sram=None, port_type="rw", name=""): """ Constructor """ - name = "control_logic_" + port_type - super().__init__(name) - debug.info(1, "Creating {}".format(name)) - self.add_comment("num_rows: {0}".format(num_rows)) - self.add_comment("words_per_row: {0}".format(words_per_row)) - self.add_comment("word_size {0}".format(word_size)) - - self.sram=sram - self.num_rows = num_rows - self.words_per_row = words_per_row - self.word_size = word_size - self.port_type = port_type - - if not spare_columns: - self.num_spare_cols = 0 - else: - self.num_spare_cols = spare_columns - - self.num_cols = word_size * words_per_row + self.num_spare_cols - self.num_words = num_rows * words_per_row - - self.enable_delay_chain_resizing = False - self.inv_parasitic_delay = logical_effort.pinv - - # Determines how much larger the sen delay should be. Accounts for possible error in model. - # FIXME: This should be made a parameter - self.wl_timing_tolerance = 1 - self.wl_stage_efforts = None - self.sen_stage_efforts = None - - if self.port_type == "rw": - self.num_control_signals = 2 - else: - self.num_control_signals = 1 - - self.create_netlist() - if not OPTS.netlist_only: - self.create_layout() - - def create_netlist(self): - self.setup_signal_busses() - self.add_pins() - self.add_modules() - self.create_instances() - - def create_layout(self): - """ Create layout and route between modules """ - self.place_instances() - self.route_all() - # self.add_lvs_correspondence_points() - self.add_boundary() - self.DRC_LVS() + super().__init__(num_rows, words_per_row, word_size, spare_columns, sram, port_type, name) def add_pins(self): """ Add the pins to the control logic module. """ @@ -151,93 +99,6 @@ class control_logic(design): self.delay_chain=factory.create(module_type="delay_chain", fanout_list = OPTS.delay_chain_stages * [ OPTS.delay_chain_fanout_per_stage ]) - def get_dynamic_delay_chain_size(self, previous_stages, previous_fanout): - """Determine the size of the delay chain used for the Sense Amp Enable using path delays""" - from math import ceil - previous_delay_chain_delay = (previous_fanout + 1 + self.inv_parasitic_delay) * previous_stages - debug.info(2, "Previous delay chain produced {} delay units".format(previous_delay_chain_delay)) - - # This can be anything >=2 - delay_fanout = 3 - # The delay chain uses minimum sized inverters. There are (fanout+1)*stages inverters and each - # inverter adds 1 unit of delay (due to minimum size). This also depends on the pinv value - required_delay = self.wl_delay * self.wl_timing_tolerance - (self.sen_delay - previous_delay_chain_delay) - debug.check(required_delay > 0, "Cannot size delay chain to have negative delay") - delay_per_stage = delay_fanout + 1 + self.inv_parasitic_delay - delay_stages = ceil(required_delay / delay_per_stage) - # force an even number of stages. - if delay_stages % 2 == 1: - delay_stages += 1 - # Fanout can be varied as well but is a little more complicated but potentially optimal. - debug.info(1, "Setting delay chain to {} stages with {} fanout to match {} delay".format(delay_stages, delay_fanout, required_delay)) - return (delay_stages, delay_fanout) - - def get_dynamic_delay_fanout_list(self, previous_stages, previous_fanout): - """Determine the size of the delay chain used for the Sense Amp Enable using path delays""" - - previous_delay_per_stage = previous_fanout + 1 + self.inv_parasitic_delay - previous_delay_chain_delay = previous_delay_per_stage * previous_stages - debug.info(2, "Previous delay chain produced {} delay units".format(previous_delay_chain_delay)) - - fanout_rise = fanout_fall = 2 # This can be anything >=2 - # The delay chain uses minimum sized inverters. There are (fanout+1)*stages inverters and each - # inverter adds 1 unit of delay (due to minimum size). This also depends on the pinv value - required_delay_fall = self.wl_delay_fall * self.wl_timing_tolerance - \ - (self.sen_delay_fall - previous_delay_chain_delay / 2) - required_delay_rise = self.wl_delay_rise * self.wl_timing_tolerance - \ - (self.sen_delay_rise - previous_delay_chain_delay / 2) - debug.info(2, - "Required delays from chain: fall={}, rise={}".format(required_delay_fall, - required_delay_rise)) - - # If the fanout is different between rise/fall by this amount. Stage algorithm is made more pessimistic. - WARNING_FANOUT_DIFF = 5 - stages_close = False - # The stages need to be equal (or at least a even number of stages with matching rise/fall delays) - while True: - stages_fall = self.calculate_stages_with_fixed_fanout(required_delay_fall, - fanout_fall) - stages_rise = self.calculate_stages_with_fixed_fanout(required_delay_rise, - fanout_rise) - debug.info(1, - "Fall stages={}, rise stages={}".format(stages_fall, - stages_rise)) - if abs(stages_fall - stages_rise) == 1 and not stages_close: - stages_close = True - safe_fanout_rise = fanout_rise - safe_fanout_fall = fanout_fall - - if stages_fall == stages_rise: - break - elif abs(stages_fall - stages_rise) == 1 and WARNING_FANOUT_DIFF < abs(fanout_fall - fanout_rise): - debug.info(1, "Delay chain fanouts between stages are large. Making chain size larger for safety.") - fanout_rise = safe_fanout_rise - fanout_fall = safe_fanout_fall - break - # There should also be a condition to make sure the fanout does not get too large. - # Otherwise, increase the fanout of delay with the most stages, calculate new stages - elif stages_fall>stages_rise: - fanout_fall+=1 - else: - fanout_rise+=1 - - total_stages = max(stages_fall, stages_rise) * 2 - debug.info(1, "New Delay chain: stages={}, fanout_rise={}, fanout_fall={}".format(total_stages, fanout_rise, fanout_fall)) - - # Creates interleaved fanout list of rise/fall delays. Assumes fall is the first stage. - stage_list = [fanout_fall if i % 2==0 else fanout_rise for i in range(total_stages)] - return stage_list - - def calculate_stages_with_fixed_fanout(self, required_delay, fanout): - from math import ceil - # Delay being negative is not an error. It implies that any amount of stages would have a negative effect on the overall delay - # 3 is the minimum delay per stage (with pinv=0). - if required_delay <= 3 + self.inv_parasitic_delay: - return 1 - delay_per_stage = fanout + 1 + self.inv_parasitic_delay - delay_stages = ceil(required_delay / delay_per_stage) - return delay_stages - def setup_signal_busses(self): """ Setup bus names, determine the size of the busses etc """ @@ -277,17 +138,6 @@ class control_logic(design): self.supply_list = ["vdd", "gnd"] - def route_rails(self): - """ Add the input signal inverted tracks """ - height = self.control_logic_center.y - self.m2_pitch - # DFF spacing plus the power routing - offset = vector(self.ctrl_dff_array.width + self.m4_pitch, 0) - - self.input_bus = self.create_vertical_bus("m2", - offset, - self.internal_bus_list, - height) - def create_instances(self): """ Create all the instances """ self.create_dffs() @@ -303,21 +153,8 @@ class control_logic(design): self.create_delay() self.create_pen_row() - def place_instances(self): - """ Place all the instances """ - # Keep track of all right-most instances to determine row boundary - # and add the vdd/gnd pins - self.row_end_inst = [] - - # Add the control flops on the left of the bus - self.place_dffs() - - # All of the control logic is placed to the right of the DFFs and bus - # as well as the power supply stripe - self.control_x_offset = self.ctrl_dff_array.width + self.internal_bus_width + self.m4_pitch - + def place_logic_rows(self): row = 0 - # Add the logic on the right of the bus self.place_clk_buf_row(row) row += 1 self.place_gated_clk_bar_row(row) @@ -336,24 +173,8 @@ class control_logic(design): self.place_rbl_delay_row(row) row += 1 self.place_wlen_row(row) - row += 1 - control_center_y = self.wl_en_inst.uy() + self.m3_pitch - - # Delay chain always gets placed at row 4 - self.place_delay(4) - height = self.delay_inst.uy() - - # This offset is used for placement of the control logic in the SRAM level. - self.control_logic_center = vector(self.ctrl_dff_inst.rx(), control_center_y) - - # Extra pitch on top and right - self.height = height + 2 * self.m1_pitch - # Max of modules or logic rows - self.width = max([inst.rx() for inst in self.row_end_inst]) - if (self.port_type == "rw") or (self.port_type == "r"): - self.width = max(self.delay_inst.rx(), self.width) - self.width += self.m2_pitch + self.control_center_y = self.wl_en_inst.uy() + self.m3_pitch def route_all(self): """ Routing between modules """ @@ -373,24 +194,12 @@ class control_logic(design): self.route_supplies() def create_delay(self): - """ Create the replica bitline """ + """ Create the delay chain """ self.delay_inst=self.add_inst(name="delay_chain", mod=self.delay_chain) # rbl_bl_delay is asserted (1) when the bitline has been discharged self.connect_inst(["rbl_bl", "rbl_bl_delay", "vdd", "gnd"]) - def place_delay(self, row): - """ Place the replica bitline """ - debug.check(row % 2 == 0, "Must place delay chain at even row for supply alignment.") - - # It is flipped on X axis - y_off = row * self.and2.height + self.delay_chain.height - - # Add the RBL above the rows - # Add to the right of the control rows and routing channel - offset = vector(0, y_off) - self.delay_inst.place(offset, mirror="MX") - def route_delay(self): out_pos = self.delay_inst.get_pin("out").center() @@ -406,109 +215,6 @@ class control_logic(design): # Input from RBL goes to the delay line for futher delay self.copy_layout_pin(self.delay_inst, "in", "rbl_bl") - def create_clk_buf_row(self): - """ Create the multistage and gated clock buffer """ - self.clk_buf_inst = self.add_inst(name="clkbuf", - mod=self.clk_buf_driver) - self.connect_inst(["clk", "clk_buf", "vdd", "gnd"]) - - def place_clk_buf_row(self, row): - x_offset = self.control_x_offset - - x_offset = self.place_util(self.clk_buf_inst, x_offset, row) - - self.row_end_inst.append(self.clk_buf_inst) - - def route_clk_buf(self): - clk_pin = self.clk_buf_inst.get_pin("A") - clk_pos = clk_pin.center() - self.add_layout_pin_rect_center(text="clk", - layer="m2", - offset=clk_pos) - self.add_via_stack_center(from_layer=clk_pin.layer, - to_layer="m2", - offset=clk_pos) - - self.route_output_to_bus_jogged(self.clk_buf_inst, - "clk_buf") - self.connect_output(self.clk_buf_inst, "Z", "clk_buf") - - def create_gated_clk_bar_row(self): - self.clk_bar_inst = self.add_inst(name="inv_clk_bar", - mod=self.inv) - self.connect_inst(["clk_buf", "clk_bar", "vdd", "gnd"]) - - self.gated_clk_bar_inst = self.add_inst(name="and2_gated_clk_bar", - mod=self.and2) - self.connect_inst(["clk_bar", "cs", "gated_clk_bar", "vdd", "gnd"]) - - def place_gated_clk_bar_row(self, row): - x_offset = self.control_x_offset - - x_offset = self.place_util(self.clk_bar_inst, x_offset, row) - x_offset = self.place_util(self.gated_clk_bar_inst, x_offset, row) - - self.row_end_inst.append(self.gated_clk_bar_inst) - - def route_gated_clk_bar(self): - clkbuf_map = zip(["A"], ["clk_buf"]) - self.connect_vertical_bus(clkbuf_map, self.clk_bar_inst, self.input_bus) - - out_pin = self.clk_bar_inst.get_pin("Z") - out_pos = out_pin.center() - in_pin = self.gated_clk_bar_inst.get_pin("A") - in_pos = in_pin.center() - self.add_zjog(out_pin.layer, out_pos, in_pos) - self.add_via_stack_center(from_layer=out_pin.layer, - to_layer=in_pin.layer, - offset=in_pos) - - - # This is the second gate over, so it needs to be on M3 - clkbuf_map = zip(["B"], ["cs"]) - self.connect_vertical_bus(clkbuf_map, - self.gated_clk_bar_inst, - self.input_bus, - self.m2_stack[::-1]) - # The pin is on M1, so we need another via as well - b_pin = self.gated_clk_bar_inst.get_pin("B") - self.add_via_stack_center(from_layer=b_pin.layer, - to_layer="m3", - offset=b_pin.center()) - - # This is the second gate over, so it needs to be on M3 - self.route_output_to_bus_jogged(self.gated_clk_bar_inst, - "gated_clk_bar") - - def create_gated_clk_buf_row(self): - self.gated_clk_buf_inst = self.add_inst(name="and2_gated_clk_buf", - mod=self.and2) - self.connect_inst(["clk_buf", "cs", "gated_clk_buf", "vdd", "gnd"]) - - def place_gated_clk_buf_row(self, row): - x_offset = self.control_x_offset - - x_offset = self.place_util(self.gated_clk_buf_inst, x_offset, row) - - self.row_end_inst.append(self.gated_clk_buf_inst) - - def route_gated_clk_buf(self): - clkbuf_map = zip(["A", "B"], ["clk_buf", "cs"]) - self.connect_vertical_bus(clkbuf_map, - self.gated_clk_buf_inst, - self.input_bus) - - clkbuf_map = zip(["Z"], ["gated_clk_buf"]) - self.connect_vertical_bus(clkbuf_map, - self.gated_clk_buf_inst, - self.input_bus, - self.m2_stack[::-1]) - # The pin is on M1, so we need another via as well - z_pin = self.gated_clk_buf_inst.get_pin("Z") - self.add_via_stack_center(from_layer=z_pin.layer, - to_layer="m2", - offset=z_pin.center()) - def create_wlen_row(self): # input pre_p_en, output: wl_en self.wl_en_inst=self.add_inst(name="buf_wl_en", @@ -651,194 +357,3 @@ class control_logic(design): self.connect_vertical_bus(wen_map, self.w_en_gate_inst, self.input_bus) self.connect_output(self.w_en_gate_inst, "Z", "w_en") - - def create_dffs(self): - self.ctrl_dff_inst=self.add_inst(name="ctrl_dffs", - mod=self.ctrl_dff_array) - inst_pins = self.input_list + self.dff_output_list + ["clk_buf"] + self.supply_list - self.connect_inst(inst_pins) - - def place_dffs(self): - self.ctrl_dff_inst.place(vector(0, 0)) - - def route_dffs(self): - if self.port_type == "rw": - 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"]) - else: - dff_out_map = zip(["dout_bar_0"], ["cs"]) - self.connect_vertical_bus(dff_out_map, self.ctrl_dff_inst, self.input_bus, self.m2_stack[::-1]) - - # Connect the clock rail to the other clock rail - # by routing in the supply rail track to avoid channel conflicts - in_pos = self.ctrl_dff_inst.get_pin("clk").uc() - mid_pos = vector(in_pos.x, self.gated_clk_buf_inst.get_pin("vdd").cy() - self.m1_pitch) - rail_pos = vector(self.input_bus["clk_buf"].cx(), mid_pos.y) - self.add_wire(self.m1_stack, [in_pos, mid_pos, rail_pos]) - self.add_via_center(layers=self.m1_stack, - offset=rail_pos) - - self.copy_layout_pin(self.ctrl_dff_inst, "din_0", "csb") - if (self.port_type == "rw"): - self.copy_layout_pin(self.ctrl_dff_inst, "din_1", "web") - - def get_offset(self, row): - """ Compute the y-offset and mirroring """ - y_off = row * self.and2.height - if row % 2: - y_off += self.and2.height - mirror="MX" - else: - mirror="R0" - - return (y_off, mirror) - - def connect_output(self, inst, pin_name, out_name): - """ Create an output pin on the right side from the pin of a given instance. """ - - out_pin = inst.get_pin(pin_name) - out_pos = out_pin.center() - right_pos = out_pos + vector(self.width - out_pin.cx(), 0) - - self.add_via_stack_center(from_layer=out_pin.layer, - to_layer="m2", - offset=out_pos) - self.add_layout_pin_segment_center(text=out_name, - layer="m2", - start=out_pos, - end=right_pos) - - def route_supplies(self): - """ Add vdd and gnd to the instance cells """ - - pin_layer = self.dff.get_pin("vdd").layer - supply_layer = self.supply_stack[2] - - - # FIXME: We should be able to replace this with route_vertical_pins instead - # but we may have to make the logic gates a separate module so that they - # have row pins of the same width - max_row_x_loc = max([inst.rx() for inst in self.row_end_inst]) - min_row_x_loc = self.control_x_offset - - vdd_pin_locs = [] - gnd_pin_locs = [] - - last_via = None - for inst in self.row_end_inst: - pins = inst.get_pins("vdd") - for pin in pins: - if pin.layer == pin_layer: - row_loc = pin.rc() - pin_loc = vector(max_row_x_loc, pin.rc().y) - vdd_pin_locs.append(pin_loc) - last_via = self.add_via_stack_center(from_layer=pin_layer, - to_layer=supply_layer, - offset=pin_loc, - min_area=True) - self.add_path(pin_layer, [row_loc, pin_loc]) - - pins = inst.get_pins("gnd") - for pin in pins: - if pin.layer == pin_layer: - row_loc = pin.rc() - pin_loc = vector(min_row_x_loc, pin.rc().y) - gnd_pin_locs.append(pin_loc) - last_via = self.add_via_stack_center(from_layer=pin_layer, - to_layer=supply_layer, - offset=pin_loc, - min_area=True) - self.add_path(pin_layer, [row_loc, pin_loc]) - - if last_via: - via_height=last_via.mod.second_layer_height - via_width=last_via.mod.second_layer_width - else: - via_height=None - via_width=0 - - min_y = min([x.y for x in vdd_pin_locs]) - max_y = max([x.y for x in vdd_pin_locs]) - bot_pos = vector(max_row_x_loc, min_y - 0.5 * via_height) - top_pos = vector(max_row_x_loc, max_y + 0.5 * via_height) - self.add_layout_pin_segment_center(text="vdd", - layer=supply_layer, - start=bot_pos, - end=top_pos, - width=via_width) - - min_y = min([x.y for x in gnd_pin_locs]) - max_y = max([x.y for x in gnd_pin_locs]) - bot_pos = vector(min_row_x_loc, min_y - 0.5 * via_height) - top_pos = vector(min_row_x_loc, max_y + 0.5 * via_height) - self.add_layout_pin_segment_center(text="gnd", - layer=supply_layer, - start=bot_pos, - end=top_pos, - width=via_width) - - self.copy_layout_pin(self.delay_inst, "gnd") - self.copy_layout_pin(self.delay_inst, "vdd") - - self.copy_layout_pin(self.ctrl_dff_inst, "gnd") - self.copy_layout_pin(self.ctrl_dff_inst, "vdd") - - def add_lvs_correspondence_points(self): - """ This adds some points for easier debugging if LVS goes wrong. - These should probably be turned off by default though, since extraction - will show these as ports in the extracted netlist. - """ - # pin=self.clk_inv1.get_pin("Z") - # self.add_label_pin(text="clk1_bar", - # layer="m1", - # offset=pin.ll(), - # height=pin.height(), - # width=pin.width()) - - # pin=self.clk_inv2.get_pin("Z") - # self.add_label_pin(text="clk2", - # layer="m1", - # offset=pin.ll(), - # height=pin.height(), - # width=pin.width()) - - pin=self.delay_inst.get_pin("out") - self.add_label_pin(text="out", - layer=pin.layer, - offset=pin.ll(), - height=pin.height(), - width=pin.width()) - - def graph_exclude_dffs(self): - """Exclude dffs from graph as they do not represent critical path""" - - self.graph_inst_exclude.add(self.ctrl_dff_inst) - if self.port_type=="rw" or self.port_type=="w": - self.graph_inst_exclude.add(self.w_en_gate_inst) - - def place_util(self, inst, x_offset, row): - """ Utility to place a row and compute the next offset """ - - (y_offset, mirror) = self.get_offset(row) - offset = vector(x_offset, y_offset) - inst.place(offset, mirror) - return x_offset + inst.width - - def route_output_to_bus_jogged(self, inst, name): - # Connect this at the bottom of the buffer - out_pin = inst.get_pin("Z") - out_pos = out_pin.center() - mid1 = vector(out_pos.x, out_pos.y - 0.3 * inst.mod.height) - mid2 = vector(self.input_bus[name].cx(), mid1.y) - bus_pos = self.input_bus[name].center() - self.add_wire(self.m2_stack[::-1], [out_pos, mid1, mid2, bus_pos]) - self.add_via_stack_center(from_layer=out_pin.layer, - to_layer="m2", - offset=out_pos) - - def get_left_pins(self, name): - """ - Return the left side supply pins to connect to a vertical stripe. - """ - return(self.cntrl_dff_inst.get_pins(name) + self.delay_inst.get_pins(name)) diff --git a/compiler/modules/control_logic_base.py b/compiler/modules/control_logic_base.py new file mode 100644 index 00000000..decf1f50 --- /dev/null +++ b/compiler/modules/control_logic_base.py @@ -0,0 +1,508 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2021 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 base import design +import debug +from sram_factory import factory +import math +from base import vector +from globals import OPTS +from base import logical_effort + + +class control_logic_base(design): + """ + Generic base class for SRAM control logic. + """ + + def __init__(self, num_rows, words_per_row, word_size, spare_columns=None, sram=None, port_type="rw", name=""): + """ Constructor """ + name = "control_logic_" + port_type + super().__init__(name) + debug.info(1, "Creating {}".format(name)) + self.add_comment("num_rows: {0}".format(num_rows)) + self.add_comment("words_per_row: {0}".format(words_per_row)) + self.add_comment("word_size {0}".format(word_size)) + + self.sram=sram + self.num_rows = num_rows + self.words_per_row = words_per_row + self.word_size = word_size + self.port_type = port_type + + if not spare_columns: + self.num_spare_cols = 0 + else: + self.num_spare_cols = spare_columns + + self.num_cols = word_size * words_per_row + self.num_spare_cols + self.num_words = num_rows * words_per_row + + self.enable_delay_chain_resizing = False + self.inv_parasitic_delay = logical_effort.pinv + + # Determines how much larger the sen delay should be. Accounts for possible error in model. + # FIXME: This should be made a parameter + self.wl_timing_tolerance = 1 + self.wl_stage_efforts = None + self.sen_stage_efforts = None + + if self.port_type == "rw": + self.num_control_signals = 2 + else: + self.num_control_signals = 1 + + self.create_netlist() + if not OPTS.netlist_only: + self.create_layout() + + def create_netlist(self): + self.setup_signal_busses() + self.add_pins() + self.add_modules() + self.create_instances() + + def create_layout(self): + """ Create layout and route between modules """ + self.place_instances() + self.route_all() + # self.add_lvs_correspondence_points() + self.add_boundary() + self.DRC_LVS() + + def get_dynamic_delay_chain_size(self, previous_stages, previous_fanout): + """Determine the size of the delay chain used for the Sense Amp Enable using path delays""" + from math import ceil + previous_delay_chain_delay = (previous_fanout + 1 + self.inv_parasitic_delay) * previous_stages + debug.info(2, "Previous delay chain produced {} delay units".format(previous_delay_chain_delay)) + + # This can be anything >=2 + delay_fanout = 3 + # The delay chain uses minimum sized inverters. There are (fanout+1)*stages inverters and each + # inverter adds 1 unit of delay (due to minimum size). This also depends on the pinv value + required_delay = self.wl_delay * self.wl_timing_tolerance - (self.sen_delay - previous_delay_chain_delay) + debug.check(required_delay > 0, "Cannot size delay chain to have negative delay") + delay_per_stage = delay_fanout + 1 + self.inv_parasitic_delay + delay_stages = ceil(required_delay / delay_per_stage) + # force an even number of stages. + if delay_stages % 2 == 1: + delay_stages += 1 + # Fanout can be varied as well but is a little more complicated but potentially optimal. + debug.info(1, "Setting delay chain to {} stages with {} fanout to match {} delay".format(delay_stages, delay_fanout, required_delay)) + return (delay_stages, delay_fanout) + + def get_dynamic_delay_fanout_list(self, previous_stages, previous_fanout): + """Determine the size of the delay chain used for the Sense Amp Enable using path delays""" + + previous_delay_per_stage = previous_fanout + 1 + self.inv_parasitic_delay + previous_delay_chain_delay = previous_delay_per_stage * previous_stages + debug.info(2, "Previous delay chain produced {} delay units".format(previous_delay_chain_delay)) + + fanout_rise = fanout_fall = 2 # This can be anything >=2 + # The delay chain uses minimum sized inverters. There are (fanout+1)*stages inverters and each + # inverter adds 1 unit of delay (due to minimum size). This also depends on the pinv value + required_delay_fall = self.wl_delay_fall * self.wl_timing_tolerance - \ + (self.sen_delay_fall - previous_delay_chain_delay / 2) + required_delay_rise = self.wl_delay_rise * self.wl_timing_tolerance - \ + (self.sen_delay_rise - previous_delay_chain_delay / 2) + debug.info(2, + "Required delays from chain: fall={}, rise={}".format(required_delay_fall, + required_delay_rise)) + + # If the fanout is different between rise/fall by this amount. Stage algorithm is made more pessimistic. + WARNING_FANOUT_DIFF = 5 + stages_close = False + # The stages need to be equal (or at least a even number of stages with matching rise/fall delays) + while True: + stages_fall = self.calculate_stages_with_fixed_fanout(required_delay_fall, + fanout_fall) + stages_rise = self.calculate_stages_with_fixed_fanout(required_delay_rise, + fanout_rise) + debug.info(1, + "Fall stages={}, rise stages={}".format(stages_fall, + stages_rise)) + if abs(stages_fall - stages_rise) == 1 and not stages_close: + stages_close = True + safe_fanout_rise = fanout_rise + safe_fanout_fall = fanout_fall + + if stages_fall == stages_rise: + break + elif abs(stages_fall - stages_rise) == 1 and WARNING_FANOUT_DIFF < abs(fanout_fall - fanout_rise): + debug.info(1, "Delay chain fanouts between stages are large. Making chain size larger for safety.") + fanout_rise = safe_fanout_rise + fanout_fall = safe_fanout_fall + break + # There should also be a condition to make sure the fanout does not get too large. + # Otherwise, increase the fanout of delay with the most stages, calculate new stages + elif stages_fall>stages_rise: + fanout_fall+=1 + else: + fanout_rise+=1 + + total_stages = max(stages_fall, stages_rise) * 2 + debug.info(1, "New Delay chain: stages={}, fanout_rise={}, fanout_fall={}".format(total_stages, fanout_rise, fanout_fall)) + + # Creates interleaved fanout list of rise/fall delays. Assumes fall is the first stage. + stage_list = [fanout_fall if i % 2==0 else fanout_rise for i in range(total_stages)] + return stage_list + + def calculate_stages_with_fixed_fanout(self, required_delay, fanout): + from math import ceil + # Delay being negative is not an error. It implies that any amount of stages would have a negative effect on the overall delay + # 3 is the minimum delay per stage (with pinv=0). + if required_delay <= 3 + self.inv_parasitic_delay: + return 1 + delay_per_stage = fanout + 1 + self.inv_parasitic_delay + delay_stages = ceil(required_delay / delay_per_stage) + return delay_stages + + def route_rails(self): + """ Add the input signal inverted tracks """ + height = self.control_logic_center.y - self.m2_pitch + # DFF spacing plus the power routing + offset = vector(self.ctrl_dff_array.width + self.m4_pitch, 0) + + self.input_bus = self.create_vertical_bus("m2", + offset, + self.internal_bus_list, + height) + + def place_instances(self): + """ Place all the instances """ + # Keep track of all right-most instances to determine row boundary + # and add the vdd/gnd pins + self.row_end_inst = [] + + # Add the control flops on the left of the bus + self.place_dffs() + + # All of the control logic is placed to the right of the DFFs and bus + # as well as the power supply stripe + self.control_x_offset = self.ctrl_dff_array.width + self.internal_bus_width + self.m4_pitch + + self.place_logic_rows() + + # Delay chain always gets placed at row 4 + self.place_delay(4) + height = self.delay_inst.uy() + + # This offset is used for placement of the control logic in the SRAM level. + self.control_logic_center = vector(self.ctrl_dff_inst.rx(), self.control_center_y) + + # Extra pitch on top and right + self.height = height + 2 * self.m1_pitch + # Max of modules or logic rows + self.width = max([inst.rx() for inst in self.row_end_inst]) + if (self.port_type == "rw") or (self.port_type == "r"): + self.width = max(self.delay_inst.rx(), self.width) + self.width += self.m2_pitch + + def place_delay(self, row): + """ Place the delay chain """ + debug.check(row % 2 == 0, "Must place delay chain at even row for supply alignment.") + + # It is flipped on X axis + y_off = row * self.and2.height + self.delay_chain.height + + # Add to the right of the control rows and routing channel + offset = vector(0, y_off) + self.delay_inst.place(offset, mirror="MX") + + def create_clk_buf_row(self): + """ Create the multistage and gated clock buffer """ + self.clk_buf_inst = self.add_inst(name="clkbuf", + mod=self.clk_buf_driver) + self.connect_inst(["clk", "clk_buf", "vdd", "gnd"]) + + def place_clk_buf_row(self, row): + x_offset = self.control_x_offset + + x_offset = self.place_util(self.clk_buf_inst, x_offset, row) + + self.row_end_inst.append(self.clk_buf_inst) + + def route_clk_buf(self): + clk_pin = self.clk_buf_inst.get_pin("A") + clk_pos = clk_pin.center() + self.add_layout_pin_rect_center(text="clk", + layer="m2", + offset=clk_pos) + self.add_via_stack_center(from_layer=clk_pin.layer, + to_layer="m2", + offset=clk_pos) + + self.route_output_to_bus_jogged(self.clk_buf_inst, + "clk_buf") + self.connect_output(self.clk_buf_inst, "Z", "clk_buf") + + def create_gated_clk_bar_row(self): + self.clk_bar_inst = self.add_inst(name="inv_clk_bar", + mod=self.inv) + self.connect_inst(["clk_buf", "clk_bar", "vdd", "gnd"]) + + self.gated_clk_bar_inst = self.add_inst(name="and2_gated_clk_bar", + mod=self.and2) + self.connect_inst(["clk_bar", "cs", "gated_clk_bar", "vdd", "gnd"]) + + def place_gated_clk_bar_row(self, row): + x_offset = self.control_x_offset + + x_offset = self.place_util(self.clk_bar_inst, x_offset, row) + x_offset = self.place_util(self.gated_clk_bar_inst, x_offset, row) + + self.row_end_inst.append(self.gated_clk_bar_inst) + + def route_gated_clk_bar(self): + clkbuf_map = zip(["A"], ["clk_buf"]) + self.connect_vertical_bus(clkbuf_map, self.clk_bar_inst, self.input_bus) + + out_pin = self.clk_bar_inst.get_pin("Z") + out_pos = out_pin.center() + in_pin = self.gated_clk_bar_inst.get_pin("A") + in_pos = in_pin.center() + self.add_zjog(out_pin.layer, out_pos, in_pos) + self.add_via_stack_center(from_layer=out_pin.layer, + to_layer=in_pin.layer, + offset=in_pos) + + + # This is the second gate over, so it needs to be on M3 + clkbuf_map = zip(["B"], ["cs"]) + self.connect_vertical_bus(clkbuf_map, + self.gated_clk_bar_inst, + self.input_bus, + self.m2_stack[::-1]) + # The pin is on M1, so we need another via as well + b_pin = self.gated_clk_bar_inst.get_pin("B") + self.add_via_stack_center(from_layer=b_pin.layer, + to_layer="m3", + offset=b_pin.center()) + + # This is the second gate over, so it needs to be on M3 + self.route_output_to_bus_jogged(self.gated_clk_bar_inst, + "gated_clk_bar") + + def create_gated_clk_buf_row(self): + self.gated_clk_buf_inst = self.add_inst(name="and2_gated_clk_buf", + mod=self.and2) + self.connect_inst(["clk_buf", "cs", "gated_clk_buf", "vdd", "gnd"]) + + def place_gated_clk_buf_row(self, row): + x_offset = self.control_x_offset + + x_offset = self.place_util(self.gated_clk_buf_inst, x_offset, row) + + self.row_end_inst.append(self.gated_clk_buf_inst) + + def route_gated_clk_buf(self): + clkbuf_map = zip(["A", "B"], ["clk_buf", "cs"]) + self.connect_vertical_bus(clkbuf_map, + self.gated_clk_buf_inst, + self.input_bus) + + clkbuf_map = zip(["Z"], ["gated_clk_buf"]) + self.connect_vertical_bus(clkbuf_map, + self.gated_clk_buf_inst, + self.input_bus, + self.m2_stack[::-1]) + # The pin is on M1, so we need another via as well + z_pin = self.gated_clk_buf_inst.get_pin("Z") + self.add_via_stack_center(from_layer=z_pin.layer, + to_layer="m2", + offset=z_pin.center()) + + def create_dffs(self): + self.ctrl_dff_inst=self.add_inst(name="ctrl_dffs", + mod=self.ctrl_dff_array) + inst_pins = self.input_list + self.dff_output_list + ["clk_buf"] + self.supply_list + self.connect_inst(inst_pins) + + def place_dffs(self): + self.ctrl_dff_inst.place(vector(0, 0)) + + def route_dffs(self): + if self.port_type == "rw": + 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"]) + else: + dff_out_map = zip(["dout_bar_0"], ["cs"]) + self.connect_vertical_bus(dff_out_map, self.ctrl_dff_inst, self.input_bus, self.m2_stack[::-1]) + + # Connect the clock rail to the other clock rail + # by routing in the supply rail track to avoid channel conflicts + in_pos = self.ctrl_dff_inst.get_pin("clk").uc() + mid_pos = vector(in_pos.x, self.gated_clk_buf_inst.get_pin("vdd").cy() - self.m1_pitch) + rail_pos = vector(self.input_bus["clk_buf"].cx(), mid_pos.y) + self.add_wire(self.m1_stack, [in_pos, mid_pos, rail_pos]) + self.add_via_center(layers=self.m1_stack, + offset=rail_pos) + + self.copy_layout_pin(self.ctrl_dff_inst, "din_0", "csb") + if (self.port_type == "rw"): + self.copy_layout_pin(self.ctrl_dff_inst, "din_1", "web") + + def get_offset(self, row): + """ Compute the y-offset and mirroring """ + y_off = row * self.and2.height + if row % 2: + y_off += self.and2.height + mirror="MX" + else: + mirror="R0" + + return (y_off, mirror) + + def connect_output(self, inst, pin_name, out_name): + """ Create an output pin on the right side from the pin of a given instance. """ + + out_pin = inst.get_pin(pin_name) + out_pos = out_pin.center() + right_pos = out_pos + vector(self.width - out_pin.cx(), 0) + + self.add_via_stack_center(from_layer=out_pin.layer, + to_layer="m2", + offset=out_pos) + self.add_layout_pin_segment_center(text=out_name, + layer="m2", + start=out_pos, + end=right_pos) + + def route_supplies(self): + """ Add vdd and gnd to the instance cells """ + + pin_layer = self.dff.get_pin("vdd").layer + supply_layer = self.supply_stack[2] + + + # FIXME: We should be able to replace this with route_vertical_pins instead + # but we may have to make the logic gates a separate module so that they + # have row pins of the same width + max_row_x_loc = max([inst.rx() for inst in self.row_end_inst]) + min_row_x_loc = self.control_x_offset + + vdd_pin_locs = [] + gnd_pin_locs = [] + + last_via = None + for inst in self.row_end_inst: + pins = inst.get_pins("vdd") + for pin in pins: + if pin.layer == pin_layer: + row_loc = pin.rc() + pin_loc = vector(max_row_x_loc, pin.rc().y) + vdd_pin_locs.append(pin_loc) + last_via = self.add_via_stack_center(from_layer=pin_layer, + to_layer=supply_layer, + offset=pin_loc, + min_area=True) + self.add_path(pin_layer, [row_loc, pin_loc]) + + pins = inst.get_pins("gnd") + for pin in pins: + if pin.layer == pin_layer: + row_loc = pin.rc() + pin_loc = vector(min_row_x_loc, pin.rc().y) + gnd_pin_locs.append(pin_loc) + last_via = self.add_via_stack_center(from_layer=pin_layer, + to_layer=supply_layer, + offset=pin_loc, + min_area=True) + self.add_path(pin_layer, [row_loc, pin_loc]) + + if last_via: + via_height=last_via.mod.second_layer_height + via_width=last_via.mod.second_layer_width + else: + via_height=None + via_width=0 + + min_y = min([x.y for x in vdd_pin_locs]) + max_y = max([x.y for x in vdd_pin_locs]) + bot_pos = vector(max_row_x_loc, min_y - 0.5 * via_height) + top_pos = vector(max_row_x_loc, max_y + 0.5 * via_height) + self.add_layout_pin_segment_center(text="vdd", + layer=supply_layer, + start=bot_pos, + end=top_pos, + width=via_width) + + min_y = min([x.y for x in gnd_pin_locs]) + max_y = max([x.y for x in gnd_pin_locs]) + bot_pos = vector(min_row_x_loc, min_y - 0.5 * via_height) + top_pos = vector(min_row_x_loc, max_y + 0.5 * via_height) + self.add_layout_pin_segment_center(text="gnd", + layer=supply_layer, + start=bot_pos, + end=top_pos, + width=via_width) + + self.copy_layout_pin(self.delay_inst, "gnd") + self.copy_layout_pin(self.delay_inst, "vdd") + + self.copy_layout_pin(self.ctrl_dff_inst, "gnd") + self.copy_layout_pin(self.ctrl_dff_inst, "vdd") + + def add_lvs_correspondence_points(self): + """ This adds some points for easier debugging if LVS goes wrong. + These should probably be turned off by default though, since extraction + will show these as ports in the extracted netlist. + """ + # pin=self.clk_inv1.get_pin("Z") + # self.add_label_pin(text="clk1_bar", + # layer="m1", + # offset=pin.ll(), + # height=pin.height(), + # width=pin.width()) + + # pin=self.clk_inv2.get_pin("Z") + # self.add_label_pin(text="clk2", + # layer="m1", + # offset=pin.ll(), + # height=pin.height(), + # width=pin.width()) + + pin=self.delay_inst.get_pin("out") + self.add_label_pin(text="out", + layer=pin.layer, + offset=pin.ll(), + height=pin.height(), + width=pin.width()) + + def graph_exclude_dffs(self): + """Exclude dffs from graph as they do not represent critical path""" + + self.graph_inst_exclude.add(self.ctrl_dff_inst) + if self.port_type=="rw" or self.port_type=="w": + self.graph_inst_exclude.add(self.w_en_gate_inst) + + def place_util(self, inst, x_offset, row): + """ Utility to place a row and compute the next offset """ + + (y_offset, mirror) = self.get_offset(row) + offset = vector(x_offset, y_offset) + inst.place(offset, mirror) + return x_offset + inst.width + + def route_output_to_bus_jogged(self, inst, name): + # Connect this at the bottom of the buffer + out_pin = inst.get_pin("Z") + out_pos = out_pin.center() + mid1 = vector(out_pos.x, out_pos.y - 0.3 * inst.mod.height) + mid2 = vector(self.input_bus[name].cx(), mid1.y) + bus_pos = self.input_bus[name].center() + self.add_wire(self.m2_stack[::-1], [out_pos, mid1, mid2, bus_pos]) + self.add_via_stack_center(from_layer=out_pin.layer, + to_layer="m2", + offset=out_pos) + + def get_left_pins(self, name): + """ + Return the left side supply pins to connect to a vertical stripe. + """ + return(self.cntrl_dff_inst.get_pins(name) + self.delay_inst.get_pins(name)) diff --git a/compiler/modules/port_data.py b/compiler/modules/port_data.py index a8f5300f..f34aa893 100644 --- a/compiler/modules/port_data.py +++ b/compiler/modules/port_data.py @@ -25,7 +25,7 @@ class port_data(design): sram_config.set_local_config(self) self.port = port - if self.write_size is not None: + if self.write_size != self.word_size: self.num_wmasks = int(math.ceil(self.word_size / self.write_size)) else: self.num_wmasks = 0 @@ -93,7 +93,7 @@ class port_data(design): if self.write_driver_array: self.create_write_driver_array() - if self.write_size is not None: + if self.write_size != self.word_size: self.create_write_mask_and_array() else: self.write_mask_and_array_inst = None @@ -245,7 +245,7 @@ class port_data(design): offsets=self.bit_offsets, write_size=self.write_size, num_spare_cols=self.num_spare_cols) - if self.write_size is not None: + if self.write_size != self.word_size: # RBLs don't get a write mask self.write_mask_and_array = factory.create(module_type="write_mask_and_array", columns=self.num_cols, @@ -391,13 +391,13 @@ class port_data(design): temp.append("sparebl_{0}".format(bit)) temp.append("sparebr_{0}".format(bit)) - if self.write_size is not None: + if self.write_size != self.word_size: for i in range(self.num_wmasks): temp.append("wdriver_sel_{}".format(i)) for i in range(self.num_spare_cols): temp.append("bank_spare_wen{}".format(i)) - elif self.num_spare_cols and not self.write_size: + elif self.num_spare_cols and self.write_size == self.word_size: temp.append("w_en") for i in range(self.num_spare_cols): temp.append("bank_spare_wen{}".format(i)) diff --git a/compiler/modules/sram.py b/compiler/modules/sram.py index 167a54e5..6c26e5ec 100644 --- a/compiler/modules/sram.py +++ b/compiler/modules/sram.py @@ -36,14 +36,10 @@ class sram(): self.name = name - if self.num_banks == 1: - from .sram_1bank import sram_1bank as sram - elif self.num_banks == 2: - from .sram_2bank import sram_2bank as sram - else: - debug.error("Invalid number of banks.", -1) + from .sram_1bank import sram_1bank as sram self.s = sram(name, sram_config) + self.s.create_netlist() if not OPTS.netlist_only: self.s.create_layout() @@ -62,6 +58,10 @@ class sram(): def verilog_write(self, name): self.s.verilog_write(name) + if self.num_banks != 1: + from .sram_multibank import sram_multibank + mb = sram_multibank(self.s) + mb.verilog_write(name[:-2] + '_top.v') def extended_config_write(self, name): """Dump config file with all options. @@ -166,7 +166,7 @@ class sram(): # Write a verilog model start_time = datetime.datetime.now() - vname = OPTS.output_path + self.s.name + ".v" + vname = OPTS.output_path + self.s.name + '.v' debug.print_raw("Verilog: Writing to {0}".format(vname)) self.verilog_write(vname) print_time("Verilog", datetime.datetime.now(), start_time) diff --git a/compiler/modules/sram_1bank.py b/compiler/modules/sram_1bank.py index f5122097..63303c40 100644 --- a/compiler/modules/sram_1bank.py +++ b/compiler/modules/sram_1bank.py @@ -6,18 +6,756 @@ # All rights reserved. # from base import vector -from .sram_base import sram_base from base import channel_route from router import router_tech -from globals import OPTS +from globals import OPTS, print_time +import datetime +import debug +from math import ceil +from importlib import reload +from base import design +from base import verilog +from base import lef +from sram_factory import factory +from tech import spice -class sram_1bank(sram_base): +class sram_1bank(design, verilog, lef): """ Procedures specific to a one bank SRAM. """ def __init__(self, name, sram_config): - sram_base.__init__(self, name, sram_config) + design.__init__(self, name) + lef.__init__(self, ["m1", "m2", "m3", "m4"]) + verilog.__init__(self) + + self.sram_config = sram_config + sram_config.set_local_config(self) + + self.bank_insts = [] + + if self.write_size != self.word_size: + self.num_wmasks = int(ceil(self.word_size / self.write_size)) + else: + self.num_wmasks = 0 + + if not self.num_spare_cols: + self.num_spare_cols = 0 + + try: + from tech import power_grid + self.supply_stack = power_grid + except ImportError: + # if no power_grid is specified by tech we use sensible defaults + # Route a M3/M4 grid + self.supply_stack = self.m3_stack + + def add_pins(self): + """ Add pins for entire SRAM. """ + + for port in self.write_ports: + for bit in range(self.word_size + self.num_spare_cols): + self.add_pin("din{0}[{1}]".format(port, bit), "INPUT") + for port in self.all_ports: + for bit in range(self.bank_addr_size): + self.add_pin("addr{0}[{1}]".format(port, bit), "INPUT") + + # These are used to create the physical pins + self.control_logic_inputs = [] + self.control_logic_outputs = [] + for port in self.all_ports: + if port in self.readwrite_ports: + self.control_logic_inputs.append(self.control_logic_rw.get_inputs()) + self.control_logic_outputs.append(self.control_logic_rw.get_outputs()) + elif port in self.write_ports: + self.control_logic_inputs.append(self.control_logic_w.get_inputs()) + self.control_logic_outputs.append(self.control_logic_w.get_outputs()) + else: + self.control_logic_inputs.append(self.control_logic_r.get_inputs()) + self.control_logic_outputs.append(self.control_logic_r.get_outputs()) + + for port in self.all_ports: + self.add_pin("csb{}".format(port), "INPUT") + for port in self.readwrite_ports: + self.add_pin("web{}".format(port), "INPUT") + for port in self.all_ports: + self.add_pin("clk{}".format(port), "INPUT") + # add the optional write mask pins + for port in self.write_ports: + for bit in range(self.num_wmasks): + self.add_pin("wmask{0}[{1}]".format(port, bit), "INPUT") + if self.num_spare_cols == 1: + self.add_pin("spare_wen{0}".format(port), "INPUT") + else: + for bit in range(self.num_spare_cols): + self.add_pin("spare_wen{0}[{1}]".format(port, bit), "INPUT") + for port in self.read_ports: + for bit in range(self.word_size + self.num_spare_cols): + self.add_pin("dout{0}[{1}]".format(port, bit), "OUTPUT") + + # Standard supply and ground names + try: + self.vdd_name = spice["power"] + except KeyError: + self.vdd_name = "vdd" + try: + self.gnd_name = spice["ground"] + except KeyError: + self.gnd_name = "gnd" + + self.add_pin(self.vdd_name, "POWER") + self.add_pin(self.gnd_name, "GROUND") + self.ext_supplies = [self.vdd_name, self.gnd_name] + self.ext_supply = {"vdd" : self.vdd_name, "gnd" : self.gnd_name} + + def add_global_pex_labels(self): + """ + Add pex labels at the sram level for spice analysis + """ + + + + # add pex labels for bitcells + for bank_num in range(len(self.bank_insts)): + bank = self.bank_insts[bank_num] + pex_data = bank.reverse_transformation_bitcell(self.bitcell.name) + + bank_offset = pex_data[0] # offset bank relative to sram + Q_offset = pex_data[1] # offset of storage relative to bank + Q_bar_offset = pex_data[2] # offset of storage relative to bank + bl_offsets = pex_data[3] + br_offsets = pex_data[4] + bl_meta = pex_data[5] + br_meta = pex_data[6] + + bl = [] + br = [] + + storage_layer_name = "m1" + bitline_layer_name = self.bitcell.get_pin("bl").layer + + for cell in range(len(bank_offset)): + Q = [bank_offset[cell][0] + Q_offset[cell][0], + bank_offset[cell][1] + Q_offset[cell][1]] + Q_bar = [bank_offset[cell][0] + Q_bar_offset[cell][0], + bank_offset[cell][1] + Q_bar_offset[cell][1]] + OPTS.words_per_row = self.words_per_row + row = int(cell % (OPTS.num_words / self.words_per_row)) + col = int(cell / (OPTS.num_words)) + self.add_layout_pin_rect_center("bitcell_Q_b{}_r{}_c{}".format(bank_num, + row, + col), + storage_layer_name, + Q) + self.add_layout_pin_rect_center("bitcell_Q_bar_b{}_r{}_c{}".format(bank_num, + row, + col), + storage_layer_name, + Q_bar) + + for cell in range(len(bl_offsets)): + col = bl_meta[cell][0][2] + for bitline in range(len(bl_offsets[cell])): + bitline_location = [float(bank_offset[cell][0]) + bl_offsets[cell][bitline][0], + float(bank_offset[cell][1]) + bl_offsets[cell][bitline][1]] + bl.append([bitline_location, bl_meta[cell][bitline][3], col]) + + for cell in range(len(br_offsets)): + col = br_meta[cell][0][2] + for bitline in range(len(br_offsets[cell])): + bitline_location = [float(bank_offset[cell][0]) + br_offsets[cell][bitline][0], + float(bank_offset[cell][1]) + br_offsets[cell][bitline][1]] + br.append([bitline_location, br_meta[cell][bitline][3], col]) + + for i in range(len(bl)): + self.add_layout_pin_rect_center("bl{0}_{1}".format(bl[i][1], bl[i][2]), + bitline_layer_name, bl[i][0]) + + for i in range(len(br)): + self.add_layout_pin_rect_center("br{0}_{1}".format(br[i][1], br[i][2]), + bitline_layer_name, br[i][0]) + + # add pex labels for control logic + for i in range(len(self.control_logic_insts)): + instance = self.control_logic_insts[i] + control_logic_offset = instance.offset + for output in instance.mod.output_list: + pin = instance.mod.get_pin(output) + pin.transform([0, 0], instance.mirror, instance.rotate) + offset = [control_logic_offset[0] + pin.center()[0], + control_logic_offset[1] + pin.center()[1]] + self.add_layout_pin_rect_center("{0}{1}".format(pin.name, i), + storage_layer_name, + offset) + + def create_netlist(self): + """ Netlist creation """ + + start_time = datetime.datetime.now() + + # Must create the control logic before pins to get the pins + self.add_modules() + self.add_pins() + self.create_modules() + + # This is for the lib file if we don't create layout + self.width=0 + self.height=0 + + if not OPTS.is_unit_test: + print_time("Submodules", datetime.datetime.now(), start_time) + + def create_layout(self): + """ Layout creation """ + start_time = datetime.datetime.now() + self.place_instances() + if not OPTS.is_unit_test: + print_time("Placement", datetime.datetime.now(), start_time) + + start_time = datetime.datetime.now() + self.route_layout() + + if not OPTS.is_unit_test: + print_time("Routing", datetime.datetime.now(), start_time) + + self.add_lvs_correspondence_points() + + self.offset_all_coordinates() + + highest_coord = self.find_highest_coords() + self.width = highest_coord[0] + self.height = highest_coord[1] + if OPTS.use_pex and OPTS.pex_exe[0] != "calibre": + debug.info(2, "adding global pex labels") + self.add_global_pex_labels() + self.add_boundary(ll=vector(0, 0), + ur=vector(self.width, self.height)) + + start_time = datetime.datetime.now() + if not OPTS.is_unit_test: + # We only enable final verification if we have routed the design + # Only run this if not a unit test, because unit test will also verify it. + self.DRC_LVS(final_verification=OPTS.route_supplies, force_check=OPTS.check_lvsdrc) + print_time("Verification", datetime.datetime.now(), start_time) + + def create_modules(self): + debug.error("Must override pure virtual function.", -1) + + def route_supplies(self, bbox=None): + """ Route the supply grid and connect the pins to them. """ + + # Copy the pins to the top level + # This will either be used to route or left unconnected. + for pin_name in ["vdd", "gnd"]: + for inst in self.insts: + self.copy_power_pins(inst, pin_name, self.ext_supply[pin_name]) + + if not OPTS.route_supplies: + # Do not route the power supply (leave as must-connect pins) + return + elif OPTS.route_supplies == "grid": + from router import supply_grid_router as router + else: + from router import supply_tree_router as router + rtr=router(layers=self.supply_stack, + design=self, + bbox=bbox, + pin_type=OPTS.supply_pin_type) + + rtr.route() + + if OPTS.supply_pin_type in ["left", "right", "top", "bottom", "ring"]: + # Find the lowest leftest pin for vdd and gnd + for pin_name in ["vdd", "gnd"]: + # Copy the pin shape(s) to rectangles + for pin in self.get_pins(pin_name): + self.add_rect(pin.layer, + pin.ll(), + pin.width(), + pin.height()) + + # Remove the pin shape(s) + self.remove_layout_pin(pin_name) + + # Get new pins + pins = rtr.get_new_pins(pin_name) + for pin in pins: + self.add_layout_pin(self.ext_supply[pin_name], + pin.layer, + pin.ll(), + pin.width(), + pin.height()) + + elif OPTS.route_supplies and OPTS.supply_pin_type == "single": + # Update these as we may have routed outside the region (perimeter pins) + lowest_coord = self.find_lowest_coords() + + # Find the lowest leftest pin for vdd and gnd + for pin_name in ["vdd", "gnd"]: + # Copy the pin shape(s) to rectangles + for pin in self.get_pins(pin_name): + self.add_rect(pin.layer, + pin.ll(), + pin.width(), + pin.height()) + + # Remove the pin shape(s) + self.remove_layout_pin(pin_name) + + # Get the lowest, leftest pin + pin = rtr.get_ll_pin(pin_name) + + pin_width = 2 * getattr(self, "{}_width".format(pin.layer)) + + # Add it as an IO pin to the perimeter + route_width = pin.rx() - lowest_coord.x + pin_offset = vector(lowest_coord.x, pin.by()) + self.add_rect(pin.layer, + pin_offset, + route_width, + pin.height()) + + self.add_layout_pin(self.ext_supply[pin_name], + pin.layer, + pin_offset, + pin_width, + pin.height()) + else: + # Grid is left with many top level pins + pass + + def route_escape_pins(self, bbox): + """ + Add the top-level pins for a single bank SRAM with control. + """ + + # List of pin to new pin name + pins_to_route = [] + for port in self.all_ports: + # Connect the control pins as inputs + for signal in self.control_logic_inputs[port]: + if signal.startswith("rbl"): + continue + if signal=="clk": + pins_to_route.append("{0}{1}".format(signal, port)) + else: + pins_to_route.append("{0}{1}".format(signal, port)) + + if port in self.write_ports: + for bit in range(self.word_size + self.num_spare_cols): + pins_to_route.append("din{0}[{1}]".format(port, bit)) + + if port in self.readwrite_ports or port in self.read_ports: + for bit in range(self.word_size + self.num_spare_cols): + pins_to_route.append("dout{0}[{1}]".format(port, bit)) + + for bit in range(self.col_addr_size): + pins_to_route.append("addr{0}[{1}]".format(port, bit)) + + for bit in range(self.row_addr_size): + pins_to_route.append("addr{0}[{1}]".format(port, bit + self.col_addr_size)) + + if port in self.write_ports: + if self.write_size != self.word_size: + for bit in range(self.num_wmasks): + pins_to_route.append("wmask{0}[{1}]".format(port, bit)) + + if port in self.write_ports: + if self.num_spare_cols == 1: + pins_to_route.append("spare_wen{0}".format(port)) + else: + for bit in range(self.num_spare_cols): + pins_to_route.append("spare_wen{0}[{1}]".format(port, bit)) + + from router import signal_escape_router as router + rtr=router(layers=self.m3_stack, + design=self, + bbox=bbox) + rtr.escape_route(pins_to_route) + + def compute_bus_sizes(self): + """ Compute the independent bus widths shared between two and four bank SRAMs """ + + # address size + control signals + one-hot bank select signals + self.num_vertical_line = self.bank_addr_size + self.control_size + 1# + log(self.num_banks, 2) + 1 + # data bus size + self.num_horizontal_line = self.word_size + + self.vertical_bus_width = self.m2_pitch * self.num_vertical_line + # vertical bus height depends on 2 or 4 banks + + self.data_bus_height = self.m3_pitch * self.num_horizontal_line + self.data_bus_width = 2 * (self.bank.width + self.bank_to_bus_distance) + self.vertical_bus_width + + self.control_bus_height = self.m1_pitch * (self.control_size + 2) + self.control_bus_width = self.bank.width + self.bank_to_bus_distance + self.vertical_bus_width + + self.supply_bus_height = self.m1_pitch * 2 # 2 for vdd/gnd placed with control bus + self.supply_bus_width = self.data_bus_width + + # Sanity check to ensure we can fit the control logic above a single bank (0.9 is a hack really) + debug.check(self.bank.width + self.vertical_bus_width > 0.9 * self.control_logic.width, + "Bank is too small compared to control logic.") + + def add_busses(self): + """ Add the horizontal and vertical busses """ + # Vertical bus + # The order of the control signals on the control bus: + self.control_bus_names = [] + for port in self.all_ports: + self.control_bus_names[port] = ["clk_buf{}".format(port)] + wen = "w_en{}".format(port) + sen = "s_en{}".format(port) + pen = "p_en_bar{}".format(port) + if self.port_id[port] == "r": + self.control_bus_names[port].extend([sen, pen]) + elif self.port_id[port] == "w": + self.control_bus_names[port].extend([wen, pen]) + else: + self.control_bus_names[port].extend([sen, wen, pen]) + self.vert_control_bus_positions = self.create_vertical_bus(layer="m2", + pitch=self.m2_pitch, + offset=self.vertical_bus_offset, + names=self.control_bus_names[port], + length=self.vertical_bus_height) + + self.addr_bus_names=["A{0}[{1}]".format(port, i) for i in range(self.bank_addr_size)] + self.vert_control_bus_positions.update(self.create_vertical_pin_bus(layer="m2", + pitch=self.m2_pitch, + offset=self.addr_bus_offset, + names=self.addr_bus_names, + length=self.addr_bus_height)) + + # Horizontal data bus + self.data_bus_names = ["DATA{0}[{1}]".format(port, i) for i in range(self.word_size)] + self.data_bus_positions = self.create_horizontal_pin_bus(layer="m3", + pitch=self.m3_pitch, + offset=self.data_bus_offset, + names=self.data_bus_names, + length=self.data_bus_width) + + # Horizontal control logic bus + # vdd/gnd in bus go along whole SRAM + # FIXME: Fatten these wires? + self.horz_control_bus_positions = self.create_horizontal_bus(layer="m1", + pitch=self.m1_pitch, + offset=self.supply_bus_offset, + names=["vdd"], + length=self.supply_bus_width) + # The gnd rail must not be the entire width since we protrude the right-most vdd rail up for + # the decoder in 4-bank SRAMs + self.horz_control_bus_positions.update(self.create_horizontal_bus(layer="m1", + pitch=self.m1_pitch, + offset=self.supply_bus_offset + vector(0, self.m1_pitch), + names=["gnd"], + length=self.supply_bus_width)) + self.horz_control_bus_positions.update(self.create_horizontal_bus(layer="m1", + pitch=self.m1_pitch, + offset=self.control_bus_offset, + names=self.control_bus_names[port], + length=self.control_bus_width)) + + def add_modules(self): + self.bitcell = factory.create(module_type=OPTS.bitcell) + self.dff = factory.create(module_type="dff") + + # Create the bank module (up to four are instantiated) + self.bank = factory.create("bank", sram_config=self.sram_config, module_name="bank") + + self.num_spare_cols = self.bank.num_spare_cols + + # Create the address and control flops (but not the clk) + self.row_addr_dff = factory.create("dff_array", module_name="row_addr_dff", rows=self.row_addr_size, columns=1) + + if self.col_addr_size > 0: + self.col_addr_dff = factory.create("dff_array", module_name="col_addr_dff", rows=1, columns=self.col_addr_size) + else: + self.col_addr_dff = None + + self.data_dff = factory.create("dff_array", module_name="data_dff", rows=1, columns=self.word_size + self.num_spare_cols) + + if self.write_size != self.word_size: + self.wmask_dff = factory.create("dff_array", module_name="wmask_dff", rows=1, columns=self.num_wmasks) + + if self.num_spare_cols: + self.spare_wen_dff = factory.create("dff_array", module_name="spare_wen_dff", rows=1, columns=self.num_spare_cols) + + self.bank_count = 0 + + c = reload(__import__('modules.' + OPTS.control_logic)) + self.mod_control_logic = getattr(c, OPTS.control_logic) + + # Create the control logic module for each port type + if len(self.readwrite_ports) > 0: + self.control_logic_rw = self.mod_control_logic(num_rows=self.num_rows, + words_per_row=self.words_per_row, + word_size=self.word_size, + spare_columns=self.num_spare_cols, + sram=self, + port_type="rw") + if len(self.writeonly_ports) > 0: + self.control_logic_w = self.mod_control_logic(num_rows=self.num_rows, + words_per_row=self.words_per_row, + word_size=self.word_size, + spare_columns=self.num_spare_cols, + sram=self, + port_type="w") + if len(self.readonly_ports) > 0: + self.control_logic_r = self.mod_control_logic(num_rows=self.num_rows, + words_per_row=self.words_per_row, + word_size=self.word_size, + spare_columns=self.num_spare_cols, + sram=self, + port_type="r") + + def create_bank(self, bank_num): + """ Create a bank """ + self.bank_insts.append(self.add_inst(name="bank{0}".format(bank_num), + mod=self.bank)) + + temp = [] + for port in self.read_ports: + for bit in range(self.word_size + self.num_spare_cols): + temp.append("dout{0}[{1}]".format(port, bit)) + for port in self.all_ports: + temp.append("rbl_bl{0}".format(port)) + for port in self.write_ports: + for bit in range(self.word_size + self.num_spare_cols): + temp.append("bank_din{0}_{1}".format(port, bit)) + for port in self.all_ports: + for bit in range(self.bank_addr_size): + temp.append("a{0}_{1}".format(port, bit)) + for port in self.read_ports: + temp.append("s_en{0}".format(port)) + for port in self.all_ports: + temp.append("p_en_bar{0}".format(port)) + for port in self.write_ports: + temp.append("w_en{0}".format(port)) + for bit in range(self.num_wmasks): + temp.append("bank_wmask{0}_{1}".format(port, bit)) + for bit in range(self.num_spare_cols): + temp.append("bank_spare_wen{0}_{1}".format(port, bit)) + for port in self.all_ports: + temp.append("wl_en{0}".format(port)) + temp.extend(self.ext_supplies) + self.connect_inst(temp) + + return self.bank_insts[-1] + + def place_bank(self, bank_inst, position, x_flip, y_flip): + """ Place a bank at the given position with orientations """ + + # x_flip == 1 --> no flip in x_axis + # x_flip == -1 --> flip in x_axis + # y_flip == 1 --> no flip in y_axis + # y_flip == -1 --> flip in y_axis + + # x_flip and y_flip are used for position translation + + if x_flip == -1 and y_flip == -1: + bank_rotation = 180 + else: + bank_rotation = 0 + + if x_flip == y_flip: + bank_mirror = "R0" + elif x_flip == -1: + bank_mirror = "MX" + elif y_flip == -1: + bank_mirror = "MY" + else: + bank_mirror = "R0" + + bank_inst.place(offset=position, + mirror=bank_mirror, + rotate=bank_rotation) + + return bank_inst + + def create_row_addr_dff(self): + """ Add all address flops for the main decoder """ + insts = [] + for port in self.all_ports: + insts.append(self.add_inst(name="row_address{}".format(port), + mod=self.row_addr_dff)) + + # inputs, outputs/output/bar + inputs = [] + outputs = [] + for bit in range(self.row_addr_size): + inputs.append("addr{}[{}]".format(port, bit + self.col_addr_size)) + outputs.append("a{}_{}".format(port, bit + self.col_addr_size)) + + self.connect_inst(inputs + outputs + ["clk_buf{}".format(port)] + self.ext_supplies) + + return insts + + def create_col_addr_dff(self): + """ Add and place all address flops for the column decoder """ + insts = [] + for port in self.all_ports: + insts.append(self.add_inst(name="col_address{}".format(port), + mod=self.col_addr_dff)) + + # inputs, outputs/output/bar + inputs = [] + outputs = [] + for bit in range(self.col_addr_size): + inputs.append("addr{}[{}]".format(port, bit)) + outputs.append("a{}_{}".format(port, bit)) + + self.connect_inst(inputs + outputs + ["clk_buf{}".format(port)] + self.ext_supplies) + + return insts + + def create_data_dff(self): + """ Add and place all data flops """ + insts = [] + for port in self.all_ports: + if port in self.write_ports: + insts.append(self.add_inst(name="data_dff{}".format(port), + mod=self.data_dff)) + else: + insts.append(None) + continue + + # inputs, outputs/output/bar + inputs = [] + outputs = [] + for bit in range(self.word_size + self.num_spare_cols): + inputs.append("din{}[{}]".format(port, bit)) + outputs.append("bank_din{}_{}".format(port, bit)) + + self.connect_inst(inputs + outputs + ["clk_buf{}".format(port)] + self.ext_supplies) + + return insts + + def create_wmask_dff(self): + """ Add and place all wmask flops """ + insts = [] + for port in self.all_ports: + if port in self.write_ports: + insts.append(self.add_inst(name="wmask_dff{}".format(port), + mod=self.wmask_dff)) + else: + insts.append(None) + continue + + # inputs, outputs/output/bar + inputs = [] + outputs = [] + for bit in range(self.num_wmasks): + inputs.append("wmask{}[{}]".format(port, bit)) + outputs.append("bank_wmask{}_{}".format(port, bit)) + + self.connect_inst(inputs + outputs + ["clk_buf{}".format(port)] + self.ext_supplies) + + return insts + + def create_spare_wen_dff(self): + """ Add all spare write enable flops """ + insts = [] + for port in self.all_ports: + if port in self.write_ports: + insts.append(self.add_inst(name="spare_wen_dff{}".format(port), + mod=self.spare_wen_dff)) + else: + insts.append(None) + continue + + # inputs, outputs/output/bar + inputs = [] + outputs = [] + for bit in range(self.num_spare_cols): + inputs.append("spare_wen{}[{}]".format(port, bit)) + outputs.append("bank_spare_wen{}_{}".format(port, bit)) + + self.connect_inst(inputs + outputs + ["clk_buf{}".format(port)] + self.ext_supplies) + + return insts + + def create_control_logic(self): + """ Add control logic instances """ + + insts = [] + for port in self.all_ports: + if port in self.readwrite_ports: + mod = self.control_logic_rw + elif port in self.write_ports: + mod = self.control_logic_w + else: + mod = self.control_logic_r + + insts.append(self.add_inst(name="control{}".format(port), mod=mod)) + + # Inputs + temp = ["csb{}".format(port)] + if port in self.readwrite_ports: + temp.append("web{}".format(port)) + temp.append("clk{}".format(port)) + temp.append("rbl_bl{}".format(port)) + + # Outputs + if port in self.read_ports: + temp.append("s_en{}".format(port)) + if port in self.write_ports: + temp.append("w_en{}".format(port)) + temp.append("p_en_bar{}".format(port)) + temp.extend(["wl_en{}".format(port), "clk_buf{}".format(port)] + self.ext_supplies) + self.connect_inst(temp) + + return insts + + def sp_write(self, sp_name, lvs=False, trim=False): + # Write the entire spice of the object to the file + ############################################################ + # Spice circuit + ############################################################ + sp = open(sp_name, 'w') + + sp.write("**************************************************\n") + sp.write("* OpenRAM generated memory.\n") + sp.write("* Words: {}\n".format(self.num_words)) + sp.write("* Data bits: {}\n".format(self.word_size)) + sp.write("* Banks: {}\n".format(self.num_banks)) + sp.write("* Column mux: {}:1\n".format(self.words_per_row)) + sp.write("* Trimmed: {}\n".format(trim)) + sp.write("* LVS: {}\n".format(lvs)) + sp.write("**************************************************\n") + # This causes unit test mismatch + + # sp.write("* Created: {0}\n".format(datetime.datetime.now())) + # sp.write("* User: {0}\n".format(getpass.getuser())) + # sp.write(".global {0} {1}\n".format(spice["vdd_name"], + # spice["gnd_name"])) + usedMODS = list() + self.sp_write_file(sp, usedMODS, lvs=lvs, trim=trim) + del usedMODS + sp.close() + + def graph_exclude_bits(self, targ_row, targ_col): + """ + Excludes bits in column from being added to graph except target + """ + self.bank.graph_exclude_bits(targ_row, targ_col) + + def clear_exclude_bits(self): + """ + Clears the bit exclusions + """ + self.bank.clear_exclude_bits() + + def graph_exclude_column_mux(self, column_include_num, port): + """ + Excludes all columns muxes unrelated to the target bit being simulated. + """ + self.bank.graph_exclude_column_mux(column_include_num, port) + + def graph_clear_column_mux(self, port): + """ + Clear mux exclusions to allow different bit tests. + """ + self.bank.graph_clear_column_mux(port) def create_modules(self): """ @@ -34,7 +772,7 @@ class sram_1bank(sram_base): if self.col_addr_dff: self.col_addr_dff_insts = self.create_col_addr_dff() - if self.write_size: + if self.write_size != self.word_size: self.wmask_dff_insts = self.create_wmask_dff() self.data_dff_insts = self.create_data_dff() else: @@ -178,7 +916,7 @@ class sram_1bank(sram_base): self.col_addr_pos[port] = vector(x_offset, 0) if port in self.write_ports: - if self.write_size: + if self.write_size != self.word_size: # Add the write mask flops below the write mask AND array. self.wmask_pos[port] = vector(x_offset, y_offset) @@ -230,7 +968,7 @@ class sram_1bank(sram_base): self.spare_wen_dff_insts[port].place(self.spare_wen_pos[port], mirror="MX") x_offset = self.spare_wen_dff_insts[port].lx() - if self.write_size: + if self.write_size != self.word_size: # Add the write mask flops below the write mask AND array. self.wmask_pos[port] = vector(x_offset - self.wmask_dff_insts[port].width, y_offset) @@ -297,7 +1035,7 @@ class sram_1bank(sram_base): start_layer=pin_layer) if port in self.write_ports: - if self.write_size: + if self.write_size != self.word_size: for bit in range(self.num_wmasks): self.add_io_pin(self.wmask_dff_insts[port], "din_{}".format(bit), @@ -609,7 +1347,7 @@ class sram_1bank(sram_base): # Data dffs and wmask dffs are only for writing so are not useful for evaluating read delay. for inst in self.data_dff_insts: self.graph_inst_exclude.add(inst) - if self.write_size: + if self.write_size != self.word_size: for inst in self.wmask_dff_insts: self.graph_inst_exclude.add(inst) if self.num_spare_cols: diff --git a/compiler/modules/sram_2bank.py b/compiler/modules/sram_2bank.py deleted file mode 100644 index 69ecb496..00000000 --- a/compiler/modules/sram_2bank.py +++ /dev/null @@ -1,240 +0,0 @@ -# See LICENSE for licensing information. -# -# Copyright (c) 2016-2021 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 sys -from tech import drc, spice -import debug -from math import log,sqrt,ceil -import datetime -import getpass -from base import vector -from globals import OPTS, print_time - -from .sram_base import sram_base -from modules import bank -from modules import dff_buf_array -from modules import dff_array - -class sram_2bank(sram_base): - """ - Procedures specific to a two bank SRAM. - """ - def __init__(self, name, sram_config): - sram_base.__init__(self, name, sram_config) - - def compute_bank_offsets(self): - """ Compute the overall offsets for a two bank SRAM """ - - # In 2 bank SRAM, the height is determined by the control bus which is higher than the msb address - self.vertical_bus_height = self.bank.height + 2*self.bank_to_bus_distance + self.data_bus_height + self.control_bus_height - # The address bus extends down through the power rails, but control and bank_sel bus don't - self.addr_bus_height = self.vertical_bus_height - - self.vertical_bus_offset = vector(self.bank.width + self.bank_to_bus_distance, 0) - self.data_bus_offset = vector(0, self.bank.height + self.bank_to_bus_distance) - self.supply_bus_offset = vector(0, self.data_bus_offset.y + self.data_bus_height) - self.control_bus_offset = vector(0, self.supply_bus_offset.y + self.supply_bus_height) - self.bank_sel_bus_offset = self.vertical_bus_offset + vector(self.m2_pitch*self.control_size,0) - self.addr_bus_offset = self.bank_sel_bus_offset.scale(1,0) + vector(self.m2_pitch*self.num_banks,0) - - # Control is placed at the top above the control bus and everything - self.control_logic_position = vector(0, self.control_bus_offset.y + self.control_bus_height + self.m1_pitch) - - # Bank select flops get put to the right of control logic above bank1 and the buses - # Leave a pitch to get the vdd rails up to M2 - self.msb_address_position = vector(self.bank_inst[1].lx() + 3*self.supply_rail_pitch, - self.supply_bus_offset.y + self.supply_bus_height \ - + 2*self.m1_pitch + self.msb_address.width) - - def add_modules(self): - """ Adds the modules and the buses to the top level """ - - self.compute_bus_sizes() - - self.add_banks() - - self.compute_bank_offsets() - - self.add_busses() - - self.add_logic() - - self.width = self.bank_inst[1].ur().x - self.height = self.control_logic_inst.uy() - - - - def add_banks(self): - # Placement of bank 0 (left) - bank_position_0 = vector(self.bank.width, - self.bank.height) - self.bank_inst=[self.add_bank(0, bank_position_0, -1, -1)] - - # Placement of bank 1 (right) - x_off = self.bank.width + self.vertical_bus_width + 2*self.bank_to_bus_distance - bank_position_1 = vector(x_off, bank_position_0.y) - self.bank_inst.append(self.add_bank(1, bank_position_1, -1, 1)) - - def add_logic(self): - """ Add the control and MSB logic """ - - self.add_control_logic(position=self.control_logic_position) - - self.msb_address_inst = self.add_inst(name="msb_address", - mod=self.msb_address, - offset=self.msb_address_position, - rotate=270) - self.msb_bank_sel_addr = "addr[{}]".format(self.addr_size-1) - self.connect_inst([self.msb_bank_sel_addr,"bank_sel[1]","bank_sel[0]","clk_buf", "vdd", "gnd"]) - - - def route_shared_banks(self): - """ Route the shared signals for two and four bank configurations. """ - - # create the input control pins - for n in self.control_logic_inputs + ["clk"]: - self.copy_layout_pin(self.control_logic_inst, n) - - # connect the control logic to the control bus - for n in self.control_logic_outputs + ["vdd", "gnd"]: - pins = self.control_logic_inst.get_pins(n) - for pin in pins: - if pin.layer=="m2": - pin_pos = pin.bc() - break - rail_pos = vector(pin_pos.x,self.horz_control_bus_positions[n].y) - self.add_path("m2",[pin_pos,rail_pos]) - self.add_via_center(self.m1_stack,rail_pos) - - # connect the control logic cross bar - for n in self.control_logic_outputs: - cross_pos = vector(self.vert_control_bus_positions[n].x,self.horz_control_bus_positions[n].y) - self.add_via_center(self.m1_stack,cross_pos) - - # connect the bank select signals to the vertical bus - for i in range(self.num_banks): - pin = self.bank_inst[i].get_pin("bank_sel") - pin_pos = pin.rc() if i==0 else pin.lc() - rail_pos = vector(self.vert_control_bus_positions["bank_sel[{}]".format(i)].x,pin_pos.y) - self.add_path("m3",[pin_pos,rail_pos]) - self.add_via_center(self.m2_stack,rail_pos) - - def route_single_msb_address(self): - """ Route one MSB address bit for 2-bank SRAM """ - - # connect the bank MSB flop supplies - vdd_pins = self.msb_address_inst.get_pins("vdd") - for vdd_pin in vdd_pins: - if vdd_pin.layer != "m1": continue - vdd_pos = vdd_pin.bc() - down_pos = vdd_pos - vector(0,self.m1_pitch) - rail_pos = vector(vdd_pos.x,self.horz_control_bus_positions["vdd"].y) - self.add_path("m1",[vdd_pos,down_pos]) - self.add_via_center(self.m1_stack,down_pos,rotate=90) - self.add_path("m2",[down_pos,rail_pos]) - self.add_via_center(self.m1_stack,rail_pos) - - gnd_pins = self.msb_address_inst.get_pins("gnd") - # Only add the ground connection to the lowest metal2 rail in the flop array - # FIXME: SCMOS doesn't have a vertical rail in the cell, or we could use those - lowest_y = None - for gnd_pin in gnd_pins: - if gnd_pin.layer != "m2": continue - if lowest_y==None or gnd_pin.by() 0.9 * self.control_logic.width, - "Bank is too small compared to control logic.") - - def add_busses(self): - """ Add the horizontal and vertical busses """ - # Vertical bus - # The order of the control signals on the control bus: - self.control_bus_names = [] - for port in self.all_ports: - self.control_bus_names[port] = ["clk_buf{}".format(port)] - wen = "w_en{}".format(port) - sen = "s_en{}".format(port) - pen = "p_en_bar{}".format(port) - if self.port_id[port] == "r": - self.control_bus_names[port].extend([sen, pen]) - elif self.port_id[port] == "w": - self.control_bus_names[port].extend([wen, pen]) - else: - self.control_bus_names[port].extend([sen, wen, pen]) - self.vert_control_bus_positions = self.create_vertical_bus(layer="m2", - pitch=self.m2_pitch, - offset=self.vertical_bus_offset, - names=self.control_bus_names[port], - length=self.vertical_bus_height) - - self.addr_bus_names=["A{0}[{1}]".format(port, i) for i in range(self.addr_size)] - self.vert_control_bus_positions.update(self.create_vertical_pin_bus(layer="m2", - pitch=self.m2_pitch, - offset=self.addr_bus_offset, - names=self.addr_bus_names, - length=self.addr_bus_height)) - - self.bank_sel_bus_names = ["bank_sel{0}_{1}".format(port, i) for i in range(self.num_banks)] - self.vert_control_bus_positions.update(self.create_vertical_pin_bus(layer="m2", - pitch=self.m2_pitch, - offset=self.bank_sel_bus_offset, - names=self.bank_sel_bus_names, - length=self.vertical_bus_height)) - - # Horizontal data bus - self.data_bus_names = ["DATA{0}[{1}]".format(port, i) for i in range(self.word_size)] - self.data_bus_positions = self.create_horizontal_pin_bus(layer="m3", - pitch=self.m3_pitch, - offset=self.data_bus_offset, - names=self.data_bus_names, - length=self.data_bus_width) - - # Horizontal control logic bus - # vdd/gnd in bus go along whole SRAM - # FIXME: Fatten these wires? - self.horz_control_bus_positions = self.create_horizontal_bus(layer="m1", - pitch=self.m1_pitch, - offset=self.supply_bus_offset, - names=["vdd"], - length=self.supply_bus_width) - # The gnd rail must not be the entire width since we protrude the right-most vdd rail up for - # the decoder in 4-bank SRAMs - self.horz_control_bus_positions.update(self.create_horizontal_bus(layer="m1", - pitch=self.m1_pitch, - offset=self.supply_bus_offset + vector(0, self.m1_pitch), - names=["gnd"], - length=self.supply_bus_width)) - self.horz_control_bus_positions.update(self.create_horizontal_bus(layer="m1", - pitch=self.m1_pitch, - offset=self.control_bus_offset, - names=self.control_bus_names[port], - length=self.control_bus_width)) - - def add_multi_bank_modules(self): - """ Create the multibank address flops and bank decoder """ - from dff_buf_array import dff_buf_array - self.msb_address = dff_buf_array(name="msb_address", - rows=1, - columns=self.num_banks / 2) - - if self.num_banks>2: - self.msb_decoder = self.bank.decoder.pre2_4 - - def add_modules(self): - self.bitcell = factory.create(module_type=OPTS.bitcell) - self.dff = factory.create(module_type="dff") - - # Create the bank module (up to four are instantiated) - self.bank = factory.create("bank", sram_config=self.sram_config, module_name="bank") - - self.num_spare_cols = self.bank.num_spare_cols - - # Create the address and control flops (but not the clk) - self.row_addr_dff = factory.create("dff_array", module_name="row_addr_dff", rows=self.row_addr_size, columns=1) - - if self.col_addr_size > 0: - self.col_addr_dff = factory.create("dff_array", module_name="col_addr_dff", rows=1, columns=self.col_addr_size) - else: - self.col_addr_dff = None - - self.data_dff = factory.create("dff_array", module_name="data_dff", rows=1, columns=self.word_size + self.num_spare_cols) - - if self.write_size: - self.wmask_dff = factory.create("dff_array", module_name="wmask_dff", rows=1, columns=self.num_wmasks) - - if self.num_spare_cols: - self.spare_wen_dff = factory.create("dff_array", module_name="spare_wen_dff", rows=1, columns=self.num_spare_cols) - - # Create bank decoder - if(self.num_banks > 1): - self.add_multi_bank_modules() - - self.bank_count = 0 - - c = importlib.import_module("modules." + OPTS.control_logic) - self.mod_control_logic = getattr(c, OPTS.control_logic) - - # Create the control logic module for each port type - if len(self.readwrite_ports) > 0: - self.control_logic_rw = self.mod_control_logic(num_rows=self.num_rows, - words_per_row=self.words_per_row, - word_size=self.word_size, - spare_columns=self.num_spare_cols, - sram=self, - port_type="rw") - if len(self.writeonly_ports) > 0: - self.control_logic_w = self.mod_control_logic(num_rows=self.num_rows, - words_per_row=self.words_per_row, - word_size=self.word_size, - spare_columns=self.num_spare_cols, - sram=self, - port_type="w") - if len(self.readonly_ports) > 0: - self.control_logic_r = self.mod_control_logic(num_rows=self.num_rows, - words_per_row=self.words_per_row, - word_size=self.word_size, - spare_columns=self.num_spare_cols, - sram=self, - port_type="r") - - def create_bank(self, bank_num): - """ Create a bank """ - self.bank_insts.append(self.add_inst(name="bank{0}".format(bank_num), - mod=self.bank)) - - temp = [] - for port in self.read_ports: - for bit in range(self.word_size + self.num_spare_cols): - temp.append("dout{0}[{1}]".format(port, bit)) - for port in self.all_ports: - temp.append("rbl_bl{0}".format(port)) - for port in self.write_ports: - for bit in range(self.word_size + self.num_spare_cols): - temp.append("bank_din{0}_{1}".format(port, bit)) - for port in self.all_ports: - for bit in range(self.bank_addr_size): - temp.append("a{0}_{1}".format(port, bit)) - if(self.num_banks > 1): - for port in self.all_ports: - temp.append("bank_sel{0}_{1}".format(port, bank_num)) - for port in self.read_ports: - temp.append("s_en{0}".format(port)) - for port in self.all_ports: - temp.append("p_en_bar{0}".format(port)) - for port in self.write_ports: - temp.append("w_en{0}".format(port)) - for bit in range(self.num_wmasks): - temp.append("bank_wmask{0}_{1}".format(port, bit)) - for bit in range(self.num_spare_cols): - temp.append("bank_spare_wen{0}_{1}".format(port, bit)) - for port in self.all_ports: - temp.append("wl_en{0}".format(port)) - temp.extend(self.ext_supplies) - self.connect_inst(temp) - - return self.bank_insts[-1] - - def place_bank(self, bank_inst, position, x_flip, y_flip): - """ Place a bank at the given position with orientations """ - - # x_flip == 1 --> no flip in x_axis - # x_flip == -1 --> flip in x_axis - # y_flip == 1 --> no flip in y_axis - # y_flip == -1 --> flip in y_axis - - # x_flip and y_flip are used for position translation - - if x_flip == -1 and y_flip == -1: - bank_rotation = 180 - else: - bank_rotation = 0 - - if x_flip == y_flip: - bank_mirror = "R0" - elif x_flip == -1: - bank_mirror = "MX" - elif y_flip == -1: - bank_mirror = "MY" - else: - bank_mirror = "R0" - - bank_inst.place(offset=position, - mirror=bank_mirror, - rotate=bank_rotation) - - return bank_inst - - def create_row_addr_dff(self): - """ Add all address flops for the main decoder """ - insts = [] - for port in self.all_ports: - insts.append(self.add_inst(name="row_address{}".format(port), - mod=self.row_addr_dff)) - - # inputs, outputs/output/bar - inputs = [] - outputs = [] - for bit in range(self.row_addr_size): - inputs.append("addr{}[{}]".format(port, bit + self.col_addr_size)) - outputs.append("a{}_{}".format(port, bit + self.col_addr_size)) - - self.connect_inst(inputs + outputs + ["clk_buf{}".format(port)] + self.ext_supplies) - - return insts - - def create_col_addr_dff(self): - """ Add and place all address flops for the column decoder """ - insts = [] - for port in self.all_ports: - insts.append(self.add_inst(name="col_address{}".format(port), - mod=self.col_addr_dff)) - - # inputs, outputs/output/bar - inputs = [] - outputs = [] - for bit in range(self.col_addr_size): - inputs.append("addr{}[{}]".format(port, bit)) - outputs.append("a{}_{}".format(port, bit)) - - self.connect_inst(inputs + outputs + ["clk_buf{}".format(port)] + self.ext_supplies) - - return insts - - def create_data_dff(self): - """ Add and place all data flops """ - insts = [] - for port in self.all_ports: - if port in self.write_ports: - insts.append(self.add_inst(name="data_dff{}".format(port), - mod=self.data_dff)) - else: - insts.append(None) - continue - - # inputs, outputs/output/bar - inputs = [] - outputs = [] - for bit in range(self.word_size + self.num_spare_cols): - inputs.append("din{}[{}]".format(port, bit)) - outputs.append("bank_din{}_{}".format(port, bit)) - - self.connect_inst(inputs + outputs + ["clk_buf{}".format(port)] + self.ext_supplies) - - return insts - - def create_wmask_dff(self): - """ Add and place all wmask flops """ - insts = [] - for port in self.all_ports: - if port in self.write_ports: - insts.append(self.add_inst(name="wmask_dff{}".format(port), - mod=self.wmask_dff)) - else: - insts.append(None) - continue - - # inputs, outputs/output/bar - inputs = [] - outputs = [] - for bit in range(self.num_wmasks): - inputs.append("wmask{}[{}]".format(port, bit)) - outputs.append("bank_wmask{}_{}".format(port, bit)) - - self.connect_inst(inputs + outputs + ["clk_buf{}".format(port)] + self.ext_supplies) - - return insts - - def create_spare_wen_dff(self): - """ Add all spare write enable flops """ - insts = [] - for port in self.all_ports: - if port in self.write_ports: - insts.append(self.add_inst(name="spare_wen_dff{}".format(port), - mod=self.spare_wen_dff)) - else: - insts.append(None) - continue - - # inputs, outputs/output/bar - inputs = [] - outputs = [] - for bit in range(self.num_spare_cols): - if self.num_spare_cols == 1: - inputs.append("spare_wen{0}".format(port)) - else: - inputs.append("spare_wen{0}[{1}]".format(port, bit)) - outputs.append("bank_spare_wen{}_{}".format(port, bit)) - - self.connect_inst(inputs + outputs + ["clk_buf{}".format(port)] + self.ext_supplies) - - return insts - - def create_control_logic(self): - """ Add control logic instances """ - - insts = [] - for port in self.all_ports: - if port in self.readwrite_ports: - mod = self.control_logic_rw - elif port in self.write_ports: - mod = self.control_logic_w - else: - mod = self.control_logic_r - - insts.append(self.add_inst(name="control{}".format(port), mod=mod)) - - # Inputs - temp = ["csb{}".format(port)] - if port in self.readwrite_ports: - temp.append("web{}".format(port)) - temp.append("clk{}".format(port)) - temp.append("rbl_bl{}".format(port)) - - # Outputs - if port in self.read_ports: - temp.append("s_en{}".format(port)) - if port in self.write_ports: - temp.append("w_en{}".format(port)) - temp.append("p_en_bar{}".format(port)) - temp.extend(["wl_en{}".format(port), "clk_buf{}".format(port)] + self.ext_supplies) - self.connect_inst(temp) - - return insts - - def sp_write(self, sp_name, lvs=False, trim=False): - # Write the entire spice of the object to the file - ############################################################ - # Spice circuit - ############################################################ - sp = open(sp_name, 'w') - - sp.write("**************************************************\n") - sp.write("* OpenRAM generated memory.\n") - sp.write("* Words: {}\n".format(self.num_words)) - sp.write("* Data bits: {}\n".format(self.word_size)) - sp.write("* Banks: {}\n".format(self.num_banks)) - sp.write("* Column mux: {}:1\n".format(self.words_per_row)) - sp.write("* Trimmed: {}\n".format(trim)) - sp.write("* LVS: {}\n".format(lvs)) - sp.write("**************************************************\n") - # This causes unit test mismatch - - # sp.write("* Created: {0}\n".format(datetime.datetime.now())) - # sp.write("* User: {0}\n".format(getpass.getuser())) - # sp.write(".global {0} {1}\n".format(spice["vdd_name"], - # spice["gnd_name"])) - usedMODS = list() - self.sp_write_file(sp, usedMODS, lvs=lvs, trim=trim) - del usedMODS - sp.close() - - def graph_exclude_bits(self, targ_row, targ_col): - """ - Excludes bits in column from being added to graph except target - """ - self.bank.graph_exclude_bits(targ_row, targ_col) - - def clear_exclude_bits(self): - """ - Clears the bit exclusions - """ - self.bank.clear_exclude_bits() - - def graph_exclude_column_mux(self, column_include_num, port): - """ - Excludes all columns muxes unrelated to the target bit being simulated. - """ - self.bank.graph_exclude_column_mux(column_include_num, port) - - def graph_clear_column_mux(self, port): - """ - Clear mux exclusions to allow different bit tests. - """ - self.bank.graph_clear_column_mux(port) diff --git a/compiler/modules/sram_config.py b/compiler/modules/sram_config.py index 4c1bb117..a46c80a2 100644 --- a/compiler/modules/sram_config.py +++ b/compiler/modules/sram_config.py @@ -18,10 +18,11 @@ class sram_config: self.word_size = word_size self.num_words = num_words # Don't add a write mask if it is the same size as the data word - if write_size and write_size==word_size: - self.write_size = None - else: + self.write_size_init = write_size + if write_size: self.write_size = write_size + else: + self.write_size = word_size self.num_banks = num_banks self.num_spare_rows = num_spare_rows self.num_spare_cols = num_spare_cols @@ -72,8 +73,8 @@ class sram_config: bitcell = factory.create(module_type=OPTS.bitcell) - debug.check(self.num_banks in [1, 2, 4], - "Valid number of banks are 1 , 2 and 4.") + debug.check(ceil(log(self.num_banks, 2)) == log(self.num_banks, 2) , + "Number of banks should be power of 2.") self.num_words_per_bank = self.num_words / self.num_banks self.num_bits_per_bank = self.word_size * self.num_words_per_bank @@ -117,11 +118,18 @@ class sram_config: self.num_rows = self.num_rows_temp + self.num_spare_rows debug.info(1, "Rows: {} Cols: {}".format(self.num_rows_temp, self.num_cols)) + # Fix the write_size + if self.write_size_init: + self.write_size = self.write_size_init + else: + self.write_size = self.word_size + # Compute the address and bank sizes self.row_addr_size = ceil(log(self.num_rows, 2)) self.col_addr_size = int(log(self.words_per_row, 2)) self.bank_addr_size = self.col_addr_size + self.row_addr_size self.addr_size = self.bank_addr_size + int(log(self.num_banks, 2)) + #self.addr_size = self.bank_addr_size debug.info(1, "Row addr size: {}".format(self.row_addr_size) + " Col addr size: {}".format(self.col_addr_size) + " Bank addr size: {}".format(self.bank_addr_size)) diff --git a/compiler/modules/sram_multibank.py b/compiler/modules/sram_multibank.py new file mode 100644 index 00000000..675e0c02 --- /dev/null +++ b/compiler/modules/sram_multibank.py @@ -0,0 +1,40 @@ +from .template import template +from globals import OPTS +import os +from math import ceil, log +import re + + +class sram_multibank: + + def __init__(self, sram): + rw_ports = [i for i in sram.all_ports if i in sram.read_ports and i in sram.write_ports] + r_ports = [i for i in sram.all_ports if i in sram.read_ports and i not in sram.write_ports] + w_ports = [i for i in sram.all_ports if i not in sram.read_ports and i in sram.write_ports] + self.dict = { + 'module_name': sram.name + '_top', + 'bank_module_name': sram.name, + 'vdd': 'vdd', + 'gnd': 'gnd', + 'ports': sram.all_ports, + 'rw_ports': rw_ports, + 'r_ports': r_ports, + 'w_ports': w_ports, + 'banks': list(range(sram.num_banks)), + 'data_width': sram.word_size, + 'addr_width': sram.bank_addr_size + ceil(log(sram.num_banks, 2)), + 'bank_sel': ceil(log(sram.num_banks, 2)), + 'num_wmask': sram.num_wmasks, + 'write_size': sram.write_size + } + + def verilog_write(self, name): + template_filename = os.path.join(os.path.abspath(os.environ["OPENRAM_HOME"]), "modules/sram_multibank_template.v") + t = template(template_filename, self.dict) + t.write(name) + with open(name, 'r') as f: + text = f.read() + badComma = re.compile(r',(\s*\n\s*\);)') + text = badComma.sub(r'\1', text) + with open(name, 'w') as f: + f.write(text) diff --git a/compiler/modules/sram_multibank_template.v b/compiler/modules/sram_multibank_template.v new file mode 100644 index 00000000..85881a43 --- /dev/null +++ b/compiler/modules/sram_multibank_template.v @@ -0,0 +1,189 @@ + +module {{ module_name }} ( +`ifdef USE_POWER_PINS + {{ vdd }}, + {{ gnd }}, +`endif +{% for port in rw_ports %} + clk{{ port }}, + addr{{ port }}, + din{{ port }}, + csb{{ port }}, +{% if num_wmask > 1 %} + wmask{{ port }}, +{% endif %} + web{{ port }}, + dout{{ port }}, +{% endfor %} +{% for port in r_ports %} + clk{{ port }}, + addr{{ port }}, + csb{{ port }}, + dout{{ port }}, +{% endfor %} +{% for port in w_ports %} + clk{{ port }}, + addr{{ port }}, + din{{ port }}, + csb{{ port }}, +{% if num_wmask > 1 %} + wmask{{ port }}, +{% endif %} + web{{ port }}, +{% endfor %} + ); + + parameter DATA_WIDTH = {{ data_width }}; + parameter ADDR_WIDTH= {{ addr_width }}; + + parameter BANK_SEL = {{ bank_sel }}; + parameter NUM_WMASK = {{ num_wmask }}; + +`ifdef USE_POWER_PINS + inout {{ vdd }}; + inout {{ gnd }}; +`endif +{% for port in rw_ports %} + input clk{{ port }}; + input [ADDR_WIDTH - 1 : 0] addr{{ port }}; + input [DATA_WIDTH - 1: 0] din{{ port }}; + input csb{{ port }}; + input web{{ port }}; +{% if num_wmask > 1 %} + input [NUM_WMASK - 1 : 0] wmask{{ port }}; +{% endif %} + output reg [DATA_WIDTH - 1 : 0] dout{{ port }}; +{% endfor %} +{% for port in r_ports %} + input clk{{ port }}; + input [ADDR_WIDTH - 1 : 0] addr{{ port }}; + input csb{{ port }}; + output reg [DATA_WIDTH - 1 : 0] dout{{ port }}; +{% endfor %} +{% for port in w_ports %} + input clk{{ port }}; + input [ADDR_WIDTH - 1 : 0] addr{{ port }}; + input [DATA_WIDTH - 1: 0] din{{ port }}; + input csb{{ port }}; + input web{{ port }}; +{% if num_wmask > 1 %} + input [NUM_WMASK - 1 : 0] wmask{{ port }}; +{% endif %} +{% endfor %} + +{% for port in ports %} + reg [BANK_SEL - 1 : 0] addr{{ port }}_reg; + +{% for bank in banks %} + wire [DATA_WIDTH - 1 : 0] dout{{ port }}_bank{{ bank }}; + + reg web{{ port }}_bank{{ bank }}; + + reg csb{{ port }}_bank{{ bank }}; + +{% endfor %} +{% endfor %} + +{% for bank in banks %} + {{ bank_module_name }} bank{{ bank }} ( +`ifdef USE_POWER_PINS + .{{ vdd }}({{ vdd }}), + .{{ gnd }}({{ gnd }}), +`endif +{% for port in rw_ports %} + .clk{{ port }}(clk{{ port }}), + .addr{{ port }}(addr{{ port }}[ADDR_WIDTH - BANK_SEL - 1 : 0]), + .din{{ port }}(din{{ port }}), + .csb{{ port }}(csb{{ port }}_bank{{ bank }}), + .web{{ port }}(web{{ port }}_bank{{ bank }}), +{% if num_wmask > 1 %} + .wmask{{ port }}(wmask{{ port }}), +{% endif %} + .dout{{ port }}(dout{{ port }}_bank{{ bank }}), +{% endfor %} +{% for port in r_ports %} + .clk{{ port }}(clk{{ port }}), + .addr{{ port }}(addr{{ port }}[ADDR_WIDTH - BANK_SEL - 1 : 0]), + .csb{{ port }}(csb{{ port }}_bank{{ bank }}), + .dout{{ port }}(dout{{ port }}_bank{{ bank }}), +{% endfor %} +{% for port in w_ports %} + .clk{{ port }}(clk{{ port }}), + .addr{{ port }}(addr{{ port }}[ADDR_WIDTH - BANK_SEL - 1 : 0]), + .din{{ port }}(din{{ port }}), + .csb{{ port }}(csb{{ port }}_bank{{ bank }}), +{% if num_wmask > 1 %} + .wmask{{ port }}(wmask{{ port }}), +{% endif %} + .web{{ port }}(web{{ port }}_bank{{ bank }}), +{% endfor %} + ); +{% endfor %} + +{% for port in ports %} + always @(posedge clk{{ port }}) begin + addr{{ port }}_reg <= addr{{ port }}[ADDR_WIDTH - 1 : ADDR_WIDTH - BANK_SEL]; + end +{% endfor %} + +{% for port in ports %} + always @(*) begin + case (addr{{ port }}_reg) +{% for bank in banks %} + {{ bank }}: begin + dout{{ port }} = dout{{ port }}_bank{{ bank }}; + end +{% endfor %} + endcase + end +{% endfor %} + +{% for port in rw_ports %} + always @(*) begin +{% for bank in banks %} + csb{{ port }}_bank{{ bank }} = 1'b1; + web{{ port }}_bank{{ bank }} = 1'b1; +{% endfor %} + case (addr{{ port }}[ADDR_WIDTH - 1 : ADDR_WIDTH - BANK_SEL]) +{% for bank in banks %} + {{ bank }}: begin + web{{ port }}_bank{{ bank }} = web{{ port }}; + csb{{ port }}_bank{{ bank }} = csb{{ port }}; + end +{% endfor %} + endcase + end +{% endfor %} + +{% for port in w_ports %} + always @(*) begin +{% for bank in banks %} + csb{{ port }}_bank{{ bank }} = 1'b1; + web{{ port }}_bank{{ bank }} = 1'b1; +{% endfor %} + case (addr{{ port }}[ADDR_WIDTH - 1 : ADDR_WIDTH - BANK_SEL]) +{% for bank in banks %} + {{ bank }}: begin + web{{ port }}_bank{{ bank }} = web{{ port }}; + csb{{ port }}_bank{{ bank }} = csb{{ port }}; + end +{% endfor %} + endcase + end +{% endfor %} + +{% for port in r_ports %} + always @(*) begin +{% for bank in banks %} + csb{{ port }}_bank{{ bank }} = 1'b1; +{% endfor %} + case (addr{{ port }}[ADDR_WIDTH - 1 : ADDR_WIDTH - BANK_SEL]) +{% for bank in banks %} + {{ bank }}: begin + csb{{ port }}_bank{{ bank }} = csb{{ port }}; + end +{% endfor %} + endcase + end +{% endfor %} +endmodule diff --git a/compiler/modules/template.py b/compiler/modules/template.py new file mode 100644 index 00000000..ae93647f --- /dev/null +++ b/compiler/modules/template.py @@ -0,0 +1,120 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2021 Regents of the University of California +# Santa Cruz +# All rights reserved. +# +import re + + +class baseSection: + """ + This is the base section class for other section classes to inherit. + It is also used as the top most section. + """ + def __init__(self): + self.children = [] + + def expand(self, dict, fd): + for c in self.children: + c.expand(dict, fd) + + +class loopSection(baseSection): + """ + This section is for looping elements. It will repeat the children + sections based on the key list. + """ + + def __init__(self, var, key): + baseSection.__init__(self) + self.var = var + self.key = key + + def expand(self, dict, fd): + for ind in dict[self.key]: + dict[self.var] = ind + for c in self.children: + c.expand(dict, fd) + if self.var in dict: + del dict[self.var] + + +class conditionalSection(baseSection): + """ + This class will conditionally print it's children based on the 'cond' + element. + """ + def __init__(self, cond): + baseSection.__init__(self) + self.cond = cond + + def expand(self, dict, fd): + run = eval(self.cond, dict) + if run: + for c in self.children: + c.expand(dict, fd) + + +class textSection(baseSection): + """ + This is plain text section. It can contain parameters that can be + replaced based on the dictionary. + """ + + def __init__(self, text): + self.text = text + + def expand(self, dict, fd): + varRE = re.compile(r'\{\{ (\S*) \}\}') + vars = varRE.finditer(self.text) + newText = self.text + for var in vars: + newText = newText.replace('{{ ' + var.group(1) + ' }}', str(dict[var.group(1)])) + fd.write(newText) + + +class template: + """ + The template class will read a template and generate an output file + based on the template and the given dictionary. + """ + + def __init__(self, template, dict): + self.template = template + self.dict = dict + + def readTemplate(self): + lines = [] + with open(self.template, 'r') as f: + lines = f.readlines() + + self.baseSectionSection = baseSection() + context = [self.baseSectionSection] + forRE = re.compile(r'\s*\{% for (\S*) in (\S*) %\}') + endforRE = re.compile(r'\s*\{% endfor %\}') + ifRE = re.compile(r'\s*{% if (.*) %\}') + endifRE = re.compile(r'\s*\{% endif %\}') + for line in lines: + m = forRE.match(line) + if m: + section = loopSection(m.group(1), m.group(2)) + context[-1].children.append(section) + context.append(section) + continue + m = ifRE.match(line) + if m: + section = conditionalSection(m.group(1)) + context[-1].children.append(section) + context.append(section) + continue + if endforRE.match(line) or endifRE.match(line): + context.pop() + else: + context[-1].children.append(textSection(line)) + + def write(self, filename): + fd = open(filename, 'w') + self.readTemplate() + self.baseSectionSection.expand(self.dict, fd) + fd.close() diff --git a/compiler/modules/write_driver_array.py b/compiler/modules/write_driver_array.py index ee3d3c2f..5c3b664f 100644 --- a/compiler/modules/write_driver_array.py +++ b/compiler/modules/write_driver_array.py @@ -29,7 +29,10 @@ class write_driver_array(design): self.columns = columns self.word_size = word_size - self.write_size = write_size + if write_size is None: + self.write_size = word_size + else: + self.write_size = write_size self.offsets = offsets self.column_offset = column_offset self.words_per_row = int(columns / word_size) @@ -38,8 +41,10 @@ class write_driver_array(design): else: self.num_spare_cols = num_spare_cols - if self.write_size: + if self.write_size != self.word_size: self.num_wmasks = int(math.ceil(self.word_size / self.write_size)) + else: + self.num_wmasks = 0 self.create_netlist() if not OPTS.netlist_only: @@ -82,10 +87,10 @@ class write_driver_array(design): for i in range(self.word_size + self.num_spare_cols): self.add_pin(self.get_bl_name() + "_{0}".format(i), "OUTPUT") self.add_pin(self.get_br_name() + "_{0}".format(i), "OUTPUT") - if self.write_size: + if self.write_size != self.word_size: for i in range(self.num_wmasks + self.num_spare_cols): self.add_pin(self.en_name + "_{0}".format(i), "INPUT") - elif self.num_spare_cols and not self.write_size: + elif self.num_spare_cols and self.write_size == self.word_size: for i in range(self.num_spare_cols + 1): self.add_pin(self.en_name + "_{0}".format(i), "INPUT") else: @@ -110,7 +115,7 @@ class write_driver_array(design): self.local_insts.append(self.add_inst(name=name, mod=self.driver)) - if self.write_size: + if self.write_size != self.word_size: self.connect_inst([self.data_name + "_{0}".format(index), self.get_bl_name() + "_{0}".format(index), self.get_br_name() + "_{0}".format(index), @@ -121,7 +126,7 @@ class write_driver_array(design): w = 0 windex+=1 - elif self.num_spare_cols and not self.write_size: + elif self.num_spare_cols and self.write_size == self.word_size: self.connect_inst([self.data_name + "_{0}".format(index), self.get_bl_name() + "_{0}".format(index), self.get_br_name() + "_{0}".format(index), @@ -135,7 +140,7 @@ class write_driver_array(design): for i in range(self.num_spare_cols): index = self.word_size + i - if self.write_size: + if self.write_size != self.word_size: offset = self.num_wmasks else: offset = 1 @@ -205,7 +210,7 @@ class write_driver_array(design): width=br_pin.width(), height=br_pin.height()) - if self.write_size: + if self.write_size != self.word_size: for bit in range(self.num_wmasks): inst = self.local_insts[bit * self.write_size] en_pin = inst.get_pin(inst.mod.en_name) @@ -229,7 +234,7 @@ class write_driver_array(design): layer="m1", offset=en_pin.lr() + vector(-drc("minwidth_m1"),0)) - elif self.num_spare_cols and not self.write_size: + elif self.num_spare_cols and self.write_size == self.word_size: # shorten enable rail to accomodate those for spare write drivers left_inst = self.local_insts[0] left_en_pin = left_inst.get_pin(inst.mod.en_name) diff --git a/compiler/router/router.py b/compiler/router/router.py index 15af1a87..2e18b37a 100644 --- a/compiler/router/router.py +++ b/compiler/router/router.py @@ -805,7 +805,7 @@ class router(router_tech): try: x = track[0]*self.track_width - 0.5*self.track_width except TypeError: - print(track[0], type(track[0]), self.track_width, type(self.track_width)) + debug.warning("{} {} {} {}".format(track[0], type(track[0]), self.track_width, type(self.track_width))) y = track[1]*self.track_width - 0.5*self.track_width # offset lowest corner object to to (-track halo,-track halo) ll = snap_to_grid(vector(x, y)) diff --git a/compiler/router/supply_tree_router.py b/compiler/router/supply_tree_router.py index 80a386e0..a34f7dd0 100644 --- a/compiler/router/supply_tree_router.py +++ b/compiler/router/supply_tree_router.py @@ -145,13 +145,14 @@ class supply_tree_router(router): connections.append((x, y)) # Route MST components + level=99 for index, (src, dest) in enumerate(connections): if not (index % 25): debug.info(1, "{0} supply segments routed, {1} remaining.".format(index, len(connections) - index)) self.route_signal(pin_name, src, dest) if False and pin_name == "gnd": - print("\nSRC {}: ".format(src) + str(self.pin_groups[pin_name][src].grids) + str(self.pin_groups[pin_name][src].blockages)) - print("DST {}: ".format(dest) + str(self.pin_groups[pin_name][dest].grids) + str(self.pin_groups[pin_name][dest].blockages)) + debug.info(level, "\nSRC {}: ".format(src) + str(self.pin_groups[pin_name][src].grids) + str(self.pin_groups[pin_name][src].blockages)) + debug.info(level, ("DST {}: ".format(dest) + str(self.pin_groups[pin_name][dest].grids) + str(self.pin_groups[pin_name][dest].blockages))) self.write_debug_gds("post_{0}_{1}.gds".format(src, dest), False) #self.write_debug_gds("final_tree_router_{}.gds".format(pin_name), False) diff --git a/compiler/sram/__init__.py b/compiler/sram/__init__.py deleted file mode 100644 index 93bb0cbb..00000000 --- a/compiler/sram/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from .sram_1bank import * -from .sram_2bank import * -from .sram_base import * -from .sram_config import * -from .sram import * diff --git a/compiler/tests/00_code_format_check_test.py b/compiler/tests/00_code_format_check_test.py index 97ffda09..cfe72a21 100755 --- a/compiler/tests/00_code_format_check_test.py +++ b/compiler/tests/00_code_format_check_test.py @@ -125,6 +125,11 @@ def check_file_format_whitespace(file_name): def check_print_output(file_name): """Check if any files (except debug.py) call the _print_ function. We should use the debug output with verbosity instead!""" + + skip_files = ["printGDS.py", "uniquifyGDS.py", "processGDS.py", "model_data_util.py"] + base_file_name = os.path.basename(file_name) + if base_file_name in skip_files: + return(0) file = open(file_name, "r+b") line = file.read().decode('utf-8') # skip comments with a hash diff --git a/compiler/tests/21_model_delay_test.py b/compiler/tests/21_model_delay_test.py index ab6f3888..16924f0b 100755 --- a/compiler/tests/21_model_delay_test.py +++ b/compiler/tests/21_model_delay_test.py @@ -92,8 +92,8 @@ class model_delay_test(openram_test): else: self.assertTrue(False) # other techs fail - print('spice_delays', spice_delays) - print('model_delays', model_delays) + debug.info(3, 'spice_delays {}'.fomrat(spice_delays)) + debug.info(3, 'model_delays {}'.format(model_delays)) # Check if no too many or too few results self.assertTrue(len(spice_delays.keys())==len(model_delays.keys())) diff --git a/compiler/tests/25_verilog_multibank_test.py b/compiler/tests/25_verilog_multibank_test.py new file mode 100755 index 00000000..91fb04ae --- /dev/null +++ b/compiler/tests/25_verilog_multibank_test.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2021 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 +import debug + + +class multibank_verilog_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + OPTS.route_supplies=False + OPTS.check_lvsdrc=False + OPTS.netlist_only=True + from modules import sram + from modules import sram_config + c = sram_config(word_size=2, + num_words=16, + num_banks=2) + c.words_per_row=1 + c.recompute_sizes() + debug.info(1, "Testing Verilog for sample 2 bit, 16 words SRAM with 2 bank") + # This doesn't have to use the factory since worst case + # it will just replaece the top-level module of the same name + s = sram(c, "sram_2_16_2_{0}".format(OPTS.tech_name)) + + vfile = s.name + "_top.v" + vname = OPTS.openram_temp + vfile + + v1bfile = s.name + ".v" + v1bname = OPTS.openram_temp + v1bfile + + s.verilog_write(v1bname) + + # let's diff the result with a golden model + multi_golden = "{0}/golden/{1}".format(os.path.dirname(os.path.realpath(__file__)), vfile) + self.assertTrue(self.isdiff(vname, multi_golden)) + + one_golden = "{0}/golden/{1}".format(os.path.dirname(os.path.realpath(__file__)), v1bfile) + self.assertTrue(self.isdiff(v1bname, one_golden)) + + 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()) diff --git a/compiler/tests/Makefile b/compiler/tests/Makefile index f7bbc91b..24e9a526 100644 --- a/compiler/tests/Makefile +++ b/compiler/tests/Makefile @@ -72,6 +72,7 @@ BROKEN_STAMPS = \ sky130/23_lib_sram_prune_test.ok \ sky131/23_lib_sram_test.ok \ %/26_hspice_pex_pinv_test.ok \ + %/27_verilog_multibank_test.ok \ %/50_riscv_1k_1rw1r_func_test.ok \ %/50_riscv_1k_1rw_func_test.ok \ %/50_riscv_1rw1r_func_test.ok \ diff --git a/compiler/tests/golden/sram_2_16_2_freepdk45.v b/compiler/tests/golden/sram_2_16_2_freepdk45.v new file mode 100644 index 00000000..62484416 --- /dev/null +++ b/compiler/tests/golden/sram_2_16_2_freepdk45.v @@ -0,0 +1,73 @@ +// OpenRAM SRAM model +// Words: 16 +// Word size: 2 + +module sram_2_16_2_freepdk45( +`ifdef USE_POWER_PINS + vdd, + gnd, +`endif +// Port 0: RW + clk0,csb0,web0,addr0,din0,dout0 + ); + + parameter DATA_WIDTH = 2 ; + parameter ADDR_WIDTH = 3 ; + parameter RAM_DEPTH = 1 << ADDR_WIDTH; + // FIXME: This delay is arbitrary. + parameter DELAY = 3 ; + parameter VERBOSE = 1 ; //Set to 0 to only display warnings + parameter T_HOLD = 1 ; //Delay to hold dout value after posedge. Value is arbitrary + +`ifdef USE_POWER_PINS + inout vdd; + inout gnd; +`endif + input clk0; // clock + input csb0; // active low chip select + input web0; // active low write control + input [ADDR_WIDTH-1:0] addr0; + input [DATA_WIDTH-1:0] din0; + output [DATA_WIDTH-1:0] dout0; + + reg [DATA_WIDTH-1:0] mem [0:RAM_DEPTH-1]; + + reg csb0_reg; + reg web0_reg; + reg [ADDR_WIDTH-1:0] addr0_reg; + reg [DATA_WIDTH-1:0] din0_reg; + reg [DATA_WIDTH-1:0] dout0; + + // All inputs are registers + always @(posedge clk0) + begin + csb0_reg = csb0; + web0_reg = web0; + addr0_reg = addr0; + din0_reg = din0; + #(T_HOLD) dout0 = 2'bx; + if ( !csb0_reg && web0_reg && VERBOSE ) + $display($time," Reading %m addr0=%b dout0=%b",addr0_reg,mem[addr0_reg]); + if ( !csb0_reg && !web0_reg && VERBOSE ) + $display($time," Writing %m addr0=%b din0=%b",addr0_reg,din0_reg); + end + + + // Memory Write Block Port 0 + // Write Operation : When web0 = 0, csb0 = 0 + always @ (negedge clk0) + begin : MEM_WRITE0 + if ( !csb0_reg && !web0_reg ) begin + mem[addr0_reg][1:0] = din0_reg[1:0]; + end + end + + // Memory Read Block Port 0 + // Read Operation : When web0 = 1, csb0 = 0 + always @ (negedge clk0) + begin : MEM_READ0 + if (!csb0_reg && web0_reg) + dout0 <= #(DELAY) mem[addr0_reg]; + end + +endmodule diff --git a/compiler/tests/golden/sram_2_16_2_freepdk45_top.v b/compiler/tests/golden/sram_2_16_2_freepdk45_top.v new file mode 100644 index 00000000..1fdcde40 --- /dev/null +++ b/compiler/tests/golden/sram_2_16_2_freepdk45_top.v @@ -0,0 +1,105 @@ + +module sram_2_16_2_freepdk45_top ( +`ifdef USE_POWER_PINS + vdd, + gnd, +`endif + clk0, + addr0, + din0, + csb0, + web0, + dout0 + ); + + parameter DATA_WIDTH = 2; + parameter ADDR_WIDTH= 4; + + parameter BANK_SEL = 1; + parameter NUM_WMASK = 0; + +`ifdef USE_POWER_PINS + inout vdd; + inout gnd; +`endif + input clk0; + input [ADDR_WIDTH - 1 : 0] addr0; + input [DATA_WIDTH - 1: 0] din0; + input csb0; + input web0; + output reg [DATA_WIDTH - 1 : 0] dout0; + + reg [BANK_SEL - 1 : 0] addr0_reg; + + wire [DATA_WIDTH - 1 : 0] dout0_bank0; + + reg web0_bank0; + + reg csb0_bank0; + + wire [DATA_WIDTH - 1 : 0] dout0_bank1; + + reg web0_bank1; + + reg csb0_bank1; + + + sram_2_16_2_freepdk45 bank0 ( +`ifdef USE_POWER_PINS + .vdd(vdd), + .gnd(gnd), +`endif + .clk0(clk0), + .addr0(addr0[ADDR_WIDTH - BANK_SEL - 1 : 0]), + .din0(din0), + .csb0(csb0_bank0), + .web0(web0_bank0), + .dout0(dout0_bank0) + ); + sram_2_16_2_freepdk45 bank1 ( +`ifdef USE_POWER_PINS + .vdd(vdd), + .gnd(gnd), +`endif + .clk0(clk0), + .addr0(addr0[ADDR_WIDTH - BANK_SEL - 1 : 0]), + .din0(din0), + .csb0(csb0_bank1), + .web0(web0_bank1), + .dout0(dout0_bank1) + ); + + always @(posedge clk0) begin + addr0_reg <= addr0[ADDR_WIDTH - 1 : ADDR_WIDTH - BANK_SEL]; + end + + always @(*) begin + case (addr0_reg) + 0: begin + dout0 = dout0_bank0; + end + 1: begin + dout0 = dout0_bank1; + end + endcase + end + + always @(*) begin + csb0_bank0 = 1'b1; + web0_bank0 = 1'b1; + csb0_bank1 = 1'b1; + web0_bank1 = 1'b1; + case (addr0[ADDR_WIDTH - 1 : ADDR_WIDTH - BANK_SEL]) + 0: begin + web0_bank0 = web0; + csb0_bank0 = csb0; + end + 1: begin + web0_bank1 = web0; + csb0_bank1 = csb0; + end + endcase + end + + +endmodule diff --git a/compiler/tests/golden/sram_2_16_2_scn4m_subm.v b/compiler/tests/golden/sram_2_16_2_scn4m_subm.v new file mode 100644 index 00000000..93f818cd --- /dev/null +++ b/compiler/tests/golden/sram_2_16_2_scn4m_subm.v @@ -0,0 +1,73 @@ +// OpenRAM SRAM model +// Words: 16 +// Word size: 2 + +module sram_2_16_2_scn4m_subm( +`ifdef USE_POWER_PINS + vdd, + gnd, +`endif +// Port 0: RW + clk0,csb0,web0,addr0,din0,dout0 + ); + + parameter DATA_WIDTH = 2 ; + parameter ADDR_WIDTH = 3 ; + parameter RAM_DEPTH = 1 << ADDR_WIDTH; + // FIXME: This delay is arbitrary. + parameter DELAY = 3 ; + parameter VERBOSE = 1 ; //Set to 0 to only display warnings + parameter T_HOLD = 1 ; //Delay to hold dout value after posedge. Value is arbitrary + +`ifdef USE_POWER_PINS + inout vdd; + inout gnd; +`endif + input clk0; // clock + input csb0; // active low chip select + input web0; // active low write control + input [ADDR_WIDTH-1:0] addr0; + input [DATA_WIDTH-1:0] din0; + output [DATA_WIDTH-1:0] dout0; + + reg [DATA_WIDTH-1:0] mem [0:RAM_DEPTH-1]; + + reg csb0_reg; + reg web0_reg; + reg [ADDR_WIDTH-1:0] addr0_reg; + reg [DATA_WIDTH-1:0] din0_reg; + reg [DATA_WIDTH-1:0] dout0; + + // All inputs are registers + always @(posedge clk0) + begin + csb0_reg = csb0; + web0_reg = web0; + addr0_reg = addr0; + din0_reg = din0; + #(T_HOLD) dout0 = 2'bx; + if ( !csb0_reg && web0_reg && VERBOSE ) + $display($time," Reading %m addr0=%b dout0=%b",addr0_reg,mem[addr0_reg]); + if ( !csb0_reg && !web0_reg && VERBOSE ) + $display($time," Writing %m addr0=%b din0=%b",addr0_reg,din0_reg); + end + + + // Memory Write Block Port 0 + // Write Operation : When web0 = 0, csb0 = 0 + always @ (negedge clk0) + begin : MEM_WRITE0 + if ( !csb0_reg && !web0_reg ) begin + mem[addr0_reg][1:0] = din0_reg[1:0]; + end + end + + // Memory Read Block Port 0 + // Read Operation : When web0 = 1, csb0 = 0 + always @ (negedge clk0) + begin : MEM_READ0 + if (!csb0_reg && web0_reg) + dout0 <= #(DELAY) mem[addr0_reg]; + end + +endmodule diff --git a/compiler/tests/golden/sram_2_16_2_scn4m_subm_top.v b/compiler/tests/golden/sram_2_16_2_scn4m_subm_top.v new file mode 100644 index 00000000..e52084d9 --- /dev/null +++ b/compiler/tests/golden/sram_2_16_2_scn4m_subm_top.v @@ -0,0 +1,105 @@ + +module sram_2_16_2_scn4m_subm_top ( +`ifdef USE_POWER_PINS + vdd, + gnd, +`endif + clk0, + addr0, + din0, + csb0, + web0, + dout0 + ); + + parameter DATA_WIDTH = 2; + parameter ADDR_WIDTH= 4; + + parameter BANK_SEL = 1; + parameter NUM_WMASK = 0; + +`ifdef USE_POWER_PINS + inout vdd; + inout gnd; +`endif + input clk0; + input [ADDR_WIDTH - 1 : 0] addr0; + input [DATA_WIDTH - 1: 0] din0; + input csb0; + input web0; + output reg [DATA_WIDTH - 1 : 0] dout0; + + reg [BANK_SEL - 1 : 0] addr0_reg; + + wire [DATA_WIDTH - 1 : 0] dout0_bank0; + + reg web0_bank0; + + reg csb0_bank0; + + wire [DATA_WIDTH - 1 : 0] dout0_bank1; + + reg web0_bank1; + + reg csb0_bank1; + + + sram_2_16_2_scn4m_subm bank0 ( +`ifdef USE_POWER_PINS + .vdd(vdd), + .gnd(gnd), +`endif + .clk0(clk0), + .addr0(addr0[ADDR_WIDTH - BANK_SEL - 1 : 0]), + .din0(din0), + .csb0(csb0_bank0), + .web0(web0_bank0), + .dout0(dout0_bank0) + ); + sram_2_16_2_scn4m_subm bank1 ( +`ifdef USE_POWER_PINS + .vdd(vdd), + .gnd(gnd), +`endif + .clk0(clk0), + .addr0(addr0[ADDR_WIDTH - BANK_SEL - 1 : 0]), + .din0(din0), + .csb0(csb0_bank1), + .web0(web0_bank1), + .dout0(dout0_bank1) + ); + + always @(posedge clk0) begin + addr0_reg <= addr0[ADDR_WIDTH - 1 : ADDR_WIDTH - BANK_SEL]; + end + + always @(*) begin + case (addr0_reg) + 0: begin + dout0 = dout0_bank0; + end + 1: begin + dout0 = dout0_bank1; + end + endcase + end + + always @(*) begin + csb0_bank0 = 1'b1; + web0_bank0 = 1'b1; + csb0_bank1 = 1'b1; + web0_bank1 = 1'b1; + case (addr0[ADDR_WIDTH - 1 : ADDR_WIDTH - BANK_SEL]) + 0: begin + web0_bank0 = web0; + csb0_bank0 = csb0; + end + 1: begin + web0_bank1 = web0; + csb0_bank1 = csb0; + end + endcase + end + + +endmodule diff --git a/compiler/verify/magic.py b/compiler/verify/magic.py index 10b60d05..a3e0e511 100644 --- a/compiler/verify/magic.py +++ b/compiler/verify/magic.py @@ -26,7 +26,6 @@ import shutil import debug from globals import OPTS from .run_script import * - # Keep track of statistics num_drc_runs = 0 num_lvs_runs = 0 @@ -96,13 +95,24 @@ def write_drc_script(cell_name, gds_name, extract, final_verification, output_pa f.write("gds warning default\n") # Flatten the transistors # Bug in Netgen 1.5.194 when using this... - f.write("gds flatglob *_?mos_m*\n") + try: + from tech import blackbox_cells + except ImportError: + blackbox_cells = [] + + try: + from tech import flatglob + except ImportError: + flatglob = [] + f.write("gds readonly true\n") + + for entry in flatglob: + f.write("gds flatglob " +entry + "\n") # These two options are temporarily disabled until Tim fixes a bug in magic related # to flattening channel routes and vias (hierarchy with no devices in it). Otherwise, # they appear to be disconnected. f.write("gds flatten true\n") f.write("gds ordering true\n") - f.write("gds readonly true\n") f.write("gds read {}\n".format(gds_name)) f.write('puts "Finished reading gds {}"\n'.format(gds_name)) f.write("load {}\n".format(cell_name)) @@ -164,10 +174,6 @@ def write_drc_script(cell_name, gds_name, extract, final_verification, output_pa f.write("#!/bin/sh\n") f.write('export OPENRAM_TECH="{}"\n'.format(os.environ['OPENRAM_TECH'])) # Copy the bitcell mag files if they exist - try: - from tech import blackbox_cells - except ImportError: - blackbox_cells = [] for blackbox_cell_name in blackbox_cells: mag_file = OPTS.openram_tech + "maglef_lib/" + blackbox_cell_name + ".mag" debug.check(os.path.isfile(mag_file), "Could not find blackbox cell {}".format(mag_file)) diff --git a/docker/Dockerfile b/docker/Dockerfile index 8a9a9d79..fc165e19 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -11,6 +11,7 @@ RUN apt-get --no-install-recommends -y upgrade # General tools for building etc. RUN apt-get install --no-install-recommends -y build-essential git ssh vim gosu autoconf automake libtool bison flex # Use bash instead of dash +# Must be on one line or else ln won't work without a shell! RUN rm /bin/sh && ln -s /bin/bash /bin/sh # Needed by OpenRAM RUN apt-get install --no-install-recommends -y python3 python3-numpy python3-scipy python3-pip python3-matplotlib python3-venv python3-sklearn python3-subunit python3-coverage @@ -30,8 +31,8 @@ WORKDIR /root RUN git clone https://github.com/KLayout/klayout WORKDIR /root/klayout RUN git checkout ${KLAYOUT_COMMIT} -RUN ./build.sh -qt5 -debug -j 8 \ - && cp -r bin-debug /usr/local/klayout +RUN ./build.sh -qt5 -debug -j$(nproc) +RUN cp -r bin-debug /usr/local/klayout RUN rm -rf /root/klayout ### Trilinos ### @@ -99,15 +100,15 @@ RUN ../configure CXXFLAGS="-O3 -std=c++11" \ RUN make -j 4 install ### Ngspice ### -ARG NGSPICE_COMIT=032b1c32c4dbad45ff132bcfac1dbecadbd8abb0 +ARG NGSPICE_COMMIT=032b1c32c4dbad45ff132bcfac1dbecadbd8abb0 WORKDIR /root RUN git clone git://git.code.sf.net/p/ngspice/ngspice WORKDIR /root/ngspice RUN git checkout ${NGSPICE_COMMIT} -RUN ./autogen.sh \ - && ./configure --enable-openmp --with-readline \ - && make \ - && make install +RUN ./autogen.sh +RUN ./configure --enable-openmp --with-readline +RUN make +RUN make install RUN rm -rf /root/ngspice ### Netgen ### @@ -118,9 +119,9 @@ WORKDIR /root RUN git clone git://opencircuitdesign.com/netgen netgen WORKDIR /root/netgen RUN git checkout ${NETGEN_COMMIT} -RUN ./configure \ - && make -j$(nproc) \ - && make install +RUN ./configure +RUN make -j$(nproc) +RUN make install RUN rm -rf /root/netgen ### iVerilog ### @@ -137,9 +138,9 @@ WORKDIR /root/magic RUN git checkout ${MAGIC_COMMIT} COPY mrg.patch /root/magic RUN git apply mrg.patch -RUN ./configure \ - && make \ - && make install +RUN ./configure +RUN make +RUN make install RUN rm -rf /root/magic diff --git a/technology/sky130/custom/sky130_replica_column.py b/technology/sky130/custom/sky130_replica_column.py index 8997d4f2..0425160d 100644 --- a/technology/sky130/custom/sky130_replica_column.py +++ b/technology/sky130/custom/sky130_replica_column.py @@ -90,8 +90,8 @@ class sky130_replica_column(sky130_bitcell_base_array): self.add_pin("vdd", "POWER") self.add_pin("gnd", "GROUND") - self.add_pin("top_gate", "INPUT") - self.add_pin("bot_gate", "INPUT") + #self.add_pin("top_gate", "INPUT") + #self.add_pin("bot_gate", "INPUT") def add_modules(self): self.replica_cell = factory.create(module_type="replica_bitcell_1port", version="opt1") @@ -220,6 +220,16 @@ class sky130_replica_column(sky130_bitcell_base_array): width=self.width, height=wl_pin.height()) + # for colend in [self.cell_inst[0], self.cell_inst[self.row_size]]: + # inst = self.cell_inst[row] + # for pin_name in ["top_gate", "bot_gate"]: + # pin = inst.get_pin("gate") + # self.add_layout_pin(text=pin_name, + # layer=pin.layer, + # offset=pin.ll(), + # width=pin.width(), + # height=pin.height()) + for row in range(self.row_size + 2): inst = self.cell_inst[row] # add only 1 label per col @@ -235,7 +245,6 @@ class sky130_replica_column(sky130_bitcell_base_array): self.objs.append(geometry.label("vdd", layer["nwell"], pin.center())) if 'VNB' or 'vnb' in self.cell_inst[row].mod.pins: - print("welling") try: from tech import layer_override if layer_override['VNB']: diff --git a/technology/sky130/tech/tech.py b/technology/sky130/tech/tech.py index 8eea88f1..6957530b 100644 --- a/technology/sky130/tech/tech.py +++ b/technology/sky130/tech/tech.py @@ -781,6 +781,16 @@ library_prefix_name = "sky130_fd_bd_sram__" # List of cells to skip running DRC/LVS on directly # This will look for a maglef file and copy it over the mag file # before DRC after extraction + +flatglob = ["*_?mos_m*", + "sky130_fd_bd_sram__sram_sp_cell_fom_serifs", + "sky130_fd_bd_sram__openram_sp_cell_opt1a_cell", + "sky130_fd_bd_sram__openram_sp_cell_opt1a_replica_ce", + "sky130_fd_bd_sram__openram_sp_cell_opt1_replica_cell", + "sky130_fd_bd_sram__openram_sp_cell_opt1_replica_ce", + "sky130_fd_bd_sram__openram_sp_cell_opt1a_cell", + "sky130_fd_bd_sram__sram_sp_cell_fom_serifs"] + blackbox_cells = ["sky130_fd_bd_sram__openram_dp_cell", "sky130_fd_bd_sram__openram_dp_cell_dummy", "sky130_fd_bd_sram__openram_dp_cell_replica",