From 3ee769a771b673846d172a6f8fc3b4d0e88c408f Mon Sep 17 00:00:00 2001 From: FriedrichWu Date: Fri, 21 Jun 2024 13:48:42 +0200 Subject: [PATCH 01/26] first commit change one sram into several single macros --- compiler/gdsMill/gdsMill/vlsiLayout.py | 3 + compiler/modules/sram_part.py | 2589 +++++++++++++++++++++++ compiler/router/signal_escape_router.py | 60 +- compiler/router/supply_router.py | 2 + compiler/sram.py | 2 +- compiler/sram_road.py | 253 +++ sram_compiler.py | 7 +- 7 files changed, 2909 insertions(+), 7 deletions(-) create mode 100644 compiler/modules/sram_part.py create mode 100644 compiler/sram_road.py diff --git a/compiler/gdsMill/gdsMill/vlsiLayout.py b/compiler/gdsMill/gdsMill/vlsiLayout.py index b87aea776..5dec6a803 100644 --- a/compiler/gdsMill/gdsMill/vlsiLayout.py +++ b/compiler/gdsMill/gdsMill/vlsiLayout.py @@ -742,6 +742,9 @@ def getAllPinShapes(self, pin_name): Search for a pin label and return ALL the enclosing rectangles on the same layer as the pin label. """ + #debug + for pin in self.pins: + print(pin) shape_list = [] pin_map = self.pins[pin_name] for pin_list in pin_map: diff --git a/compiler/modules/sram_part.py b/compiler/modules/sram_part.py new file mode 100644 index 000000000..5d374940a --- /dev/null +++ b/compiler/modules/sram_part.py @@ -0,0 +1,2589 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2024 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. +# + +# This file aims to use openroad to do the routing +import datetime +from math import ceil +from importlib import import_module, reload +from openram import debug +from openram.base import vector +from openram.base import channel_route +from openram.base import design +from openram.base import verilog +from openram.base import lef +from openram.sram_factory import factory +from openram.tech import spice +from openram import OPTS, print_time + + +class sram_1bank(design, verilog, lef): + """ + Procedures specific to a one bank SRAM. + """ + def __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 openram.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 + + # delay control logic does not have RBLs + self.has_rbl = OPTS.control_logic != "control_logic_delay" + + def add_pin_bank(self): + # do not now why need this + """ Adding pins for Bank module""" + 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") + for port in self.all_ports: + if self.has_rbl: + self.add_pin("rbl_bl_{0}_{0}".format(port), "OUTPUT") + 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") + + # For more than one bank, we have a bank select and name + # the signals gated_*. + for port in self.read_ports: + self.add_pin("s_en{0}".format(port), "INPUT") + for port in self.all_ports: + self.add_pin("p_en_bar{0}".format(port), "INPUT") + for port in self.write_ports: + self.add_pin("w_en{0}".format(port), "INPUT") + for bit in range(self.num_wmasks): + self.add_pin("bank_wmask{0}_{1}".format(port, bit), "INPUT") + for bit in range(self.num_spare_cols): + self.add_pin("bank_spare_wen{0}_{1}".format(port, bit), "INPUT") + for port in self.all_ports: + self.add_pin("wl_en{0}".format(port), "INPUT") + + # 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_pin_row_addr_dff(self): + # do not know why we need this + for port in self.all_ports: + for bit in range(self.row_addr_size): + #input + self.add_pin("addr{}[{}]".format(port, bit + self.col_addr_size), "INPUT") + #output + self.add_pin("a{}_{}".format(port, bit + self.col_addr_size), "OUTPUT") + #clk_buf, regard as input + self.add_pin("clk_buf{}".format(port), "INPUT") + # 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_pin_col_addr_dff(self): + # do not know why we need this + for port in self.all_ports: + for bit in range(self.col_addr_size): + #input + self.add_pin("addr{}[{}]".format(port, bit), "INPUT") + #output + self.add_pin("a{}_{}".format(port, bit), "OUTPUT") + #clk_buf, regard as input + self.add_pin("clk_buf{}".format(port), "INPUT") + # 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_pin_data_dff(self): + # do not know why we need this + for port in self.all_ports: + for bit in range(self.word_size + self.num_spare_cols): + # input + self.add_pin("din{}[{}]".format(port, bit), "INPUT") + # output + self.add_pin("bank_din{}_{}".format(port, bit), "OUTPUT") + #clk_buf, regard as input + self.add_pin("clk_buf{}".format(port), "INPUT") + # 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_pin_wmask_dff(self): + # do not know why we need this + for port in self.all_ports: + for bit in range(self.num_wmasks): + # input + self.add_pin("wmask{}[{}]".format(port, bit), "INPUT") + # output + self.add_pin("bank_wmask{}_{}".format(port, bit), "OUTPUT") + #clk_buf, regard as input + self.add_pin("clk_buf{}".format(port), "INPUT") + # 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_pin_spare_wen_dff(self): + # do not know why we need this + for port in self.all_ports: + for bit in range(self.num_spare_cols): + # input + self.add_pin("spare_wen{}[{}]".format(port, bit), "INPUT") + # output + self.add_pin("bank_spare_wen{}_{}".format(port, bit), "OUTPUT") + #clk_buf, regard as input + self.add_pin("clk_buf{}".format(port), "INPUT") + # 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_pin_control(self): + #Do not know why we need this + for port in self.all_ports: + # Inputs + self.add_pin("csb{}".format(port), "INPUT") + if port in self.readwrite_ports: + self.add_pin("web{}".format(port), "INPUT") + self.add_pin("clk{}".format(port), "INPUT") + if self.has_rbl: + self.add_pin("rbl_bl{}".format(port), "INPUT") + # Outputs + if port in self.read_ports: + self.add_pin("s_en{}".format(port), "OUTPUT") + if port in self.write_ports: + self.add_pin("w_en{}".format(port), "OUTPUT") + self.add_pin("p_en_bar{}".format(port), "OUTPUT") + self.add_pin("wl_en{}".format(port), "OUTPUT") + self.add_pin("clk_buf{}".format(port), "OUTPUT") + + # Standard supply and ground names + # not sure if this part should stay inside the for loop + 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_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_netlist_bank(self): + """ Netlist creation """ + + start_time = datetime.datetime.now() + + # Must create the control logic before pins to get the pins + self.add_modules() + self.add_pin_bank() + self.bank_inst = self.create_bank(0) + + # 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 Bank", datetime.datetime.now(), start_time) + + def create_netlist_control(self): + """ Netlist creation """ + + start_time = datetime.datetime.now() + + # Must create the control logic before pins to get the pins + self.add_modules() + self.add_pin_control() + self.control_logic_insts = self.create_control_logic() # be aware, multi-insts possible + + # 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 Control", datetime.datetime.now(), start_time) + + def create_netlist_row_addr_dff(self): + """ Netlist creation """ + + start_time = datetime.datetime.now() + + # Must create the control logic before pins to get the pins + self.add_modules() + self.add_pin_row_addr_dff() + self.row_addr_dff_insts = self.create_row_addr_dff() # be aware, multi-insts possible + + # 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 row_addr_dff", datetime.datetime.now(), start_time) + + def create_netlist_col_addr_dff(self): + """ Netlist creation """ + + start_time = datetime.datetime.now() + + # Must create the control logic before pins to get the pins + self.add_modules() + if self.col_addr_dff: + self.add_pin_col_addr_dff() + self.col_addr_dff_insts = self.create_col_addr_dff() # be aware, multi-insts possible, or none + + # 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 col_addr_dff", datetime.datetime.now(), start_time) + else: + return False # no col_addr_dff will be generated + + def create_netlist_data_dff(self): + """ Netlist creation """ + + start_time = datetime.datetime.now() + + # Must create the control logic before pins to get the pins + self.add_modules() + self.add_pin_data_dff() + self.bank_inst = self.create_col_addr_dff() # be aware, multi-insts possible + + # 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 data dff", datetime.datetime.now(), start_time) + + def create_netlist_wmask_dff(self): + """ Netlist creation """ + + start_time = datetime.datetime.now() + + # Must create the control logic before pins to get the pins + self.add_modules() + if self.write_size != self.word_size: + self.add_pin_wmask_dff() + self.bank_inst = self.create_wmask_dff() # be aware, multi-insts possible, or none + + # 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 wmask dff", datetime.datetime.now(), start_time) + else: + return False # no wmask_dff will be generated + + def create_netlist_spare_wen_dff(self): + """ Netlist creation """ + + start_time = datetime.datetime.now() + + # Must create the control logic before pins to get the pins + self.add_modules() + if self.num_spare_cols: + self.add_pin_spare_wen_dff() + self.spare_wen_dff_insts = self.create_spare_wen_dff() # be aware, multi-insts possible, or none + + # 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 spare wen dff", datetime.datetime.now(), start_time) + else: + self.num_spare_cols = 0 + return False # no spare_col will be generated + + 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_layout_bank_only(self): + start_time = datetime.datetime.now() + #Placement + self.place_bank(self.bank_inst, [0, 0], 1, 1) + if not OPTS.is_unit_test: + print_time("Bank Placement", datetime.datetime.now(), start_time) + start_time = datetime.datetime.now() + self.route_bank_only() + + 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_layout_control_only(self, instance_index=0): + start_time = datetime.datetime.now() + # Placement + # We will generate different layout for different port(if there are multi-port) + port = instance_index + self.control_pos[port] = vector(0,0) + self.control_logic_insts[port].place(self.control_pos[port]) + + if not OPTS.is_unit_test: + print_time("Control Placement", datetime.datetime.now(), start_time) + start_time = datetime.datetime.now() + self.route_control_only(instance_index=instance_index) + + 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_layout_row_addr_dff_only(self, instance_index=0): + start_time = datetime.datetime.now() + # Placement + # We will generate different layout for different port(if there are multi-port) + port = instance_index + self.row_addr_pos[port] = vector(0,0) + self.row_addr_dff_insts[port].place(self.row_addr_pos[port]) + + if not OPTS.is_unit_test: + print_time("row_addr_dff Placement", datetime.datetime.now(), start_time) + start_time = datetime.datetime.now() + self.route_row_addr_dff_only(instance_index=instance_index) + + 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_layout_col_addr_dff_only(self, instance_index=0): + start_time = datetime.datetime.now() + # Placement + # We will generate different layout for different port(if there are multi-port) + port = instance_index + self.col_addr_pos[port] = vector(0,0) + self.col_addr_dff_insts[port].place(self.col_addr_pos[port]) + + if not OPTS.is_unit_test: + print_time("col_addr_dff Placement", datetime.datetime.now(), start_time) + start_time = datetime.datetime.now() + self.route_col_addr_dff_only(instance_index=instance_index) + + 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_layout_data_dff_only(self, instance_index=0): + start_time = datetime.datetime.now() + # Placement + # We will generate different layout for different port(if there are multi-port) + port = instance_index + self.data_pos[port] = vector(0,0) + self.data_dff_insts[port].place(self.data_pos[port]) + + if not OPTS.is_unit_test: + print_time("data_dff Placement", datetime.datetime.now(), start_time) + start_time = datetime.datetime.now() + self.route_data_dff_only(instance_index=instance_index) + + 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_layout_wmask_dff_only(self, instance_index=0): + start_time = datetime.datetime.now() + # Placement + # We will generate different layout for different port(if there are multi-port) + port = instance_index + self.wmask_pos[port] = vector(0,0) + self.wmask_dff_insts[port].place(self.wmask_pos[port]) + + if not OPTS.is_unit_test: + print_time("wmask_dff Placement", datetime.datetime.now(), start_time) + start_time = datetime.datetime.now() + self.route_wmask_dff_only(instance_index=instance_index) + + 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_layout_spare_wen_dff_only(self, instance_index=0): + start_time = datetime.datetime.now() + # Placement + # We will generate different layout for different port(if there are multi-port) + port = instance_index + self.spare_wen_pos[port] = vector(0,0) + self.spare_wen_dff_insts[port].place(self.spare_wen_pos[port]) + + if not OPTS.is_unit_test: + print_time("spare_wen_dff Placement", datetime.datetime.now(), start_time) + start_time = datetime.datetime.now() + self.route_spare_wen_dff_only(instance_index=instance_index) + + 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 route_bank_only(self): + # We add the vias to M3 before routing supplies because + # they might create some blockages + self.add_layout_bank_only_pins() + pins_to_route_bank = self.add_pins_to_route_bank() + print("====================================================") + for pin in self.pins: + print(pin) + + # Some technologies have an isolation + self.add_dnwell(inflate=2.5) + + init_bbox = self.get_bbox() + # Route the supplies together and/or to the ring/stripes. + # Route the pins to the perimeter + if OPTS.perimeter_pins: + # We now route the escape routes far enough out so that they will + # reach past the power ring or stripes on the sides + from openram.router import signal_escape_router as router + rtr = router(layers=self.m3_stack, + bbox=init_bbox, + design=self) + rtr.route(pins_to_route_bank) + if OPTS.route_supplies: + self.route_supplies(init_bbox) + + def route_control_only(self, instance_index=0): + # We add the vias to M3 before routing supplies because + # they might create some blockages + self.add_layout_control_only_pins(instance_index=instance_index) + pins_to_route_control = self.add_pins_to_route_control(instance_index=instance_index) + print("====================================================") + for pin in self.pins: + print(pin) + + # Some technologies have an isolation + self.add_dnwell(inflate=2.5) + + init_bbox = self.get_bbox() + # Route the supplies together and/or to the ring/stripes. + # Route the pins to the perimeter + if OPTS.perimeter_pins: + # We now route the escape routes far enough out so that they will + # reach past the power ring or stripes on the sides + from openram.router import signal_escape_router as router + rtr = router(layers=self.m3_stack, + bbox=init_bbox, + design=self) + rtr.route(pins_to_route_control) + if OPTS.route_supplies: + self.route_supplies(init_bbox) + + def route_row_addr_dff_only(self, instance_index=0): + # We add the vias to M3 before routing supplies because + # they might create some blockages + self.add_layout_row_addr_dff_only_pins(instance_index=instance_index) + pins_to_route_row_addr_dff = self.add_pins_to_route_row_addr_dff(instance_index=instance_index) + print("====================================================") + for pin in self.pins: + print(pin) + + # Some technologies have an isolation + self.add_dnwell(inflate=2.5) + + init_bbox = self.get_bbox() + # Route the supplies together and/or to the ring/stripes. + # Route the pins to the perimeter + if OPTS.perimeter_pins: + # We now route the escape routes far enough out so that they will + # reach past the power ring or stripes on the sides + from openram.router import signal_escape_router as router + rtr = router(layers=self.m3_stack, + bbox=init_bbox, + design=self) + rtr.route(pins_to_route_row_addr_dff) + if OPTS.route_supplies: + self.route_supplies(init_bbox) + + def route_col_addr_dff_only(self, instance_index=0): + # We add the vias to M3 before routing supplies because + # they might create some blockages + self.add_layout_col_addr_dff_only_pins(instance_index=instance_index) + pins_to_route_col_addr_dff = self.add_pins_to_route_col_addr_dff(instance_index=instance_index) + print("====================================================") + for pin in self.pins: + print(pin) + + # Some technologies have an isolation + self.add_dnwell(inflate=2.5) + + init_bbox = self.get_bbox() + # Route the supplies together and/or to the ring/stripes. + # Route the pins to the perimeter + if OPTS.perimeter_pins: + # We now route the escape routes far enough out so that they will + # reach past the power ring or stripes on the sides + from openram.router import signal_escape_router as router + rtr = router(layers=self.m3_stack, + bbox=init_bbox, + design=self) + rtr.route(pins_to_route_col_addr_dff) + if OPTS.route_supplies: + self.route_supplies(init_bbox) + + def route_data_dff_only(self, instance_index=0): + # We add the vias to M3 before routing supplies because + # they might create some blockages + self.add_layout_data_dff_only_pins(instance_index=instance_index) + pins_to_route_data_dff = self.add_pins_to_route_data_dff(instance_index=instance_index) + print("====================================================") + for pin in self.pins: + print(pin) + + # Some technologies have an isolation + self.add_dnwell(inflate=2.5) + + init_bbox = self.get_bbox() + # Route the supplies together and/or to the ring/stripes. + # Route the pins to the perimeter + if OPTS.perimeter_pins: + # We now route the escape routes far enough out so that they will + # reach past the power ring or stripes on the sides + from openram.router import signal_escape_router as router + rtr = router(layers=self.m3_stack, + bbox=init_bbox, + design=self) + rtr.route(pins_to_route_data_dff) + if OPTS.route_supplies: + self.route_supplies(init_bbox) + + def route_wmask_dff_only(self, instance_index=0): + # We add the vias to M3 before routing supplies because + # they might create some blockages + self.add_layout_wmask_dff_only_pins(instance_index=instance_index) + pins_to_route_wmask_dff = self.add_pins_to_route_wmask_dff(instance_index=instance_index) + print("====================================================") + for pin in self.pins: + print(pin) + + # Some technologies have an isolation + self.add_dnwell(inflate=2.5) + + init_bbox = self.get_bbox() + # Route the supplies together and/or to the ring/stripes. + # Route the pins to the perimeter + if OPTS.perimeter_pins: + # We now route the escape routes far enough out so that they will + # reach past the power ring or stripes on the sides + from openram.router import signal_escape_router as router + rtr = router(layers=self.m3_stack, + bbox=init_bbox, + design=self) + rtr.route(pins_to_route_wmask_dff) + if OPTS.route_supplies: + self.route_supplies(init_bbox) + + def route_spare_wen_dff_only(self, instance_index=0): + # We add the vias to M3 before routing supplies because + # they might create some blockages + self.add_layout_spare_wen_dff_only_pins(instance_index=instance_index) + pins_to_route_spare_wen_dff = self.add_pins_to_route_spare_wen_dff(instance_index=instance_index) + print("====================================================") + for pin in self.pins: + print(pin) + + # Some technologies have an isolation + self.add_dnwell(inflate=2.5) + + init_bbox = self.get_bbox() + # Route the supplies together and/or to the ring/stripes. + # Route the pins to the perimeter + if OPTS.perimeter_pins: + # We now route the escape routes far enough out so that they will + # reach past the power ring or stripes on the sides + from openram.router import signal_escape_router as router + rtr = router(layers=self.m3_stack, + bbox=init_bbox, + design=self) + rtr.route(pins_to_route_spare_wen_dff) + if OPTS.route_supplies: + self.route_supplies(init_bbox) + + def add_layout_spare_wen_dff_only_pins(self, instance_index=0): + #only contains signal pins, no power pins + """ Adding to route pins for spare wen dff """ + pins_to_route = [] + port = instance_index + + for bit in range(self.num_spare_cols): + # input + pins_to_route.append("spare_wen{}[{}]".format(port, bit)) + # output + pins_to_route.append("bank_spare_wen{}_{}".format(port, bit)) + #clk_buf, regard as input + pins_to_route.append("clk_buf{}".format(port)) + + return pins_to_route + + def add_pins_to_route_spare_wen_dff(self, add_vias=True, instance_index=0): + """ + Add the top-level pins for a single bank SRAM with control. + """ + port = instance_index + # Hack: If we are escape routing, set the pin layer to + # None so that we will start from the pin layer + # Otherwise, set it as the pin layer so that no vias are added. + # Otherwise, when we remove pins to move the dff array dynamically, + # we will leave some remaining vias when the pin locations change. + if add_vias: + pin_layer = None + else: + pin_layer = self.pwr_grid_layers[0] + + for bit in range(self.num_spare_cols): + # input + self.add_io_pin(self.spare_wen_dff_insts[port], + "spare_wen{}[{}]".format(port, bit), # old name + "spare_wen{}[{}]".format(port, bit), # new name + start_layer=pin_layer) + # output + self.add_io_pin(self.spare_wen_dff_insts[port], + "bank_spare_wen{}_{}".format(port, bit), + "bank_spare_wen{}_{}".format(port, bit), + start_layer=pin_layer) + #clk_buf, regard as input + self.add_io_pin(self.spare_wen_dff_insts[port], + "clk_buf{}".format(port), + "clk_buf{}".format(port), + start_layer=pin_layer) + + def add_pins_to_route_wmask_dff(self, instance_index=0): + #only contains signal pins, no power pins + """ Adding to route pins for wmask addr dff """ + pins_to_route = [] + port = instance_index + for bit in range(self.num_wmasks): + # input + pins_to_route.append("wmask{}[{}]".format(port, bit)) + # output + pins_to_route.append("bank_wmask{}_{}".format(port, bit)) + #clk_buf, regard as input + pins_to_route.append("clk_buf{}".format(port)) + + return pins_to_route + + def add_layout_wmask_dff_only_pins(self, add_vias=True, instance_index=0): + """ + Add the top-level pins for a single bank SRAM with control. + """ + port = instance_index + # Hack: If we are escape routing, set the pin layer to + # None so that we will start from the pin layer + # Otherwise, set it as the pin layer so that no vias are added. + # Otherwise, when we remove pins to move the dff array dynamically, + # we will leave some remaining vias when the pin locations change. + if add_vias: + pin_layer = None + else: + pin_layer = self.pwr_grid_layers[0] + + for bit in range(self.num_wmasks): + # input + self.add_io_pin(self.wmask_dff_insts[port], + "wmask{}[{}]".format(port, bit), + "wmask{}[{}]".format(port, bit), + start_layer=pin_layer) + # output + self.add_io_pin(self.wmask_dff_insts[port], + "bank_wmask{}_{}".format(port, bit), + "bank_wmask{}_{}".format(port, bit), + start_layer=pin_layer) + #clk_buf, regard as input + self.add_io_pin(self.wmask_dff_insts[port], + "clk_buf{}".format(port), + "clk_buf{}".format(port), + start_layer=pin_layer) + + def add_pins_to_route_data_dff(self, instance_index=0): + #only contains signal pins, no power pins + """ Adding to route pins for col addr dff """ + pins_to_route = [] + port = instance_index + for bit in range(self.word_size + self.num_spare_cols): + # input + pins_to_route.append("din{}[{}]".format(port, bit)) + # output + pins_to_route.append("bank_din{}_{}".format(port, bit)) + #clk_buf, regard as input + pins_to_route.append("clk_buf{}".format(port)) + + return pins_to_route + + def add_layout_data_dff_only_pins(self, add_vias=True, instance_index=0): + """ + Add the top-level pins for a single bank SRAM with control. + """ + port = instance_index + # Hack: If we are escape routing, set the pin layer to + # None so that we will start from the pin layer + # Otherwise, set it as the pin layer so that no vias are added. + # Otherwise, when we remove pins to move the dff array dynamically, + # we will leave some remaining vias when the pin locations change. + if add_vias: + pin_layer = None + else: + pin_layer = self.pwr_grid_layers[0] + + for bit in range(self.word_size + self.num_spare_cols): + # input + self.add_io_pin(self.data_dff_insts[port], + "din{}[{}]".format(port, bit), + "din{}[{}]".format(port, bit), + start_layer=pin_layer) + # output + self.add_io_pin(self.data_dff_insts[port], + "bank_din{}_{}".format(port, bit), + "bank_din{}_{}".format(port, bit), + start_layer=pin_layer) + #clk_buf, regard as input + self.add_io_pin(self.data_dff_insts[port], + "clk_buf{}".format(port), + "clk_buf{}".format(port), + start_layer=pin_layer) + + def add_pins_to_route_col_addr_dff(self, instance_index=0): + #only contains signal pins, no power pins + """ Adding to route pins for col addr dff """ + pins_to_route = [] + port = instance_index + for bit in range(self.col_addr_size): + #input + pins_to_route.append("addr{}[{}]".format(port, bit)) + #output + pins_to_route.append("a{}_{}".format(port, bit)) + #clk_buf, regard as input + pins_to_route.append("clk_buf{}".format(port)) + + return pins_to_route + + def add_layout_col_addr_dff_only_pins(self, add_vias=True, instance_index=0): + """ + Add the top-level pins for a single bank SRAM with control. + """ + port = instance_index + # Hack: If we are escape routing, set the pin layer to + # None so that we will start from the pin layer + # Otherwise, set it as the pin layer so that no vias are added. + # Otherwise, when we remove pins to move the dff array dynamically, + # we will leave some remaining vias when the pin locations change. + if add_vias: + pin_layer = None + else: + pin_layer = self.pwr_grid_layers[0] + + for bit in range(self.col_addr_size): + #input + self.add_io_pin(self.col_addr_dff_insts[port], + "addr{}[{}]".format(port, bit), # old name + "addr{}[{}]".format(port, bit), # new name + start_layer=pin_layer) + #output + self.add_io_pin(self.col_addr_dff_insts[port], + "a{}_{}".format(port, bit), + "a{}_{}".format(port, bit), + start_layer=pin_layer) + #clk_buf, regard as input + self.add_pin(self.col_addr_dff_insts[port], + "clk_buf{}".format(port), + "clk_buf{}".format(port), + start_layer=pin_layer) + + def add_pins_to_route_row_addr_dff(self, instance_index=0): + #only contains signal pins, no power pins + """ Adding to route pins for row addr dff """ + pins_to_route = [] + port = instance_index + for bit in range(self.row_addr_size): + #input + pins_to_route.append("addr{}[{}]".format(port, bit + self.col_addr_size)) + #output + pins_to_route.append("a{}_{}".format(port, bit + self.col_addr_size)) + #clk_buf, regard as input + pins_to_route.append("clk_buf{}".format(port)) + + return pins_to_route + + def add_layout_row_addr_dff_only_pins(self, add_vias=True, instance_index=0): + """ + Add the top-level pins for a single bank SRAM with control. + """ + port = instance_index + # Hack: If we are escape routing, set the pin layer to + # None so that we will start from the pin layer + # Otherwise, set it as the pin layer so that no vias are added. + # Otherwise, when we remove pins to move the dff array dynamically, + # we will leave some remaining vias when the pin locations change. + if add_vias: + pin_layer = None + else: + pin_layer = self.pwr_grid_layers[0] + + for bit in range(self.row_addr_size): + #input + self.add_io_pin(self.row_addr_dff_insts[port], + "addr{}[{}]".format(port, bit + self.col_addr_size), # old name + "addr{}[{}]".format(port, bit + self.col_addr_size), # new name + start_layer=pin_layer) + #output + self.add_io_pin(self.row_addr_dff_insts[port], + "a{}_{}".format(port, bit + self.col_addr_size), + "a{}_{}".format(port, bit + self.col_addr_size), + start_layer=pin_layer) + #clk_buf, regard as input + self.add_io_pin(self.row_addr_dff_insts[port], + "clk_buf{}".format(port), + "clk_buf{}".format(port), + start_layer=pin_layer) + + def add_pins_to_route_control(self, instance_index=0): + #only contains signal pins, no power pins + """ Adding to route pins for control """ + pins_to_route = [] + port = instance_index + # Inputs + pins_to_route.append("csb{}".format(port)) + if port in self.readwrite_ports: + pins_to_route.append("web{}".format(port)), + pins_to_route.append("clk{}".format(port)) + if self.has_rbl: + pins_to_route.append("rbl_bl{}".format(port)) + # Outputs + if port in self.read_ports: + pins_to_route.append("s_en{}".format(port)) + if port in self.write_ports: + pins_to_route.append("w_en{}".format(port)) + pins_to_route.append("p_en_bar{}".format(port)) + pins_to_route.append("wl_en{}".format(port)) + pins_to_route.append("clk_buf{}".format(port)) + + return pins_to_route + + def add_layout_control_only_pins(self, add_vias=True, instance_index=0): + """ + Add the top-level pins for a single bank SRAM with control. + """ + port = instance_index + # Hack: If we are escape routing, set the pin layer to + # None so that we will start from the pin layer + # Otherwise, set it as the pin layer so that no vias are added. + # Otherwise, when we remove pins to move the dff array dynamically, + # we will leave some remaining vias when the pin locations change. + if add_vias: + pin_layer = None + else: + pin_layer = self.pwr_grid_layers[0] + + # Inputs + self.add_io_pin(self.control_insts[port], + "csb{}".format(port), #old name + "csb{}".format(port), #new name + start_layer=pin_layer) + if port in self.readwrite_ports: + self.add_io_pin(self.control_insts[port], + "web{}".format(port), + "web{}".format(port), + start_layer=pin_layer) + self.add_io_pin(self.control_insts[port], + "clk{}".format(port), + "clk{}".format(port), + start_layer=pin_layer) + if self.has_rbl: + self.add_io_pin(self.control_insts[port], + "rbl_bl{}".format(port), + "rbl_bl{}".format(port), + start_layer=pin_layer) + # Outputs + if port in self.read_ports: + self.add_io_pin(self.control_insts[port], + "s_en{}".format(port), + "s_en{}".format(port), + start_layer=pin_layer) + if port in self.write_ports: + self.add_io_pin(self.control_insts[port], + "w_en{}".format(port), + "w_en{}".format(port), + start_layer=pin_layer) + self.add_io_pin(self.control_insts[port], + "p_en_bar{}".format(port), + "p_en_bar{}".format(port), + start_layer=pin_layer) + self.add_io_pin(self.control_insts[port], + "wl_en{}".format(port), + "wl_en{}".format(port), + start_layer=pin_layer) + self.add_io_pin(self.control_insts[port], + "clk_buf{}".format(port), + "clk_buf{}".format(port), + start_layer=pin_layer) + + def add_pins_to_route_bank(self): + #only contains signal pins, no power pins + """ Adding to route pins for Bank """ + pins_to_route = [] + for 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 port in self.all_ports: + if self.has_rbl: + pins_to_route.append("rbl_bl{0}".format(port)) + for port in self.write_ports: + for bit in range(self.word_size + self.num_spare_cols): + pins_to_route.append("bank_din{0}_{1}".format(port, bit)) + for port in self.all_ports: + for bit in range(self.bank_addr_size): + pins_to_route.append("addr{0}_{1}".format(port, bit)) + + # For more than one bank, we have a bank select and name + # the signals gated_*. + for port in self.read_ports: + pins_to_route.append("s_en{0}".format(port)) + for port in self.all_ports: + pins_to_route.append("p_en_bar{0}".format(port)) + for port in self.write_ports: + pins_to_route.append("w_en{0}".format(port)) + for bit in range(self.num_wmasks): + pins_to_route.append("bank_wmask{0}_{1}".format(port, bit)) + for bit in range(self.num_spare_cols): + pins_to_route.append("bank_spare_wen{0}_{1}".format(port, bit)) + for port in self.all_ports: + pins_to_route.append("wl_en{0}".format(port)) + + return pins_to_route + + def add_layout_bank_only_pins(self, add_vias=True): + """ + Add the top-level pins for a single bank SRAM with control. + """ + # Hack: If we are escape routing, set the pin layer to + # None so that we will start from the pin layer + # Otherwise, set it as the pin layer so that no vias are added. + # Otherwise, when we remove pins to move the dff array dynamically, + # we will leave some remaining vias when the pin locations change. + if add_vias: + pin_layer = None + else: + pin_layer = self.pwr_grid_layers[0] + + for port in self.readwrite_ports or port in self.read_ports: + for bit in range(self.word_size + self.num_spare_cols): + self.add_io_pin(self.bank_inst, + "dout{0}_{1}".format(port, bit),#old name + "dout{0}[{1}]".format(port, bit),#new name + start_layer=pin_layer) + """ Adding pins for Bank module""" + for port in self.all_ports: + if self.has_rbl: + self.add_io_pin(self.bank_inst, + "rbl_bl_{0}_{0}".format(port), + "rbl_bl{0}".format(port), + start_layer=pin_layer) + for port in self.write_ports: + for bit in range(self.word_size + self.num_spare_cols): + self.add_io_pin(self.bank_inst, + "din{0}_{1}".format(port, bit), + "bank_din{0}_{1}".format(port, bit), + start_layer=pin_layer) + # manuel change position, so not at same y + for port in self.all_ports: + for bit in range(self.bank_addr_size): + self.change_layout_pin_position(self.bank_inst, + "addr{0}_{1}".format(port, bit), + "addr{0}_{1}".format(port, bit), + start_layer=pin_layer, + distance=bit) + for port in self.read_ports: + self.change_layout_pin_position(self.bank_inst, + "s_en{0}".format(port), + "s_en{0}".format(port), + start_layer=pin_layer, + distance=4) + for port in self.all_ports: + self.change_layout_pin_position(self.bank_inst, + "p_en_bar{0}".format(port), + "p_en_bar{0}".format(port), + start_layer=pin_layer, + distance=2) + for port in self.write_ports: + self.change_layout_pin_position(self.bank_inst, + "w_en{0}".format(port), + "w_en{0}".format(port), + start_layer=pin_layer) + + for bit in range(self.num_wmasks): + self.add_io_pin(self.bank_inst, + "bank_wmask{0}_{1}".format(port, bit), + "bank_wmask{0}_{1}".format(port, bit), + start_layer=pin_layer) + for bit in range(self.num_spare_cols): + self.add_io_pin(self.bank_inst, + "bank_spare_wen{0}_{1}".format(port, bit), + "bank_spare_wen{0}_{1}".format(port, bit), + start_layer=pin_layer) + if port in self.all_ports: + self.add_io_pin(self.bank_inst, + "wl_en{0}".format(port), + "wl_en{0}".format(port), + start_layer=pin_layer) + + def change_layout_pin_position(self, instance, pin_name, new_name, start_layer=None, directions=None, distance=1): + """ + Add a signle input or output pin up to metal 3. + This additonal operation make sure pins are not at the same y + """ + pin = instance.get_pin(pin_name) + + if not start_layer: + start_layer = pin.layer + # Just use the power pin function for now to save code + self.add_power_pin(new_name, vector(pin.center()[0], pin.center()[1] + self.m3_pitch * distance * 2), start_layer=start_layer, directions=directions) + + 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]) + + from openram.router import supply_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.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=None): + """ + 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 openram.router import signal_escape_router as router + rtr = router(layers=self.m3_stack, + bbox=bbox, + design=self) + rtr.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_module("." + OPTS.control_logic, "openram.modules")) + 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)) + if self.has_rbl: + 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)) + if self.has_rbl: + 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): + """ + This adds the modules for a single bank SRAM with control + logic. + """ + + self.bank_inst = self.create_bank(0) + + self.control_logic_insts = self.create_control_logic() + + self.row_addr_dff_insts = self.create_row_addr_dff() + + if self.col_addr_dff: + self.col_addr_dff_insts = self.create_col_addr_dff() + + if self.write_size != self.word_size: + self.wmask_dff_insts = self.create_wmask_dff() + self.data_dff_insts = self.create_data_dff() + else: + self.data_dff_insts = self.create_data_dff() + + if self.num_spare_cols: + self.spare_wen_dff_insts = self.create_spare_wen_dff() + else: + self.num_spare_cols = 0 + + def place_instances(self): + """ + This places the instances for a single bank SRAM with control + logic and up to 2 ports. + """ + + # No orientation or offset + self.place_bank(self.bank_inst, [0, 0], 1, 1) + + # The control logic is placed such that the vertical center (between the delay/RBL and + # the actual control logic is aligned with the vertical center of the bank (between + # the sense amps/column mux and cell array) + # The x-coordinate is placed to allow a single clock wire (plus an extra pitch) + # up to the row address DFFs. + self.control_pos = [None] * len(self.all_ports) + self.row_addr_pos = [None] * len(self.all_ports) + + # DFFs are placd on their own + self.col_addr_pos = [None] * len(self.all_ports) + self.wmask_pos = [None] * len(self.all_ports) + self.spare_wen_pos = [None] * len(self.all_ports) + self.data_pos = [None] * len(self.all_ports) + + # These positions utilize the channel route sizes. + # FIXME: Auto-compute these rather than manual computation. + # If a horizontal channel, they rely on the vertical channel non-preferred (contacted) pitch. + # If a vertical channel, they rely on the horizontal channel non-preferred (contacted) pitch. + # So, m3 non-pref pitch means that this is routed on the m2 layer. + self.data_bus_gap = self.m4_nonpref_pitch * 2 + + # Spare wen are on a separate layer so not included + # Start with 1 track minimum + self.data_bus_size = [1] * len(self.all_ports) + self.col_addr_bus_size = [1] * len(self.all_ports) + for port in self.all_ports: + # The column address wires are routed separately from the data bus and will always be smaller. + # All ports need the col addr flops + self.col_addr_bus_size[port] = self.col_addr_size * self.m4_nonpref_pitch + # Write ports need the data input flops and write mask flops + if port in self.write_ports: + self.data_bus_size[port] += self.num_wmasks + self.word_size + # This is for the din pins that get routed in the same channel + # when we have dout and din together + if port in self.readwrite_ports: + self.data_bus_size[port] += self.word_size + # Convert to length + self.data_bus_size[port] *= self.m4_nonpref_pitch + # Add the gap in unit length + self.data_bus_size[port] += self.data_bus_gap + + # The control and row addr flops are independent of any bus widths. + self.place_control() + self.place_row_addr_dffs() + + # Place with an initial wide channel (from above) + self.place_dffs() + + # Route the channel and set to the new data bus size + # We need to temporarily add some pins for the x offsets + # but we'll remove them so that they have the right y + # offsets after the DFF placement. + self.add_layout_pins(add_vias=False) + self.route_dffs(add_routes=False) + self.remove_layout_pins() + + # Re-place with the new channel size + self.place_dffs() + + def place_row_addr_dffs(self): + """ + Must be run after place control logic. + """ + port = 0 + # The row address bits are placed above the control logic aligned on the right. + x_offset = self.control_logic_insts[port].rx() - self.row_addr_dff_insts[port].width + # It is above the control logic and the predecoder array + y_offset = max(self.control_logic_insts[port].uy(), self.bank.predecoder_top) + + self.row_addr_pos[port] = vector(x_offset, y_offset) + self.row_addr_dff_insts[port].place(self.row_addr_pos[port]) + + if len(self.all_ports)>1: + port = 1 + # The row address bits are placed above the control logic aligned on the left. + x_offset = self.control_pos[port].x - self.control_logic_insts[port].width + self.row_addr_dff_insts[port].width + # If it can be placed above the predecoder and below the control logic, do it + y_offset = self.bank.predecoder_bottom + self.row_addr_pos[port] = vector(x_offset, y_offset) + self.row_addr_dff_insts[port].place(self.row_addr_pos[port], mirror="XY") + + def place_control(self): + port = 0 + + # This includes 2 M2 pitches for the row addr clock line. + # The delay line is aligned with the bitcell array while the control logic is aligned with the port_data + # using the control_logic_center value. + self.control_pos[port] = vector(-self.control_logic_insts[port].width - 2 * self.m2_pitch, + self.bank.bank_array_ll.y - self.control_logic_insts[port].mod.control_logic_center.y) + self.control_logic_insts[port].place(self.control_pos[port]) + if len(self.all_ports) > 1: + port = 1 + # This includes 2 M2 pitches for the row addr clock line + # The delay line is aligned with the bitcell array while the control logic is aligned with the port_data + # using the control_logic_center value. + self.control_pos[port] = vector(self.bank_inst.rx() + self.control_logic_insts[port].width + 2 * self.m2_pitch, + self.bank.bank_array_ur.y + + self.control_logic_insts[port].height + - self.control_logic_insts[port].height + + self.control_logic_insts[port].mod.control_logic_center.y) + self.control_logic_insts[port].place(self.control_pos[port], mirror="XY") + + def place_dffs(self): + """ + Place the col addr, data, wmask, and spare data DFFs. + This can be run more than once after we recompute the channel width. + """ + + port = 0 + # Add the col address flops below the bank to the right of the control logic + x_offset = self.control_logic_insts[port].rx() + self.dff.width + # Place it a data bus below the x-axis, but at least as low as the control logic to not block + # the control logic signals + y_offset = min(-self.data_bus_size[port] - self.dff.height, + self.control_logic_insts[port].by()) + if self.col_addr_dff: + self.col_addr_pos[port] = vector(x_offset, + y_offset) + self.col_addr_dff_insts[port].place(self.col_addr_pos[port]) + x_offset = self.col_addr_dff_insts[port].rx() + else: + self.col_addr_pos[port] = vector(x_offset, 0) + + if port in self.write_ports: + 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) + self.wmask_dff_insts[port].place(self.wmask_pos[port]) + x_offset = self.wmask_dff_insts[port].rx() + + # Add the data flops below the write mask flops. + self.data_pos[port] = vector(x_offset, + y_offset) + self.data_dff_insts[port].place(self.data_pos[port]) + x_offset = self.data_dff_insts[port].rx() + + # Add spare write enable flops to the right of data flops since the spare columns + # will be on the right + if self.num_spare_cols: + self.spare_wen_pos[port] = vector(x_offset, + y_offset) + self.spare_wen_dff_insts[port].place(self.spare_wen_pos[port]) + x_offset = self.spare_wen_dff_insts[port].rx() + + else: + self.wmask_pos[port] = vector(x_offset, y_offset) + self.data_pos[port] = vector(x_offset, y_offset) + self.spare_wen_pos[port] = vector(x_offset, y_offset) + + if len(self.all_ports) > 1: + port = 1 + + # Add the col address flops below the bank to the right of the control logic + x_offset = self.control_logic_insts[port].lx() - 2 * self.dff.width + # Place it a data bus below the x-axis, but at least as high as the control logic to not block + # the control logic signals + y_offset = max(self.bank.height + self.data_bus_size[port] + self.dff.height, + self.control_logic_insts[port].uy() - self.dff.height) + if self.col_addr_dff: + self.col_addr_pos[port] = vector(x_offset, + y_offset) + self.col_addr_dff_insts[port].place(self.col_addr_pos[port], mirror="XY") + x_offset = self.col_addr_dff_insts[port].lx() + else: + self.col_addr_pos[port] = vector(x_offset, y_offset) + + if port in self.write_ports: + # Add spare write enable flops to the right of the data flops since the spare + # columns will be on the left + if self.num_spare_cols: + self.spare_wen_pos[port] = vector(x_offset - self.spare_wen_dff_insts[port].width, + y_offset) + 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 != 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) + self.wmask_dff_insts[port].place(self.wmask_pos[port], mirror="MX") + x_offset = self.wmask_dff_insts[port].lx() + + # Add the data flops below the write mask flops. + self.data_pos[port] = vector(x_offset - self.data_dff_insts[port].width, + y_offset) + self.data_dff_insts[port].place(self.data_pos[port], mirror="MX") + else: + self.wmask_pos[port] = vector(x_offset, y_offset) + self.data_pos[port] = vector(x_offset, y_offset) + self.spare_wen_pos[port] = vector(x_offset, y_offset) + + def add_layout_pins(self, add_vias=True): + """ + Add the top-level pins for a single bank SRAM with control. + """ + for port in self.all_ports: + # Hack: If we are escape routing, set the pin layer to + # None so that we will start from the pin layer + # Otherwise, set it as the pin layer so that no vias are added. + # Otherwise, when we remove pins to move the dff array dynamically, + # we will leave some remaining vias when the pin locations change. + if add_vias: + pin_layer = None + else: + pin_layer = self.pwr_grid_layers[0] + + # Connect the control pins as inputs + for signal in self.control_logic_inputs[port]: + if signal.startswith("rbl"): + continue + self.add_io_pin(self.control_logic_insts[port], + signal, + signal + "{}".format(port), + start_layer=pin_layer) + + if port in self.write_ports: + for bit in range(self.word_size + self.num_spare_cols): + self.add_io_pin(self.data_dff_insts[port], + "din_{}".format(bit), + "din{0}[{1}]".format(port, bit), + start_layer=pin_layer) + + if port in self.readwrite_ports or port in self.read_ports: + for bit in range(self.word_size + self.num_spare_cols): + self.add_io_pin(self.bank_inst, + "dout{0}_{1}".format(port, bit), + "dout{0}[{1}]".format(port, bit), + start_layer=pin_layer) + + for bit in range(self.col_addr_size): + self.add_io_pin(self.col_addr_dff_insts[port], + "din_{}".format(bit), + "addr{0}[{1}]".format(port, bit), + start_layer=pin_layer) + + for bit in range(self.row_addr_size): + self.add_io_pin(self.row_addr_dff_insts[port], + "din_{}".format(bit), + "addr{0}[{1}]".format(port, bit + self.col_addr_size), + start_layer=pin_layer) + + if port in self.write_ports: + 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), + "wmask{0}[{1}]".format(port, bit), + start_layer=pin_layer) + + if port in self.write_ports: + if self.num_spare_cols == 1: + self.add_io_pin(self.spare_wen_dff_insts[port], + "din_{}".format(0), + "spare_wen{0}".format(port), + start_layer=pin_layer) + else: + for bit in range(self.num_spare_cols): + self.add_io_pin(self.spare_wen_dff_insts[port], + "din_{}".format(bit), + "spare_wen{0}[{1}]".format(port, bit), + start_layer=pin_layer) + + def route_layout(self): + """ Route a single bank SRAM """ + + self.route_clk() + + self.route_control_logic() + + self.route_row_addr_dff() + + self.route_dffs() + + # We add the vias to M3 before routing supplies because + # they might create some blockages + self.add_layout_pins() + + # Some technologies have an isolation + self.add_dnwell(inflate=2.5) + + init_bbox = self.get_bbox() + # Route the supplies together and/or to the ring/stripes. + # Route the pins to the perimeter + # change the order + + if OPTS.perimeter_pins: + # We now route the escape routes far enough out so that they will + # reach past the power ring or stripes on the sides + self.route_escape_pins(init_bbox) + #if OPTS.route_supplies: + # self.route_supplies(init_bbox) + """ + if OPTS.route_supplies: + self.route_supplies(init_bbox) + if OPTS.perimeter_pins: + # We now route the escape routes far enough out so that they will + # reach past the power ring or stripes on the sides + self.route_escape_pins(init_bbox) + """ + + + def route_dffs(self, add_routes=True): + + for port in self.all_ports: + self.route_dff(port, add_routes) + + def route_dff(self, port, add_routes): + + # This is only done when we add_routes because the data channel will be larger + # so that can be used for area estimation. + if add_routes: + self.route_col_addr_dffs(port) + + self.route_data_dffs(port, add_routes) + + def route_col_addr_dffs(self, port): + + route_map = [] + + # column mux dff is routed on it's own since it is to the far end + # decoder inputs are min pitch M2, so need to use lower layer stack + if self.col_addr_size > 0: + dff_names = ["dout_{}".format(x) for x in range(self.col_addr_size)] + dff_pins = [self.col_addr_dff_insts[port].get_pin(x) for x in dff_names] + bank_names = ["addr{0}_{1}".format(port, x) for x in range(self.col_addr_size)] + bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] + route_map.extend(list(zip(bank_pins, dff_pins))) + + if len(route_map) > 0: + + # This layer stack must be different than the data dff layer stack + layer_stack = self.m1_stack + + if port == 0: + offset = vector(self.control_logic_insts[port].rx() + self.dff.width, + - self.data_bus_size[port] + 2 * self.m3_pitch) + cr = channel_route(netlist=route_map, + offset=offset, + layer_stack=layer_stack, + parent=self) + # This causes problem in magic since it sometimes cannot extract connectivity of instances + # with no active devices. + self.add_inst(cr.name, cr) + self.connect_inst([]) + # self.add_flat_inst(cr.name, cr) + else: + offset = vector(0, + self.bank.height + self.m3_pitch) + cr = channel_route(netlist=route_map, + offset=offset, + layer_stack=layer_stack, + parent=self) + # This causes problem in magic since it sometimes cannot extract connectivity of instances + # with no active devices. + self.add_inst(cr.name, cr) + self.connect_inst([]) + # self.add_flat_inst(cr.name, cr) + + def route_data_dffs(self, port, add_routes): + route_map = [] + + # wmask dff + if self.num_wmasks > 0 and port in self.write_ports: + dff_names = ["dout_{}".format(x) for x in range(self.num_wmasks)] + dff_pins = [self.wmask_dff_insts[port].get_pin(x) for x in dff_names] + bank_names = ["bank_wmask{0}_{1}".format(port, x) for x in range(self.num_wmasks)] + bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] + route_map.extend(list(zip(bank_pins, dff_pins))) + + if port in self.write_ports: + # synchronized inputs from data dff + dff_names = ["dout_{}".format(x) for x in range(self.word_size + self.num_spare_cols)] + dff_pins = [self.data_dff_insts[port].get_pin(x) for x in dff_names] + bank_names = ["din{0}_{1}".format(port, x) for x in range(self.word_size + self.num_spare_cols)] + bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] + route_map.extend(list(zip(bank_pins, dff_pins))) + + # spare wen dff + if self.num_spare_cols > 0 and port in self.write_ports: + dff_names = ["dout_{}".format(x) for x in range(self.num_spare_cols)] + dff_pins = [self.spare_wen_dff_insts[port].get_pin(x) for x in dff_names] + bank_names = ["bank_spare_wen{0}_{1}".format(port, x) for x in range(self.num_spare_cols)] + bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] + route_map.extend(list(zip(bank_pins, dff_pins))) + + if len(route_map) > 0: + + # This layer stack must be different than the column addr dff layer stack + layer_stack = self.m3_stack + if port == 0: + # This is relative to the bank at 0,0 or the s_en which is routed on M3 also + if "s_en" in self.control_logic_insts[port].mod.pin_map: + y_bottom = min(0, self.control_logic_insts[port].get_pin("s_en").by()) + else: + y_bottom = 0 + + y_offset = y_bottom - self.data_bus_size[port] + 2 * self.m3_pitch + offset = vector(self.control_logic_insts[port].rx() + self.dff.width, + y_offset) + cr = channel_route(netlist=route_map, + offset=offset, + layer_stack=layer_stack, + parent=self) + if add_routes: + # This causes problem in magic since it sometimes cannot extract connectivity of instances + # with no active devices. + self.add_inst(cr.name, cr) + self.connect_inst([]) + # self.add_flat_inst(cr.name, cr) + else: + self.data_bus_size[port] = max(cr.height, self.col_addr_bus_size[port]) + self.data_bus_gap + else: + if "s_en" in self.control_logic_insts[port].mod.pin_map: + y_top = max(self.bank.height, self.control_logic_insts[port].get_pin("s_en").uy()) + else: + y_top = self.bank.height + y_offset = y_top + self.m3_pitch + offset = vector(0, + y_offset) + cr = channel_route(netlist=route_map, + offset=offset, + layer_stack=layer_stack, + parent=self) + if add_routes: + # This causes problem in magic since it sometimes cannot extract connectivity of instances + # with no active devices. + self.add_inst(cr.name, cr) + self.connect_inst([]) + # self.add_flat_inst(cr.name, cr) + else: + self.data_bus_size[port] = max(cr.height, self.col_addr_bus_size[port]) + self.data_bus_gap + + def route_clk(self): + """ Route the clock network """ + + # This is the actual input to the SRAM + for port in self.all_ports: + # Connect all of these clock pins to the clock in the central bus + # This is something like a "spine" clock distribution. The two spines + # are clk_buf and clk_buf_bar + control_clk_buf_pin = self.control_logic_insts[port].get_pin("clk_buf") + control_clk_buf_pos = control_clk_buf_pin.center() + + # This uses a metal2 track to the right (for port0) of the control/row addr DFF + # to route vertically. For port1, it is to the left. + row_addr_clk_pin = self.row_addr_dff_insts[port].get_pin("clk") + if port % 2: + control_clk_buf_pos = control_clk_buf_pin.lc() + row_addr_clk_pos = row_addr_clk_pin.lc() + mid1_pos = vector(self.row_addr_dff_insts[port].lx() - self.m2_pitch, + row_addr_clk_pos.y) + else: + control_clk_buf_pos = control_clk_buf_pin.rc() + row_addr_clk_pos = row_addr_clk_pin.rc() + mid1_pos = vector(self.row_addr_dff_insts[port].rx() + self.m2_pitch, + row_addr_clk_pos.y) + + # This is the steiner point where the net branches out + clk_steiner_pos = vector(mid1_pos.x, control_clk_buf_pos.y) + self.add_path(control_clk_buf_pin.layer, [control_clk_buf_pos, clk_steiner_pos]) + self.add_via_stack_center(from_layer=control_clk_buf_pin.layer, + to_layer="m2", + offset=clk_steiner_pos) + + # Note, the via to the control logic is taken care of above + self.add_wire(self.m2_stack[::-1], + [row_addr_clk_pos, mid1_pos, clk_steiner_pos]) + + if self.col_addr_dff: + dff_clk_pin = self.col_addr_dff_insts[port].get_pin("clk") + dff_clk_pos = dff_clk_pin.center() + mid_pos = vector(clk_steiner_pos.x, dff_clk_pos.y) + self.add_wire(self.m2_stack[::-1], + [dff_clk_pos, mid_pos, clk_steiner_pos]) + elif port in self.write_ports: + data_dff_clk_pin = self.data_dff_insts[port].get_pin("clk") + data_dff_clk_pos = data_dff_clk_pin.center() + mid_pos = vector(clk_steiner_pos.x, data_dff_clk_pos.y) + # In some designs, the steiner via will be too close to the mid_pos via + # so make the wire as wide as the contacts + self.add_path("m2", + [mid_pos, clk_steiner_pos], + width=max(self.m2_via.width, self.m2_via.height)) + self.add_wire(self.m2_stack[::-1], + [data_dff_clk_pos, mid_pos, clk_steiner_pos]) + + def route_control_logic(self): + """ + Route the control logic pins that are not inputs + """ + + for port in self.all_ports: + for signal in self.control_logic_outputs[port]: + # The clock gets routed separately and is not a part of the bank + if "clk" in signal: + continue + src_pin = self.control_logic_insts[port].get_pin(signal) + dest_pin = self.bank_inst.get_pin(signal + "{}".format(port)) + self.connect_vbus(src_pin, dest_pin) + + if self.has_rbl: + for port in self.all_ports: + # Only input (besides pins) is the replica bitline + src_pin = self.control_logic_insts[port].get_pin("rbl_bl") + dest_pin = self.bank_inst.get_pin("rbl_bl_{0}_{0}".format(port)) + self.add_wire(self.m3_stack, + [src_pin.center(), vector(src_pin.cx(), dest_pin.cy()), dest_pin.rc()]) + self.add_via_stack_center(from_layer=src_pin.layer, + to_layer="m4", + offset=src_pin.center()) + self.add_via_stack_center(from_layer=dest_pin.layer, + to_layer="m3", + offset=dest_pin.center()) + + def route_row_addr_dff(self): + """ + Connect the output of the row flops to the bank pins + """ + for port in self.all_ports: + for bit in range(self.row_addr_size): + flop_name = "dout_{}".format(bit) + bank_name = "addr{0}_{1}".format(port, bit + self.col_addr_size) + flop_pin = self.row_addr_dff_insts[port].get_pin(flop_name) + bank_pin = self.bank_inst.get_pin(bank_name) + flop_pos = flop_pin.center() + bank_pos = bank_pin.center() + mid_pos = vector(bank_pos.x, flop_pos.y) + self.add_via_stack_center(from_layer=flop_pin.layer, + to_layer="m3", + offset=flop_pos) + self.add_path("m3", [flop_pos, mid_pos]) + self.add_via_stack_center(from_layer=bank_pin.layer, + to_layer="m3", + offset=mid_pos) + self.add_path(bank_pin.layer, [mid_pos, bank_pos]) + + 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. + """ + return + for n in self.control_logic_outputs[0]: + pin = self.control_logic_insts[0].get_pin(n) + self.add_label(text=n, + layer=pin.layer, + offset=pin.center()) + + def graph_exclude_data_dff(self): + """ + Removes data dff and wmask dff (if applicable) from search graph. + """ + # 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 != self.word_size: + for inst in self.wmask_dff_insts: + self.graph_inst_exclude.add(inst) + if self.num_spare_cols: + for inst in self.spare_wen_dff_insts: + self.graph_inst_exclude.add(inst) + + def graph_exclude_addr_dff(self): + """ + Removes data dff from search graph. + """ + # Address is considered not part of the critical path, subjectively removed + for inst in self.row_addr_dff_insts: + self.graph_inst_exclude.add(inst) + + if self.col_addr_dff: + for inst in self.col_addr_dff_insts: + self.graph_inst_exclude.add(inst) + + def graph_exclude_ctrl_dffs(self): + """ + Exclude dffs for CSB, WEB, etc from graph + """ + # Insts located in control logic, exclusion function called here + for inst in self.control_logic_insts: + inst.mod.graph_exclude_dffs() + + def get_cell_name(self, inst_name, row, col): + """ + Gets the spice name of the target bitcell. + """ + # Sanity check in case it was forgotten + if inst_name.find("x") != 0: + inst_name = "x" + inst_name + return self.bank_inst.mod.get_cell_name(inst_name + "{}x".format(OPTS.hier_seperator) + self.bank_inst.name, row, col) + + def get_bank_num(self, inst_name, row, col): + return 0 diff --git a/compiler/router/signal_escape_router.py b/compiler/router/signal_escape_router.py index fe0cca100..6f79509a5 100644 --- a/compiler/router/signal_escape_router.py +++ b/compiler/router/signal_escape_router.py @@ -10,7 +10,7 @@ from .graph import graph from .graph_shape import graph_shape from .router import router - +import re class signal_escape_router(router): """ @@ -76,6 +76,8 @@ def route(self, pin_names): self.find_vias(new_vias) routed_count += 1 debug.info(2, "Routed {} of {} signal pins".format(routed_count, routed_max)) + print("route pins:") + print(source) self.replace_layout_pins() @@ -175,14 +177,18 @@ def add_perimeter_fake_pins(self): rect=rect, layer_name_pp=layer) self.fake_pins.append(pin) - + print("this is add_per") + print(pin.name) + print(pin.center) def create_fake_pin(self, pin): """ Create a fake pin on the perimeter orthogonal to the given pin. """ ll, ur = self.bbox c = pin.center() - + print("inside pin name") + print("----------------------------------------------------------") + print(pin.name) # Find the closest edge edge, vertical = self.get_closest_edge(c) @@ -196,16 +202,62 @@ def create_fake_pin(self, pin): fake_center = vector(ur.x + self.track_wire * 2, c.y) if edge == "top": fake_center = vector(c.x, ur.y + self.track_wire * 2) + #fake_center = vector(ll.x - self.track_wire * 2, c.y) # test if here we could change the pin position at the layout + # relocate the pin position + """ + pattern = r'^addr0_1' + if re.match(pattern, pin.name): + vertical = True + fake_center = vector(ll.x - self.track_wire * 2, c.y + self.track_wire * 4)# fix still do not know how to control the distance between every fake pin + #do not know why after this, all fake out pins are put at the same position -> because the originl inside pin has same y? + pattern = r'^addr0_2' + if re.match(pattern, pin.name): + vertical = True + fake_center = vector(ll.x - self.track_wire * 2, c.y + self.track_wire * 8)# fix still do not know how to control the distance between every fake pin + #do not know why after this, all fake out pins are put at the same position -> because the originl inside pin has same y? + pattern = r'^addr0_3' + if re.match(pattern, pin.name): + vertical = True + fake_center = vector(ll.x - self.track_wire * 2, c.y + self.track_wire*12)# fix still do not know how to control the distance between every fake pin + #do not know why after this, all fake out pins are put at the same position -> because the originl inside pin has same y? + pattern = r'^addr0_4' + if re.match(pattern, pin.name): + vertical = True + fake_center = vector(ll.x - self.track_wire * 2, c.y + self.track_wire*16)# fix still do not know how to control the distance between every fake pin + #do not know why after this, all fake out pins are put at the same position -> because the originl inside pin has same y? + pattern = r'^p_en_bar0' + if re.match(pattern, pin.name): + vertical = True + fake_center = vector(ll.x - self.track_wire * 2, c.y)# fix still do not know how to control the distance between every fake pin + #do not know why after this, all fake out pins are put at the same position -> because the originl inside pin has same y? + pattern = r'^s_en0' + if re.match(pattern, pin.name): + vertical = True + fake_center = vector(ll.x - self.track_wire * 2, c.y + self.track_width *3)# fix still do not know how to control the distance between every fake pin + #do not know why after this, all fake out pins are put at the same position -> because the originl inside pin has same y? + pattern = r'^w_en0' + if re.match(pattern, pin.name): + vertical = True + fake_center = vector(ll.x - self.track_wire * 2, c.y + self.track_width * 6)# fix still do not know how to control the distance between every fake pin + #do not know why after this, all fake out pins are put at the same position -> because the originl inside pin has same y? + """ # Create the fake pin shape layer = self.get_layer(int(not vertical)) half_wire_vector = vector([self.half_wire] * 2) nll = fake_center - half_wire_vector nur = fake_center + half_wire_vector + #not test jet + #half_wire_vector = vector([self.half_wire] * 2)# out *2 means vector([self.half_wire, self.half_wire]) + #nll = fake_center - half_wire_vector - half_wire_vector + #nur = fake_center + half_wire_vector + half_wire_vector rect = [nll, nur] pin = graph_shape(name="fake", rect=rect, layer_name_pp=layer) + print("this create_fake_pin") + print(pin.name) + print(pin.center) return pin @@ -214,6 +266,8 @@ def get_route_pairs(self, pin_names): to_route = [] for name in pin_names: + print("==============the pin names===================") + print(name) pin = next(iter(self.pins[name])) fake = self.create_fake_pin(pin) to_route.append((pin, fake, pin.distance(fake))) diff --git a/compiler/router/supply_router.py b/compiler/router/supply_router.py index fc61f4e64..e868c8b2b 100644 --- a/compiler/router/supply_router.py +++ b/compiler/router/supply_router.py @@ -76,6 +76,8 @@ def route(self, vdd_name="vdd", gnd_name="gnd"): # Create the graph g = graph(self) g.create_graph(source, target) + # debug + debug.warning("graph creat success!") # Find the shortest path from source to target path = g.find_shortest_path() # If no path is found, throw an error diff --git a/compiler/sram.py b/compiler/sram.py index ea71cad4c..36ec253c4 100644 --- a/compiler/sram.py +++ b/compiler/sram.py @@ -50,7 +50,7 @@ def __init__(self, sram_config=None, name=None): self.name = name from openram.modules.sram_1bank import sram_1bank as sram - + #from openram.modules.sram_new import sram_1bank as sram self.s = sram(name, sram_config) self.s.create_netlist() diff --git a/compiler/sram_road.py b/compiler/sram_road.py new file mode 100644 index 000000000..85592d4a6 --- /dev/null +++ b/compiler/sram_road.py @@ -0,0 +1,253 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2024 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. +# +# This file generate sram part, aiming at using openroad to do the P&R +import os +import shutil +import datetime +from openram import debug +from openram import sram_config as config +from openram import OPTS, print_time + + +class sram(): + """ + This is not a design module, but contains an SRAM design instance. + It could later try options of number of banks and organization to compare + results. + We can later add visualizer and other high-level functions as needed. + """ + def __init__(self, sram_config=None, name=None): + + # Create default configs if custom config isn't provided + if sram_config is None: + sram_config = config(word_size=OPTS.word_size, + num_words=OPTS.num_words, + write_size=OPTS.write_size, + num_banks=OPTS.num_banks, + words_per_row=OPTS.words_per_row, + num_spare_rows=OPTS.num_spare_rows, + num_spare_cols=OPTS.num_spare_cols) + + if name is None: + name = OPTS.output_name + + sram_config.set_local_config(self) + + # reset the static duplicate name checker for unit tests + # in case we create more than one SRAM + from openram.base import design + design.name_map=[] + + debug.info(2, "create sram of size {0} with {1} num of words {2} banks".format(self.word_size, + self.num_words, + self.num_banks)) + start_time = datetime.datetime.now() + + self.name = name + + #from openram.modules.sram_1bank import sram_1bank as sram + from openram.modules.sram_part import sram_1bank as sram + self.s = sram(name, sram_config) + + def get_sp_name(self): + if OPTS.use_pex: + # Use the extracted spice file + return self.pex_name + else: + # Use generated spice file for characterization + return self.sp_name + + def sp_write(self, name, lvs=False, trim=False): + self.s.sp_write(name, lvs, trim) + + def lef_write(self, name): + self.s.lef_write(name) + + def gds_write(self, name): + self.s.gds_write(name) + + def verilog_write(self, name): + self.s.verilog_write(name) + if self.num_banks != 1: + from openram.modules.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. + Include defaults and anything changed by input config.""" + f = open(name, "w") + var_dict = dict((name, getattr(OPTS, name)) for name in dir(OPTS) if not name.startswith('__') and not callable(getattr(OPTS, name))) + for var_name, var_value in var_dict.items(): + if isinstance(var_value, str): + f.write(str(var_name) + " = " + "\"" + str(var_value) + "\"\n") + else: + f.write(str(var_name) + " = " + str(var_value)+ "\n") + f.close() + + def generate_files(self, module_name): + """use to generate gds, lef files for one certain layout""" + # Import this at the last minute so that the proper tech file + # is loaded and the right tools are selected + from openram import verify + from openram.characterizer import functional + from openram.characterizer import delay + + # Save the spice file + start_time = datetime.datetime.now() + spname = OPTS.output_path + self.s.name + module_name + ".sp" + debug.print_raw("SP: Writing to {0}".format(spname)) + self.sp_write(spname) + + print_time("Spice writing", datetime.datetime.now(), start_time) + + # Save trimmed spice file + temp_trim_sp = "{0}trimmed_{1}.sp".format(OPTS.output_path, module_name) + self.sp_write(temp_trim_sp, lvs=False, trim=True) + + if not OPTS.netlist_only: + # Write the layout + start_time = datetime.datetime.now() + gdsname = OPTS.output_path + self.s.name + module_name + ".gds" + debug.print_raw("GDS: Writing to {0}".format(gdsname)) + self.gds_write(gdsname) + if OPTS.check_lvsdrc: + verify.write_drc_script(cell_name=self.s.name + module_name, + gds_name=os.path.basename(gdsname), + extract=True, + final_verification=True, + output_path=OPTS.output_path) + print_time("GDS", datetime.datetime.now(), start_time) + + # Create a LEF physical model + start_time = datetime.datetime.now() + lefname = OPTS.output_path + self.s.name + module_name + ".lef" + debug.print_raw("LEF: Writing to {0}".format(lefname)) + self.lef_write(lefname) + print_time("LEF", datetime.datetime.now(), start_time) + + # Save the LVS file + start_time = datetime.datetime.now() + lvsname = OPTS.output_path + self.s.name + module_name + ".lvs.sp" + debug.print_raw("LVS: Writing to {0}".format(lvsname)) + self.sp_write(lvsname, lvs=True) + if not OPTS.netlist_only and OPTS.check_lvsdrc: + verify.write_lvs_script(cell_name=self.s.name + module_name, + gds_name=os.path.basename(gdsname), + sp_name=os.path.basename(lvsname), + final_verification=True, + output_path=OPTS.output_path) + print_time("LVS writing", datetime.datetime.now(), start_time) + + # Save the extracted spice file + if OPTS.use_pex: + start_time = datetime.datetime.now() + # Output the extracted design if requested + pexname = OPTS.output_path + self.s.name + module_name + ".pex.sp" + spname = OPTS.output_path + self.s.name + module_name + ".sp" + verify.run_pex(self.s.name, gdsname, spname, output=pexname) + sp_file = pexname + print_time("Extraction", datetime.datetime.now(), start_time) + else: + # Use generated spice file for characterization + sp_file = spname + + # Write the config file + start_time = datetime.datetime.now() + try: + from shutil import copyfile + copyfile(OPTS.config_file, OPTS.output_path + OPTS.output_name + '.py') + except shutil.SameFileError: + pass + debug.print_raw("Config: Writing to {0}".format(OPTS.output_path + OPTS.output_name + '.py')) + print_time("Config", datetime.datetime.now(), start_time) + + + # Write a verilog model + start_time = datetime.datetime.now() + 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) + + # Write out options if specified + if OPTS.output_extended_config: + start_time = datetime.datetime.now() + oname = OPTS.output_path + OPTS.output_name + "_extended.py" + debug.print_raw("Extended Config: Writing to {0}".format(oname)) + self.extended_config_write(oname) + print_time("Extended Config", datetime.datetime.now(), start_time) + + + def save(self): + """ Save all the output files while reporting time to do it as well. """ + for i in range(7): + if i == 0: + self.s.create_netlist_bank() + if not OPTS.netlist_only: + self.s.create_layout_bank_only() + self.generate_files("bank") + elif i == 1: + self.s.create_netlist_control() + if not OPTS.netlist_only: + for port in self.s.all_ports: + self.s.create_layout_control_only(self, instance_index=port) + self.generate_files("control_" + port) + else: + for port in self.s.all_ports: + self.generate_files("control_" + port) + elif i == 2: + self.s.create_netlist_row_addr_dff() + if not OPTS.netlist_only: + for port in self.s.all_ports: + self.s.create_layout_row_addr_dff_only(self, instance_index=port) + self.generate_files("row_addr_dff_" + port) + else: + for port in self.s.all_ports: + self.generate_files("row_addr_dff_" + port) + elif i == 3: + if self.s.create_netlist_col_addr_dff() == False: + continue # do not need col addr dff + elif not OPTS.netlist_only: + for port in self.s.all_ports: + self.create_layout_col_addr_dff_only(self, instance_index=port) + self.generate_files("col_addr_dff_" + port) + else: + self.generate_files("col_addr_dff_" + port) + elif i == 4: + self.s.create_netlist_data_dff() + if not OPTS.netlist_only: + for port in self.s.all_ports: + self.s.create_layout_data_dff_only(self, instance_index=port) + self.generate_files("data_dff_" + port) + else: + for port in self.s.all_ports: + self.generate_files("data_dff_" + port) + elif i == 5: + if self.s.create_netlist_wmask_dff() == False: + continue # do not need wmask dff + elif not OPTS.netlist_only: + for port in self.s.all_ports: + self.s.create_layout_wmask_dff_only(self, instance_index=port) + self.generate_files("wmask_dff_" + port) + else: + for port in self.s.all_ports: + self.generate_files("wmask_dff_" + port) + elif i == 6: + if self.s.create_netlist_spare_wen_dff() == False: + continue # do not need spare wen dff + elif not OPTS.netlist_only: + for port in self.s.all_ports: + self.s.create_layout_spare_wen_dff_only(self, instance_index=port) + self.generate_files("spare_wen_dff_" + port) + else: + for port in self.s.all_ports: + self.generate_files("spare_wen_dff_" + port) + + + diff --git a/sram_compiler.py b/sram_compiler.py index 34b398bca..31435695b 100755 --- a/sram_compiler.py +++ b/sram_compiler.py @@ -69,9 +69,10 @@ debug.print_raw(path) # Create an SRAM (we can also pass sram_config, see documentation/tutorials for details) -from openram import sram -s = sram() - +#from openram import sram +#s = sram() +from openram import sram_road +s = sram_road.sram() # Output the files for the resulting SRAM s.save() From f69f8de00060920e9716490d645479a838e5090e Mon Sep 17 00:00:00 2001 From: FriedrichWu Date: Wed, 26 Jun 2024 17:40:22 +0200 Subject: [PATCH 02/26] first commit --- compiler/modules/sram_part.py | 96 +++++++++++++------------- compiler/sram_road.py | 125 +++++++++++++++++----------------- sram_compiler.py | 24 +++++++ 3 files changed, 133 insertions(+), 112 deletions(-) diff --git a/compiler/modules/sram_part.py b/compiler/modules/sram_part.py index 5d374940a..712edcaf2 100644 --- a/compiler/modules/sram_part.py +++ b/compiler/modules/sram_part.py @@ -105,6 +105,7 @@ def add_pin_row_addr_dff(self): for bit in range(self.row_addr_size): #input self.add_pin("addr{}[{}]".format(port, bit + self.col_addr_size), "INPUT") + print("addr{}[{}]".format(port, bit), "INPUT") #output self.add_pin("a{}_{}".format(port, bit + self.col_addr_size), "OUTPUT") #clk_buf, regard as input @@ -492,7 +493,7 @@ def create_netlist_data_dff(self): # Must create the control logic before pins to get the pins self.add_modules() self.add_pin_data_dff() - self.bank_inst = self.create_col_addr_dff() # be aware, multi-insts possible + self.data_dff_insts = self.create_data_dff() # be aware, multi-insts possible # This is for the lib file if we don't create layout self.width=0 @@ -510,7 +511,7 @@ def create_netlist_wmask_dff(self): self.add_modules() if self.write_size != self.word_size: self.add_pin_wmask_dff() - self.bank_inst = self.create_wmask_dff() # be aware, multi-insts possible, or none + self.wmask_dff_insts = self.create_wmask_dff() # be aware, multi-insts possible, or none # This is for the lib file if we don't create layout self.width=0 @@ -612,8 +613,7 @@ def create_layout_control_only(self, instance_index=0): # Placement # We will generate different layout for different port(if there are multi-port) port = instance_index - self.control_pos[port] = vector(0,0) - self.control_logic_insts[port].place(self.control_pos[port]) + self.control_logic_insts[port].place(vector(0,0)) if not OPTS.is_unit_test: print_time("Control Placement", datetime.datetime.now(), start_time) @@ -648,8 +648,7 @@ def create_layout_row_addr_dff_only(self, instance_index=0): # Placement # We will generate different layout for different port(if there are multi-port) port = instance_index - self.row_addr_pos[port] = vector(0,0) - self.row_addr_dff_insts[port].place(self.row_addr_pos[port]) + self.row_addr_dff_insts[port].place(vector(0,0)) if not OPTS.is_unit_test: print_time("row_addr_dff Placement", datetime.datetime.now(), start_time) @@ -684,8 +683,7 @@ def create_layout_col_addr_dff_only(self, instance_index=0): # Placement # We will generate different layout for different port(if there are multi-port) port = instance_index - self.col_addr_pos[port] = vector(0,0) - self.col_addr_dff_insts[port].place(self.col_addr_pos[port]) + self.col_addr_dff_insts[port].place(vector(0,0)) if not OPTS.is_unit_test: print_time("col_addr_dff Placement", datetime.datetime.now(), start_time) @@ -720,8 +718,7 @@ def create_layout_data_dff_only(self, instance_index=0): # Placement # We will generate different layout for different port(if there are multi-port) port = instance_index - self.data_pos[port] = vector(0,0) - self.data_dff_insts[port].place(self.data_pos[port]) + self.data_dff_insts[port].place(vector(0,0)) if not OPTS.is_unit_test: print_time("data_dff Placement", datetime.datetime.now(), start_time) @@ -756,8 +753,7 @@ def create_layout_wmask_dff_only(self, instance_index=0): # Placement # We will generate different layout for different port(if there are multi-port) port = instance_index - self.wmask_pos[port] = vector(0,0) - self.wmask_dff_insts[port].place(self.wmask_pos[port]) + self.wmask_dff_insts[port].place(vector(0,0)) if not OPTS.is_unit_test: print_time("wmask_dff Placement", datetime.datetime.now(), start_time) @@ -792,8 +788,7 @@ def create_layout_spare_wen_dff_only(self, instance_index=0): # Placement # We will generate different layout for different port(if there are multi-port) port = instance_index - self.spare_wen_pos[port] = vector(0,0) - self.spare_wen_dff_insts[port].place(self.spare_wen_pos[port]) + self.spare_wen_dff_insts[port].place(vector(0,0)) if not OPTS.is_unit_test: print_time("spare_wen_dff Placement", datetime.datetime.now(), start_time) @@ -1005,7 +1000,7 @@ def route_spare_wen_dff_only(self, instance_index=0): if OPTS.route_supplies: self.route_supplies(init_bbox) - def add_layout_spare_wen_dff_only_pins(self, instance_index=0): + def add_pins_to_route_spare_wen_dff(self, instance_index=0): #only contains signal pins, no power pins """ Adding to route pins for spare wen dff """ pins_to_route = [] @@ -1021,7 +1016,7 @@ def add_layout_spare_wen_dff_only_pins(self, instance_index=0): return pins_to_route - def add_pins_to_route_spare_wen_dff(self, add_vias=True, instance_index=0): + def add_layout_spare_wen_dff_only_pins(self, add_vias=True, instance_index=0): """ Add the top-level pins for a single bank SRAM with control. """ @@ -1039,17 +1034,17 @@ def add_pins_to_route_spare_wen_dff(self, add_vias=True, instance_index=0): for bit in range(self.num_spare_cols): # input self.add_io_pin(self.spare_wen_dff_insts[port], - "spare_wen{}[{}]".format(port, bit), # old name + "din_{}".format(bit), # old name "spare_wen{}[{}]".format(port, bit), # new name start_layer=pin_layer) # output self.add_io_pin(self.spare_wen_dff_insts[port], - "bank_spare_wen{}_{}".format(port, bit), + "dout_{}".format(port, bit), "bank_spare_wen{}_{}".format(port, bit), start_layer=pin_layer) #clk_buf, regard as input self.add_io_pin(self.spare_wen_dff_insts[port], - "clk_buf{}".format(port), + "clk", "clk_buf{}".format(port), start_layer=pin_layer) @@ -1086,17 +1081,17 @@ def add_layout_wmask_dff_only_pins(self, add_vias=True, instance_index=0): for bit in range(self.num_wmasks): # input self.add_io_pin(self.wmask_dff_insts[port], - "wmask{}[{}]".format(port, bit), + "din_{}".format(bit), "wmask{}[{}]".format(port, bit), start_layer=pin_layer) # output self.add_io_pin(self.wmask_dff_insts[port], - "bank_wmask{}_{}".format(port, bit), + "dout_{}".format(bit), "bank_wmask{}_{}".format(port, bit), start_layer=pin_layer) #clk_buf, regard as input self.add_io_pin(self.wmask_dff_insts[port], - "clk_buf{}".format(port), + "clk", "clk_buf{}".format(port), start_layer=pin_layer) @@ -1133,17 +1128,17 @@ def add_layout_data_dff_only_pins(self, add_vias=True, instance_index=0): for bit in range(self.word_size + self.num_spare_cols): # input self.add_io_pin(self.data_dff_insts[port], - "din{}[{}]".format(port, bit), + "din_{}".format(bit), "din{}[{}]".format(port, bit), start_layer=pin_layer) # output self.add_io_pin(self.data_dff_insts[port], - "bank_din{}_{}".format(port, bit), + "dout_{}".format(bit), "bank_din{}_{}".format(port, bit), start_layer=pin_layer) #clk_buf, regard as input self.add_io_pin(self.data_dff_insts[port], - "clk_buf{}".format(port), + "clk", "clk_buf{}".format(port), start_layer=pin_layer) @@ -1180,17 +1175,17 @@ def add_layout_col_addr_dff_only_pins(self, add_vias=True, instance_index=0): for bit in range(self.col_addr_size): #input self.add_io_pin(self.col_addr_dff_insts[port], - "addr{}[{}]".format(port, bit), # old name + "din_{}".format(bit), # old name "addr{}[{}]".format(port, bit), # new name start_layer=pin_layer) #output self.add_io_pin(self.col_addr_dff_insts[port], - "a{}_{}".format(port, bit), + "dout_{}".format(bit), "a{}_{}".format(port, bit), start_layer=pin_layer) #clk_buf, regard as input self.add_pin(self.col_addr_dff_insts[port], - "clk_buf{}".format(port), + "clk", "clk_buf{}".format(port), start_layer=pin_layer) @@ -1226,18 +1221,21 @@ def add_layout_row_addr_dff_only_pins(self, add_vias=True, instance_index=0): for bit in range(self.row_addr_size): #input + #debug + print("this is port number:{0}".format(port)) + print("insts:{0}".format(self.row_addr_dff_insts[port])) self.add_io_pin(self.row_addr_dff_insts[port], - "addr{}[{}]".format(port, bit + self.col_addr_size), # old name + "din_{}".format(bit + self.col_addr_size), # old name "addr{}[{}]".format(port, bit + self.col_addr_size), # new name start_layer=pin_layer) #output self.add_io_pin(self.row_addr_dff_insts[port], - "a{}_{}".format(port, bit + self.col_addr_size), + "dout_{}".format(bit + self.col_addr_size), "a{}_{}".format(port, bit + self.col_addr_size), start_layer=pin_layer) #clk_buf, regard as input self.add_io_pin(self.row_addr_dff_insts[port], - "clk_buf{}".format(port), + "clk", "clk_buf{}".format(port), start_layer=pin_layer) @@ -1280,45 +1278,45 @@ def add_layout_control_only_pins(self, add_vias=True, instance_index=0): pin_layer = self.pwr_grid_layers[0] # Inputs - self.add_io_pin(self.control_insts[port], - "csb{}".format(port), #old name + self.add_io_pin(self.control_logic_insts[port], + "csb", #old name "csb{}".format(port), #new name start_layer=pin_layer) if port in self.readwrite_ports: - self.add_io_pin(self.control_insts[port], - "web{}".format(port), + self.add_io_pin(self.control_logic_insts[port], + "web", "web{}".format(port), start_layer=pin_layer) - self.add_io_pin(self.control_insts[port], - "clk{}".format(port), + self.add_io_pin(self.control_logic_insts[port], + "clk", "clk{}".format(port), start_layer=pin_layer) if self.has_rbl: - self.add_io_pin(self.control_insts[port], - "rbl_bl{}".format(port), + self.add_io_pin(self.control_logic_insts[port], + "rbl_bl", "rbl_bl{}".format(port), start_layer=pin_layer) # Outputs if port in self.read_ports: - self.add_io_pin(self.control_insts[port], - "s_en{}".format(port), + self.add_io_pin(self.control_logic_insts[port], + "s_en", "s_en{}".format(port), start_layer=pin_layer) if port in self.write_ports: - self.add_io_pin(self.control_insts[port], - "w_en{}".format(port), + self.add_io_pin(self.control_logic_insts[port], + "w_en", "w_en{}".format(port), start_layer=pin_layer) - self.add_io_pin(self.control_insts[port], - "p_en_bar{}".format(port), + self.add_io_pin(self.control_logic_insts[port], + "p_en_bar", "p_en_bar{}".format(port), start_layer=pin_layer) - self.add_io_pin(self.control_insts[port], - "wl_en{}".format(port), + self.add_io_pin(self.control_logic_insts[port], + "wl_en", "wl_en{}".format(port), start_layer=pin_layer) - self.add_io_pin(self.control_insts[port], - "clk_buf{}".format(port), + self.add_io_pin(self.control_logic_insts[port], + "clk_buf", "clk_buf{}".format(port), start_layer=pin_layer) diff --git a/compiler/sram_road.py b/compiler/sram_road.py index 85592d4a6..a899a967d 100644 --- a/compiler/sram_road.py +++ b/compiler/sram_road.py @@ -184,70 +184,69 @@ def generate_files(self, module_name): print_time("Extended Config", datetime.datetime.now(), start_time) - def save(self): + def save(self, mod=0): """ Save all the output files while reporting time to do it as well. """ - for i in range(7): - if i == 0: - self.s.create_netlist_bank() - if not OPTS.netlist_only: - self.s.create_layout_bank_only() - self.generate_files("bank") - elif i == 1: - self.s.create_netlist_control() - if not OPTS.netlist_only: - for port in self.s.all_ports: - self.s.create_layout_control_only(self, instance_index=port) - self.generate_files("control_" + port) - else: - for port in self.s.all_ports: - self.generate_files("control_" + port) - elif i == 2: - self.s.create_netlist_row_addr_dff() - if not OPTS.netlist_only: - for port in self.s.all_ports: - self.s.create_layout_row_addr_dff_only(self, instance_index=port) - self.generate_files("row_addr_dff_" + port) - else: - for port in self.s.all_ports: - self.generate_files("row_addr_dff_" + port) - elif i == 3: - if self.s.create_netlist_col_addr_dff() == False: - continue # do not need col addr dff - elif not OPTS.netlist_only: - for port in self.s.all_ports: - self.create_layout_col_addr_dff_only(self, instance_index=port) - self.generate_files("col_addr_dff_" + port) - else: - self.generate_files("col_addr_dff_" + port) - elif i == 4: - self.s.create_netlist_data_dff() - if not OPTS.netlist_only: - for port in self.s.all_ports: - self.s.create_layout_data_dff_only(self, instance_index=port) - self.generate_files("data_dff_" + port) - else: - for port in self.s.all_ports: - self.generate_files("data_dff_" + port) - elif i == 5: - if self.s.create_netlist_wmask_dff() == False: - continue # do not need wmask dff - elif not OPTS.netlist_only: - for port in self.s.all_ports: - self.s.create_layout_wmask_dff_only(self, instance_index=port) - self.generate_files("wmask_dff_" + port) - else: - for port in self.s.all_ports: - self.generate_files("wmask_dff_" + port) - elif i == 6: - if self.s.create_netlist_spare_wen_dff() == False: - continue # do not need spare wen dff - elif not OPTS.netlist_only: - for port in self.s.all_ports: - self.s.create_layout_spare_wen_dff_only(self, instance_index=port) - self.generate_files("spare_wen_dff_" + port) - else: - for port in self.s.all_ports: - self.generate_files("spare_wen_dff_" + port) + if mod == 0: + self.s.create_netlist_bank() + if not OPTS.netlist_only: + self.s.create_layout_bank_only() + self.generate_files("_bank") + elif mod == 1: + self.s.create_netlist_control() + if not OPTS.netlist_only: + for port in self.s.all_ports: + self.s.create_layout_control_only(instance_index=port) + self.generate_files("_control_" + str(port)) + else: + for port in self.s.all_ports: + self.generate_files("_control_" + str(port)) + elif mod == 2: + self.s.create_netlist_row_addr_dff() + if not OPTS.netlist_only: + for port in self.s.all_ports: + self.s.create_layout_row_addr_dff_only(instance_index=port) + self.generate_files("_row_addr_dff_" + str(port)) + else: + for port in self.s.all_ports: + self.generate_files("_row_addr_dff_" + str(port)) + elif mod == 3: + if self.s.create_netlist_col_addr_dff() == False: + pass#continue # do not need col addr dff + elif not OPTS.netlist_only: + for port in self.s.all_ports: + self.create_layout_col_addr_dff_only(instance_index=port) + self.generate_files("_col_addr_dff_" + str(port)) + else: + self.generate_files("_col_addr_dff_" + str(port)) + elif mod == 4: + self.s.create_netlist_data_dff() + if not OPTS.netlist_only: + for port in self.s.all_ports: + self.s.create_layout_data_dff_only(instance_index=port) + self.generate_files("_data_dff_" + str(port)) + else: + for port in self.s.all_ports: + self.generate_files("_data_dff_" + str(port)) + elif mod == 5: + if self.s.create_netlist_wmask_dff() == False: + pass#continue # do not need wmask dff + elif not OPTS.netlist_only: + for port in self.s.all_ports: + self.s.create_layout_wmask_dff_only(instance_index=port) + self.generate_files("_wmask_dff_" + str(port)) + else: + for port in self.s.all_ports: + self.generate_files("_wmask_dff_" + str(port)) + elif mod == 6: + if self.s.create_netlist_spare_wen_dff() == False: + pass#continue # do not need spare wen dff + elif not OPTS.netlist_only: + for port in self.s.all_ports: + self.s.create_layout_spare_wen_dff_only(instance_index=port) + self.generate_files("_spare_wen_dff_" + str(port)) + else: + for port in self.s.all_ports: + self.generate_files("_spare_wen_dff_" + str(port)) diff --git a/sram_compiler.py b/sram_compiler.py index 31435695b..b9b0390cb 100755 --- a/sram_compiler.py +++ b/sram_compiler.py @@ -76,6 +76,30 @@ # Output the files for the resulting SRAM s.save() +del s +s = sram_road.sram() +s.save(mod=1) + +del s +s = sram_road.sram() +s.save(mod=2) + +del s +s = sram_road.sram() +s.save(mod=3) + +del s +s = sram_road.sram() +s.save(mod=4) + +del s +s = sram_road.sram() +s.save(mod=5) + +del s +s = sram_road.sram() +s.save(mod=6) + # Delete temp files etc. openram.end_openram() openram.print_time("End", datetime.datetime.now(), start_time) From 4efc0e688ba73519b877f5f2e69a4ea745244f90 Mon Sep 17 00:00:00 2001 From: FriedrichWu Date: Wed, 3 Jul 2024 21:14:32 +0200 Subject: [PATCH 03/26] fix the lef file problem partly, spare_wen0[0] left --- compiler/modules/sram_part.py | 72 +++++++++++++++++------------------ 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/compiler/modules/sram_part.py b/compiler/modules/sram_part.py index 712edcaf2..a07294ec2 100644 --- a/compiler/modules/sram_part.py +++ b/compiler/modules/sram_part.py @@ -54,20 +54,20 @@ def __init__(self, name, sram_config): self.has_rbl = OPTS.control_logic != "control_logic_delay" def add_pin_bank(self): - # do not now why need this + # lef file use this pin name, should be same with new pin """ Adding pins for Bank module""" 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") + self.add_pin("dout{0}[{1}]".format(port, bit), "OUTPUT") for port in self.all_ports: if self.has_rbl: - self.add_pin("rbl_bl_{0}_{0}".format(port), "OUTPUT") + self.add_pin("rbl_bl{0}".format(port), "OUTPUT") 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") + self.add_pin("bank_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") + 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_*. @@ -78,9 +78,9 @@ def add_pin_bank(self): for port in self.write_ports: self.add_pin("w_en{0}".format(port), "INPUT") for bit in range(self.num_wmasks): - self.add_pin("bank_wmask{0}_{1}".format(port, bit), "INPUT") + self.add_pin("bank_wmask{0}[{1}]".format(port, bit), "INPUT") for bit in range(self.num_spare_cols): - self.add_pin("bank_spare_wen{0}_{1}".format(port, bit), "INPUT") + self.add_pin("bank_spare_wen{0}[{1}]".format(port, bit), "INPUT") for port in self.all_ports: self.add_pin("wl_en{0}".format(port), "INPUT") @@ -100,14 +100,14 @@ def add_pin_bank(self): self.ext_supply = {"vdd" : self.vdd_name, "gnd" : self.gnd_name} def add_pin_row_addr_dff(self): - # do not know why we need this + # lef file use this pin name, should be same with new pin for port in self.all_ports: for bit in range(self.row_addr_size): #input self.add_pin("addr{}[{}]".format(port, bit + self.col_addr_size), "INPUT") print("addr{}[{}]".format(port, bit), "INPUT") #output - self.add_pin("a{}_{}".format(port, bit + self.col_addr_size), "OUTPUT") + self.add_pin("a{}[{}]".format(port, bit + self.col_addr_size), "OUTPUT") #clk_buf, regard as input self.add_pin("clk_buf{}".format(port), "INPUT") # Standard supply and ground names @@ -126,13 +126,13 @@ def add_pin_row_addr_dff(self): self.ext_supply = {"vdd" : self.vdd_name, "gnd" : self.gnd_name} def add_pin_col_addr_dff(self): - # do not know why we need this + # lef file use this pin name, should be same with new pin for port in self.all_ports: for bit in range(self.col_addr_size): #input self.add_pin("addr{}[{}]".format(port, bit), "INPUT") #output - self.add_pin("a{}_{}".format(port, bit), "OUTPUT") + self.add_pin("a{}[{}]".format(port, bit), "OUTPUT") #clk_buf, regard as input self.add_pin("clk_buf{}".format(port), "INPUT") # Standard supply and ground names @@ -151,13 +151,13 @@ def add_pin_col_addr_dff(self): self.ext_supply = {"vdd" : self.vdd_name, "gnd" : self.gnd_name} def add_pin_data_dff(self): - # do not know why we need this + # lef file use this pin name, should be same with new pin for port in self.all_ports: for bit in range(self.word_size + self.num_spare_cols): # input self.add_pin("din{}[{}]".format(port, bit), "INPUT") # output - self.add_pin("bank_din{}_{}".format(port, bit), "OUTPUT") + self.add_pin("bank_din{}[{}]".format(port, bit), "OUTPUT") #clk_buf, regard as input self.add_pin("clk_buf{}".format(port), "INPUT") # Standard supply and ground names @@ -176,13 +176,13 @@ def add_pin_data_dff(self): self.ext_supply = {"vdd" : self.vdd_name, "gnd" : self.gnd_name} def add_pin_wmask_dff(self): - # do not know why we need this + # lef file use this pin name, should be same with new pin for port in self.all_ports: for bit in range(self.num_wmasks): # input self.add_pin("wmask{}[{}]".format(port, bit), "INPUT") # output - self.add_pin("bank_wmask{}_{}".format(port, bit), "OUTPUT") + self.add_pin("bank_wmask{}[{}]".format(port, bit), "OUTPUT") #clk_buf, regard as input self.add_pin("clk_buf{}".format(port), "INPUT") # Standard supply and ground names @@ -201,13 +201,13 @@ def add_pin_wmask_dff(self): self.ext_supply = {"vdd" : self.vdd_name, "gnd" : self.gnd_name} def add_pin_spare_wen_dff(self): - # do not know why we need this + # lef file use this pin name, should be same with new pin for port in self.all_ports: for bit in range(self.num_spare_cols): # input self.add_pin("spare_wen{}[{}]".format(port, bit), "INPUT") # output - self.add_pin("bank_spare_wen{}_{}".format(port, bit), "OUTPUT") + self.add_pin("bank_spare_wen{}[{}]".format(port, bit), "OUTPUT") #clk_buf, regard as input self.add_pin("clk_buf{}".format(port), "INPUT") # Standard supply and ground names @@ -226,7 +226,7 @@ def add_pin_spare_wen_dff(self): self.ext_supply = {"vdd" : self.vdd_name, "gnd" : self.gnd_name} def add_pin_control(self): - #Do not know why we need this + # lef file use this pin name, should be same with new pin for port in self.all_ports: # Inputs self.add_pin("csb{}".format(port), "INPUT") @@ -1010,7 +1010,7 @@ def add_pins_to_route_spare_wen_dff(self, instance_index=0): # input pins_to_route.append("spare_wen{}[{}]".format(port, bit)) # output - pins_to_route.append("bank_spare_wen{}_{}".format(port, bit)) + pins_to_route.append("bank_spare_wen{}[{}]".format(port, bit)) #clk_buf, regard as input pins_to_route.append("clk_buf{}".format(port)) @@ -1040,7 +1040,7 @@ def add_layout_spare_wen_dff_only_pins(self, add_vias=True, instance_index=0): # output self.add_io_pin(self.spare_wen_dff_insts[port], "dout_{}".format(port, bit), - "bank_spare_wen{}_{}".format(port, bit), + "bank_spare_wen{}[{}]".format(port, bit), start_layer=pin_layer) #clk_buf, regard as input self.add_io_pin(self.spare_wen_dff_insts[port], @@ -1057,7 +1057,7 @@ def add_pins_to_route_wmask_dff(self, instance_index=0): # input pins_to_route.append("wmask{}[{}]".format(port, bit)) # output - pins_to_route.append("bank_wmask{}_{}".format(port, bit)) + pins_to_route.append("bank_wmask{}[{}]".format(port, bit)) #clk_buf, regard as input pins_to_route.append("clk_buf{}".format(port)) @@ -1087,7 +1087,7 @@ def add_layout_wmask_dff_only_pins(self, add_vias=True, instance_index=0): # output self.add_io_pin(self.wmask_dff_insts[port], "dout_{}".format(bit), - "bank_wmask{}_{}".format(port, bit), + "bank_wmask{}[{}]".format(port, bit), start_layer=pin_layer) #clk_buf, regard as input self.add_io_pin(self.wmask_dff_insts[port], @@ -1104,7 +1104,7 @@ def add_pins_to_route_data_dff(self, instance_index=0): # input pins_to_route.append("din{}[{}]".format(port, bit)) # output - pins_to_route.append("bank_din{}_{}".format(port, bit)) + pins_to_route.append("bank_din{}[{}]".format(port, bit)) #clk_buf, regard as input pins_to_route.append("clk_buf{}".format(port)) @@ -1134,7 +1134,7 @@ def add_layout_data_dff_only_pins(self, add_vias=True, instance_index=0): # output self.add_io_pin(self.data_dff_insts[port], "dout_{}".format(bit), - "bank_din{}_{}".format(port, bit), + "bank_din{}[{}]".format(port, bit), start_layer=pin_layer) #clk_buf, regard as input self.add_io_pin(self.data_dff_insts[port], @@ -1151,7 +1151,7 @@ def add_pins_to_route_col_addr_dff(self, instance_index=0): #input pins_to_route.append("addr{}[{}]".format(port, bit)) #output - pins_to_route.append("a{}_{}".format(port, bit)) + pins_to_route.append("a{}[{}]".format(port, bit)) #clk_buf, regard as input pins_to_route.append("clk_buf{}".format(port)) @@ -1181,7 +1181,7 @@ def add_layout_col_addr_dff_only_pins(self, add_vias=True, instance_index=0): #output self.add_io_pin(self.col_addr_dff_insts[port], "dout_{}".format(bit), - "a{}_{}".format(port, bit), + "a{}[{}]".format(port, bit), start_layer=pin_layer) #clk_buf, regard as input self.add_pin(self.col_addr_dff_insts[port], @@ -1198,7 +1198,7 @@ def add_pins_to_route_row_addr_dff(self, instance_index=0): #input pins_to_route.append("addr{}[{}]".format(port, bit + self.col_addr_size)) #output - pins_to_route.append("a{}_{}".format(port, bit + self.col_addr_size)) + pins_to_route.append("a{}[{}]".format(port, bit + self.col_addr_size)) #clk_buf, regard as input pins_to_route.append("clk_buf{}".format(port)) @@ -1231,7 +1231,7 @@ def add_layout_row_addr_dff_only_pins(self, add_vias=True, instance_index=0): #output self.add_io_pin(self.row_addr_dff_insts[port], "dout_{}".format(bit + self.col_addr_size), - "a{}_{}".format(port, bit + self.col_addr_size), + "a{}[{}]".format(port, bit + self.col_addr_size), start_layer=pin_layer) #clk_buf, regard as input self.add_io_pin(self.row_addr_dff_insts[port], @@ -1332,10 +1332,10 @@ def add_pins_to_route_bank(self): pins_to_route.append("rbl_bl{0}".format(port)) for port in self.write_ports: for bit in range(self.word_size + self.num_spare_cols): - pins_to_route.append("bank_din{0}_{1}".format(port, bit)) + pins_to_route.append("bank_din{0}[{1}]".format(port, bit)) for port in self.all_ports: for bit in range(self.bank_addr_size): - pins_to_route.append("addr{0}_{1}".format(port, bit)) + pins_to_route.append("addr{0}[{1}]".format(port, bit)) # For more than one bank, we have a bank select and name # the signals gated_*. @@ -1346,9 +1346,9 @@ def add_pins_to_route_bank(self): for port in self.write_ports: pins_to_route.append("w_en{0}".format(port)) for bit in range(self.num_wmasks): - pins_to_route.append("bank_wmask{0}_{1}".format(port, bit)) + pins_to_route.append("bank_wmask{0}[{1}]".format(port, bit)) for bit in range(self.num_spare_cols): - pins_to_route.append("bank_spare_wen{0}_{1}".format(port, bit)) + pins_to_route.append("bank_spare_wen{0}[{1}]".format(port, bit)) for port in self.all_ports: pins_to_route.append("wl_en{0}".format(port)) @@ -1385,14 +1385,14 @@ def add_layout_bank_only_pins(self, add_vias=True): for bit in range(self.word_size + self.num_spare_cols): self.add_io_pin(self.bank_inst, "din{0}_{1}".format(port, bit), - "bank_din{0}_{1}".format(port, bit), + "bank_din{0}[{1}]".format(port, bit), start_layer=pin_layer) # manuel change position, so not at same y for port in self.all_ports: for bit in range(self.bank_addr_size): self.change_layout_pin_position(self.bank_inst, "addr{0}_{1}".format(port, bit), - "addr{0}_{1}".format(port, bit), + "addr{0}[{1}]".format(port, bit), start_layer=pin_layer, distance=bit) for port in self.read_ports: @@ -1416,12 +1416,12 @@ def add_layout_bank_only_pins(self, add_vias=True): for bit in range(self.num_wmasks): self.add_io_pin(self.bank_inst, "bank_wmask{0}_{1}".format(port, bit), - "bank_wmask{0}_{1}".format(port, bit), + "bank_wmask{0}[{1}]".format(port, bit), start_layer=pin_layer) for bit in range(self.num_spare_cols): self.add_io_pin(self.bank_inst, "bank_spare_wen{0}_{1}".format(port, bit), - "bank_spare_wen{0}_{1}".format(port, bit), + "bank_spare_wen{0}[{1}]".format(port, bit), start_layer=pin_layer) if port in self.all_ports: self.add_io_pin(self.bank_inst, From 8ecdc939d04fab7eb653d7b5972e32068de90487 Mon Sep 17 00:00:00 2001 From: FriedrichWu Date: Wed, 10 Jul 2024 13:30:23 +0200 Subject: [PATCH 04/26] temp_fix of gds/lef cell name not unique problem, not elegant --- compiler/sram_road.py | 67 ++++++++++++++++++++++++++----------------- sram_compiler.py | 12 ++++---- 2 files changed, 47 insertions(+), 32 deletions(-) diff --git a/compiler/sram_road.py b/compiler/sram_road.py index a899a967d..77e7261fd 100644 --- a/compiler/sram_road.py +++ b/compiler/sram_road.py @@ -21,7 +21,7 @@ class sram(): results. We can later add visualizer and other high-level functions as needed. """ - def __init__(self, sram_config=None, name=None): + def __init__(self, sram_config=None, name=None, mod=0): # Create default configs if custom config isn't provided if sram_config is None: @@ -32,9 +32,24 @@ def __init__(self, sram_config=None, name=None): words_per_row=OPTS.words_per_row, num_spare_rows=OPTS.num_spare_rows, num_spare_cols=OPTS.num_spare_cols) - + # maybe this part should be put after set_local_config, so we could calculate the number of port and define file name if name is None: - name = OPTS.output_name + #name = OPTS.output_name + if mod == 0: + name = OPTS.output_name + "_bank_" + "0" + elif mod == 1:# not consider multi-port yet!!!!!! + name = OPTS.output_name + "_control_" + "0" + elif mod == 2: + name = OPTS.output_name + "_row_addr_dff_" + "0" + elif mod == 3: + name = OPTS.output_name + "_col_addr_dff_" + "0" + elif mod == 4: + name = OPTS.output_name + "_data_dff_" + "0" + elif mod == 5: + name = OPTS.output_name + "_wmask_dff_" + "0" + elif mod == 6: + name = OPTS.output_name + "_spare_wen_dff_" + "0" + sram_config.set_local_config(self) @@ -90,7 +105,7 @@ def extended_config_write(self, name): f.write(str(var_name) + " = " + str(var_value)+ "\n") f.close() - def generate_files(self, module_name): + def generate_files(self): """use to generate gds, lef files for one certain layout""" # Import this at the last minute so that the proper tech file # is loaded and the right tools are selected @@ -100,24 +115,24 @@ def generate_files(self, module_name): # Save the spice file start_time = datetime.datetime.now() - spname = OPTS.output_path + self.s.name + module_name + ".sp" + spname = OPTS.output_path + self.s.name + ".sp" debug.print_raw("SP: Writing to {0}".format(spname)) self.sp_write(spname) print_time("Spice writing", datetime.datetime.now(), start_time) # Save trimmed spice file - temp_trim_sp = "{0}trimmed_{1}.sp".format(OPTS.output_path, module_name) + temp_trim_sp = "{0}trimmed.sp".format(OPTS.output_path) self.sp_write(temp_trim_sp, lvs=False, trim=True) if not OPTS.netlist_only: # Write the layout start_time = datetime.datetime.now() - gdsname = OPTS.output_path + self.s.name + module_name + ".gds" + gdsname = OPTS.output_path + self.s.name + ".gds" debug.print_raw("GDS: Writing to {0}".format(gdsname)) self.gds_write(gdsname) if OPTS.check_lvsdrc: - verify.write_drc_script(cell_name=self.s.name + module_name, + verify.write_drc_script(cell_name=self.s.name, gds_name=os.path.basename(gdsname), extract=True, final_verification=True, @@ -126,18 +141,18 @@ def generate_files(self, module_name): # Create a LEF physical model start_time = datetime.datetime.now() - lefname = OPTS.output_path + self.s.name + module_name + ".lef" + lefname = OPTS.output_path + self.s.name + ".lef" debug.print_raw("LEF: Writing to {0}".format(lefname)) self.lef_write(lefname) print_time("LEF", datetime.datetime.now(), start_time) # Save the LVS file start_time = datetime.datetime.now() - lvsname = OPTS.output_path + self.s.name + module_name + ".lvs.sp" + lvsname = OPTS.output_path + self.s.name + ".lvs.sp" debug.print_raw("LVS: Writing to {0}".format(lvsname)) self.sp_write(lvsname, lvs=True) if not OPTS.netlist_only and OPTS.check_lvsdrc: - verify.write_lvs_script(cell_name=self.s.name + module_name, + verify.write_lvs_script(cell_name=self.s.name, gds_name=os.path.basename(gdsname), sp_name=os.path.basename(lvsname), final_verification=True, @@ -148,8 +163,8 @@ def generate_files(self, module_name): if OPTS.use_pex: start_time = datetime.datetime.now() # Output the extracted design if requested - pexname = OPTS.output_path + self.s.name + module_name + ".pex.sp" - spname = OPTS.output_path + self.s.name + module_name + ".sp" + pexname = OPTS.output_path + self.s.name + ".pex.sp" + spname = OPTS.output_path + self.s.name + ".sp" verify.run_pex(self.s.name, gdsname, spname, output=pexname) sp_file = pexname print_time("Extraction", datetime.datetime.now(), start_time) @@ -190,63 +205,63 @@ def save(self, mod=0): self.s.create_netlist_bank() if not OPTS.netlist_only: self.s.create_layout_bank_only() - self.generate_files("_bank") + self.generate_files() elif mod == 1: self.s.create_netlist_control() if not OPTS.netlist_only: for port in self.s.all_ports: self.s.create_layout_control_only(instance_index=port) - self.generate_files("_control_" + str(port)) + self.generate_files() else: for port in self.s.all_ports: - self.generate_files("_control_" + str(port)) + self.generate_files() elif mod == 2: self.s.create_netlist_row_addr_dff() if not OPTS.netlist_only: for port in self.s.all_ports: self.s.create_layout_row_addr_dff_only(instance_index=port) - self.generate_files("_row_addr_dff_" + str(port)) + self.generate_files() else: for port in self.s.all_ports: - self.generate_files("_row_addr_dff_" + str(port)) + self.generate_files() elif mod == 3: if self.s.create_netlist_col_addr_dff() == False: pass#continue # do not need col addr dff elif not OPTS.netlist_only: for port in self.s.all_ports: self.create_layout_col_addr_dff_only(instance_index=port) - self.generate_files("_col_addr_dff_" + str(port)) + self.generate_files() else: - self.generate_files("_col_addr_dff_" + str(port)) + self.generate_files() elif mod == 4: self.s.create_netlist_data_dff() if not OPTS.netlist_only: for port in self.s.all_ports: self.s.create_layout_data_dff_only(instance_index=port) - self.generate_files("_data_dff_" + str(port)) + self.generate_files() else: for port in self.s.all_ports: - self.generate_files("_data_dff_" + str(port)) + self.generate_files() elif mod == 5: if self.s.create_netlist_wmask_dff() == False: pass#continue # do not need wmask dff elif not OPTS.netlist_only: for port in self.s.all_ports: self.s.create_layout_wmask_dff_only(instance_index=port) - self.generate_files("_wmask_dff_" + str(port)) + self.generate_files() else: for port in self.s.all_ports: - self.generate_files("_wmask_dff_" + str(port)) + self.generate_files() elif mod == 6: if self.s.create_netlist_spare_wen_dff() == False: pass#continue # do not need spare wen dff elif not OPTS.netlist_only: for port in self.s.all_ports: self.s.create_layout_spare_wen_dff_only(instance_index=port) - self.generate_files("_spare_wen_dff_" + str(port)) + self.generate_files() else: for port in self.s.all_ports: - self.generate_files("_spare_wen_dff_" + str(port)) + self.generate_files() diff --git a/sram_compiler.py b/sram_compiler.py index b9b0390cb..3d2b7bbdc 100755 --- a/sram_compiler.py +++ b/sram_compiler.py @@ -77,27 +77,27 @@ s.save() del s -s = sram_road.sram() +s = sram_road.sram(mod=1) s.save(mod=1) del s -s = sram_road.sram() +s = sram_road.sram(mod=2) s.save(mod=2) del s -s = sram_road.sram() +s = sram_road.sram(mod=3) s.save(mod=3) del s -s = sram_road.sram() +s = sram_road.sram(mod=4) s.save(mod=4) del s -s = sram_road.sram() +s = sram_road.sram(mod=5) s.save(mod=5) del s -s = sram_road.sram() +s = sram_road.sram(mod=6) s.save(mod=6) # Delete temp files etc. From fb7f92394b70517dcaac1d2ab1e015799a3aac74 Mon Sep 17 00:00:00 2001 From: FriedrichWu Date: Mon, 15 Jul 2024 14:59:25 +0200 Subject: [PATCH 05/26] fix the [0] problem in spare_wen --- compiler/modules/sram_part.py | 84 +++++++++++++++++++++++++---------- 1 file changed, 60 insertions(+), 24 deletions(-) diff --git a/compiler/modules/sram_part.py b/compiler/modules/sram_part.py index a07294ec2..10b99145b 100644 --- a/compiler/modules/sram_part.py +++ b/compiler/modules/sram_part.py @@ -80,7 +80,10 @@ def add_pin_bank(self): for bit in range(self.num_wmasks): self.add_pin("bank_wmask{0}[{1}]".format(port, bit), "INPUT") for bit in range(self.num_spare_cols): - self.add_pin("bank_spare_wen{0}[{1}]".format(port, bit), "INPUT") + if self.num_spare_cols ==1: + self.add_pin("bank_spare_wen{0}".format(port), "INPUT") + else: + self.add_pin("bank_spare_wen{0}[{1}]".format(port, bit), "INPUT") for port in self.all_ports: self.add_pin("wl_en{0}".format(port), "INPUT") @@ -204,10 +207,16 @@ def add_pin_spare_wen_dff(self): # lef file use this pin name, should be same with new pin for port in self.all_ports: for bit in range(self.num_spare_cols): - # input - self.add_pin("spare_wen{}[{}]".format(port, bit), "INPUT") - # output - self.add_pin("bank_spare_wen{}[{}]".format(port, bit), "OUTPUT") + if self.num_spare_cols == 1: + # input + self.add_pin("spare_wen{}".format(port), "INPUT") + # output + self.add_pin("bank_spare_wen{}".format(port), "OUTPUT") + else: + # input + self.add_pin("spare_wen{}[{}]".format(port, bit), "INPUT") + # output + self.add_pin("bank_spare_wen{}[{}]".format(port, bit), "OUTPUT") #clk_buf, regard as input self.add_pin("clk_buf{}".format(port), "INPUT") # Standard supply and ground names @@ -1007,10 +1016,16 @@ def add_pins_to_route_spare_wen_dff(self, instance_index=0): port = instance_index for bit in range(self.num_spare_cols): - # input - pins_to_route.append("spare_wen{}[{}]".format(port, bit)) - # output - pins_to_route.append("bank_spare_wen{}[{}]".format(port, bit)) + if self.num_spare_cols == 1: + # input + pins_to_route.append("spare_wen{}".format(port)) + # output + pins_to_route.append("bank_spare_wen{}".format(port)) + else: + # input + pins_to_route.append("spare_wen{}[{}]".format(port, bit)) + # output + pins_to_route.append("bank_spare_wen{}[{}]".format(port, bit)) #clk_buf, regard as input pins_to_route.append("clk_buf{}".format(port)) @@ -1032,16 +1047,28 @@ def add_layout_spare_wen_dff_only_pins(self, add_vias=True, instance_index=0): pin_layer = self.pwr_grid_layers[0] for bit in range(self.num_spare_cols): - # input - self.add_io_pin(self.spare_wen_dff_insts[port], - "din_{}".format(bit), # old name - "spare_wen{}[{}]".format(port, bit), # new name - start_layer=pin_layer) - # output - self.add_io_pin(self.spare_wen_dff_insts[port], - "dout_{}".format(port, bit), - "bank_spare_wen{}[{}]".format(port, bit), - start_layer=pin_layer) + if self.num_spare_cols == 1: + # input + self.add_io_pin(self.spare_wen_dff_insts[port], + "din_{}".format(bit), # old name + "spare_wen{}".format(port), # new name + start_layer=pin_layer) + # output + self.add_io_pin(self.spare_wen_dff_insts[port], + "dout_{}".format(bit), + "bank_spare_wen{}".format(port), + start_layer=pin_layer) + else: + # input + self.add_io_pin(self.spare_wen_dff_insts[port], + "din_{}".format(bit), # old name + "spare_wen{}[{}]".format(port, bit), # new name + start_layer=pin_layer) + # output + self.add_io_pin(self.spare_wen_dff_insts[port], + "dout_{}".format(bit), + "bank_spare_wen{}[{}]".format(port, bit), + start_layer=pin_layer) #clk_buf, regard as input self.add_io_pin(self.spare_wen_dff_insts[port], "clk", @@ -1348,7 +1375,10 @@ def add_pins_to_route_bank(self): for bit in range(self.num_wmasks): pins_to_route.append("bank_wmask{0}[{1}]".format(port, bit)) for bit in range(self.num_spare_cols): - pins_to_route.append("bank_spare_wen{0}[{1}]".format(port, bit)) + if self.num_spare_cols == 1: + pins_to_route.append("bank_spare_wen{0}".format(port, bit)) + else: + pins_to_route.append("bank_spare_wen{0}[{1}]".format(port, bit)) for port in self.all_ports: pins_to_route.append("wl_en{0}".format(port)) @@ -1419,10 +1449,16 @@ def add_layout_bank_only_pins(self, add_vias=True): "bank_wmask{0}[{1}]".format(port, bit), start_layer=pin_layer) for bit in range(self.num_spare_cols): - self.add_io_pin(self.bank_inst, - "bank_spare_wen{0}_{1}".format(port, bit), - "bank_spare_wen{0}[{1}]".format(port, bit), - start_layer=pin_layer) + if self.num_spare_cols == 1: + self.add_io_pin(self.bank_inst, + "bank_spare_wen{0}_{1}".format(port, bit), + "bank_spare_wen{0}".format(port), + start_layer=pin_layer) + else: + self.add_io_pin(self.bank_inst, + "bank_spare_wen{0}_{1}".format(port, bit), + "bank_spare_wen{0}[{1}]".format(port, bit), + start_layer=pin_layer) if port in self.all_ports: self.add_io_pin(self.bank_inst, "wl_en{0}".format(port), From e6ca825157af32bee2f90b8b3a07307e80433fe9 Mon Sep 17 00:00:00 2001 From: FriedrichWu Date: Mon, 22 Jul 2024 10:44:06 +0200 Subject: [PATCH 06/26] recrusive placement of dffs-naive edition --- compiler/modules/sram_new.py | 1481 ++++++++++++++++++++++++++++++++++ compiler/sram_new_test.py | 249 ++++++ sram_compiler.py | 8 +- 3 files changed, 1734 insertions(+), 4 deletions(-) create mode 100644 compiler/modules/sram_new.py create mode 100644 compiler/sram_new_test.py diff --git a/compiler/modules/sram_new.py b/compiler/modules/sram_new.py new file mode 100644 index 000000000..aa813084c --- /dev/null +++ b/compiler/modules/sram_new.py @@ -0,0 +1,1481 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2024 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. +# +# This aim to change the position of the submodule +import datetime +from math import ceil +from importlib import import_module, reload +from openram import debug +from openram.base import vector +from openram.base import channel_route +from openram.base import design +from openram.base import verilog +from openram.base import lef +from openram.sram_factory import factory +from openram.tech import spice +from openram import OPTS, print_time + + +class sram_1bank(design, verilog, lef): + """ + Procedures specific to a one bank SRAM. + """ + def __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 openram.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 + + # delay control logic does not have RBLs + self.has_rbl = OPTS.control_logic != "control_logic_delay" + + 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_recrusive(self, position_add=0): + """ Layout creation """ + start_time = datetime.datetime.now() + self.place_instances_changeable(position_add=position_add) + 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_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]) + + from openram.router import supply_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.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=None): + """ + 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 openram.router import signal_escape_router as router + rtr = router(layers=self.m3_stack, + bbox=bbox, + design=self) + rtr.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_module("." + OPTS.control_logic, "openram.modules")) + 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)) + if self.has_rbl: + 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)) + if self.has_rbl: + 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): + """ + This adds the modules for a single bank SRAM with control + logic. + """ + + self.bank_inst=self.create_bank(0) + + self.control_logic_insts = self.create_control_logic() + + self.row_addr_dff_insts = self.create_row_addr_dff() + + if self.col_addr_dff: + self.col_addr_dff_insts = self.create_col_addr_dff() + + if self.write_size != self.word_size: + self.wmask_dff_insts = self.create_wmask_dff() + self.data_dff_insts = self.create_data_dff() + else: + self.data_dff_insts = self.create_data_dff() + + if self.num_spare_cols: + self.spare_wen_dff_insts = self.create_spare_wen_dff() + else: + self.num_spare_cols = 0 + + def place_instances_changeable(self, position_add=0): + """ + This places the instances for a single bank SRAM with control + logic and up to 2 ports, but recrusive edition + """ + + # No orientation or offset + self.place_bank(self.bank_inst, [0, 0], 1, 1) + + # The control logic is placed such that the vertical center (between the delay/RBL and + # the actual control logic is aligned with the vertical center of the bank (between + # the sense amps/column mux and cell array) + # The x-coordinate is placed to allow a single clock wire (plus an extra pitch) + # up to the row address DFFs. + self.control_pos = [None] * len(self.all_ports) + self.row_addr_pos = [None] * len(self.all_ports) + + # DFFs are placd on their own + self.col_addr_pos = [None] * len(self.all_ports) + self.wmask_pos = [None] * len(self.all_ports) + self.spare_wen_pos = [None] * len(self.all_ports) + self.data_pos = [None] * len(self.all_ports) + + # These positions utilize the channel route sizes. + # FIXME: Auto-compute these rather than manual computation. + # If a horizontal channel, they rely on the vertical channel non-preferred (contacted) pitch. + # If a vertical channel, they rely on the horizontal channel non-preferred (contacted) pitch. + # So, m3 non-pref pitch means that this is routed on the m2 layer. + self.data_bus_gap = self.m4_nonpref_pitch * 2 + + # Spare wen are on a separate layer so not included + # Start with 1 track minimum + self.data_bus_size = [1] * len(self.all_ports) + self.col_addr_bus_size = [1] * len(self.all_ports) + for port in self.all_ports: + # The column address wires are routed separately from the data bus and will always be smaller. + # All ports need the col addr flops + self.col_addr_bus_size[port] = self.col_addr_size * self.m4_nonpref_pitch + # Write ports need the data input flops and write mask flops + if port in self.write_ports: + self.data_bus_size[port] += self.num_wmasks + self.word_size + # This is for the din pins that get routed in the same channel + # when we have dout and din together + if port in self.readwrite_ports: + self.data_bus_size[port] += self.word_size + # Convert to length + self.data_bus_size[port] *= self.m4_nonpref_pitch + # Add the gap in unit length + self.data_bus_size[port] += self.data_bus_gap + + # The control and row addr flops are independent of any bus widths. + self.place_control() + self.place_row_addr_dffs() + + # Place with an initial wide channel (from above) + self.place_dffs() + + # Route the channel and set to the new data bus size + # We need to temporarily add some pins for the x offsets + # but we'll remove them so that they have the right y + # offsets after the DFF placement. + self.add_layout_pins(add_vias=False) + self.route_dffs(add_routes=False) + self.remove_layout_pins() + + for port in self.all_ports: + # Add the extra position + self.data_bus_size[port] += position_add + + # Re-place with the new channel size + self.place_dffs() + + def place_instances(self): + """ + This places the instances for a single bank SRAM with control + logic and up to 2 ports. + """ + + # No orientation or offset + self.place_bank(self.bank_inst, [0, 0], 1, 1) + + # The control logic is placed such that the vertical center (between the delay/RBL and + # the actual control logic is aligned with the vertical center of the bank (between + # the sense amps/column mux and cell array) + # The x-coordinate is placed to allow a single clock wire (plus an extra pitch) + # up to the row address DFFs. + self.control_pos = [None] * len(self.all_ports) + self.row_addr_pos = [None] * len(self.all_ports) + + # DFFs are placd on their own + self.col_addr_pos = [None] * len(self.all_ports) + self.wmask_pos = [None] * len(self.all_ports) + self.spare_wen_pos = [None] * len(self.all_ports) + self.data_pos = [None] * len(self.all_ports) + + # These positions utilize the channel route sizes. + # FIXME: Auto-compute these rather than manual computation. + # If a horizontal channel, they rely on the vertical channel non-preferred (contacted) pitch. + # If a vertical channel, they rely on the horizontal channel non-preferred (contacted) pitch. + # So, m3 non-pref pitch means that this is routed on the m2 layer. + self.data_bus_gap = self.m4_nonpref_pitch * 2 + + # Spare wen are on a separate layer so not included + # Start with 1 track minimum + self.data_bus_size = [1] * len(self.all_ports) + self.col_addr_bus_size = [1] * len(self.all_ports) + for port in self.all_ports: + # The column address wires are routed separately from the data bus and will always be smaller. + # All ports need the col addr flops + self.col_addr_bus_size[port] = self.col_addr_size * self.m4_nonpref_pitch + # Write ports need the data input flops and write mask flops + if port in self.write_ports: + self.data_bus_size[port] += self.num_wmasks + self.word_size + # This is for the din pins that get routed in the same channel + # when we have dout and din together + if port in self.readwrite_ports: + self.data_bus_size[port] += self.word_size + # Convert to length + self.data_bus_size[port] *= self.m4_nonpref_pitch + # Add the gap in unit length + self.data_bus_size[port] += self.data_bus_gap + + # The control and row addr flops are independent of any bus widths. + self.place_control() + self.place_row_addr_dffs() + + # Place with an initial wide channel (from above) + self.place_dffs() + + # Route the channel and set to the new data bus size + # We need to temporarily add some pins for the x offsets + # but we'll remove them so that they have the right y + # offsets after the DFF placement. + self.add_layout_pins(add_vias=False) + self.route_dffs(add_routes=False) + self.remove_layout_pins() + + # Re-place with the new channel size + self.place_dffs() + + def place_row_addr_dffs(self): + """ + Must be run after place control logic. + """ + port = 0 + # The row address bits are placed above the control logic aligned on the right. + x_offset = self.control_logic_insts[port].rx() - self.row_addr_dff_insts[port].width + # It is above the control logic and the predecoder array + y_offset = max(self.control_logic_insts[port].uy(), self.bank.predecoder_top) + + self.row_addr_pos[port] = vector(x_offset, y_offset) + self.row_addr_dff_insts[port].place(self.row_addr_pos[port]) + + if len(self.all_ports)>1: + port = 1 + # The row address bits are placed above the control logic aligned on the left. + x_offset = self.control_pos[port].x - self.control_logic_insts[port].width + self.row_addr_dff_insts[port].width + # If it can be placed above the predecoder and below the control logic, do it + y_offset = self.bank.predecoder_bottom + self.row_addr_pos[port] = vector(x_offset, y_offset) + self.row_addr_dff_insts[port].place(self.row_addr_pos[port], mirror="XY") + + def place_control(self): + port = 0 + + # This includes 2 M2 pitches for the row addr clock line. + # The delay line is aligned with the bitcell array while the control logic is aligned with the port_data + # using the control_logic_center value. + self.control_pos[port] = vector(-self.control_logic_insts[port].width - 2 * self.m2_pitch, + self.bank.bank_array_ll.y - self.control_logic_insts[port].mod.control_logic_center.y) + self.control_logic_insts[port].place(self.control_pos[port]) + if len(self.all_ports) > 1: + port = 1 + # This includes 2 M2 pitches for the row addr clock line + # The delay line is aligned with the bitcell array while the control logic is aligned with the port_data + # using the control_logic_center value. + self.control_pos[port] = vector(self.bank_inst.rx() + self.control_logic_insts[port].width + 2 * self.m2_pitch, + self.bank.bank_array_ur.y + + self.control_logic_insts[port].height + - self.control_logic_insts[port].height + + self.control_logic_insts[port].mod.control_logic_center.y) + self.control_logic_insts[port].place(self.control_pos[port], mirror="XY") + + def place_dffs(self): + """ + Place the col addr, data, wmask, and spare data DFFs. + This can be run more than once after we recompute the channel width. + """ + + port = 0 + # Add the col address flops below the bank to the right of the control logic + x_offset = self.control_logic_insts[port].rx() + self.dff.width + # Place it a data bus below the x-axis, but at least as low as the control logic to not block + # the control logic signals + y_offset = min(-self.data_bus_size[port] - self.dff.height, + self.control_logic_insts[port].by()) + if self.col_addr_dff: + self.col_addr_pos[port] = vector(x_offset, + y_offset) + self.col_addr_dff_insts[port].place(self.col_addr_pos[port]) + x_offset = self.col_addr_dff_insts[port].rx() + else: + self.col_addr_pos[port] = vector(x_offset, 0) + + if port in self.write_ports: + 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) + self.wmask_dff_insts[port].place(self.wmask_pos[port]) + x_offset = self.wmask_dff_insts[port].rx() + + # Add the data flops below the write mask flops. + self.data_pos[port] = vector(x_offset, + y_offset) + self.data_dff_insts[port].place(self.data_pos[port]) + x_offset = self.data_dff_insts[port].rx() + + # Add spare write enable flops to the right of data flops since the spare columns + # will be on the right + if self.num_spare_cols: + self.spare_wen_pos[port] = vector(x_offset, + y_offset) + self.spare_wen_dff_insts[port].place(self.spare_wen_pos[port]) + x_offset = self.spare_wen_dff_insts[port].rx() + + else: + self.wmask_pos[port] = vector(x_offset, y_offset) + self.data_pos[port] = vector(x_offset, y_offset) + self.spare_wen_pos[port] = vector(x_offset, y_offset) + + if len(self.all_ports) > 1: + port = 1 + + # Add the col address flops below the bank to the right of the control logic + x_offset = self.control_logic_insts[port].lx() - 2 * self.dff.width + # Place it a data bus below the x-axis, but at least as high as the control logic to not block + # the control logic signals + y_offset = max(self.bank.height + self.data_bus_size[port] + self.dff.height, + self.control_logic_insts[port].uy() - self.dff.height) + if self.col_addr_dff: + self.col_addr_pos[port] = vector(x_offset, + y_offset) + self.col_addr_dff_insts[port].place(self.col_addr_pos[port], mirror="XY") + x_offset = self.col_addr_dff_insts[port].lx() + else: + self.col_addr_pos[port] = vector(x_offset, y_offset) + + if port in self.write_ports: + # Add spare write enable flops to the right of the data flops since the spare + # columns will be on the left + if self.num_spare_cols: + self.spare_wen_pos[port] = vector(x_offset - self.spare_wen_dff_insts[port].width, + y_offset) + 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 != 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) + self.wmask_dff_insts[port].place(self.wmask_pos[port], mirror="MX") + x_offset = self.wmask_dff_insts[port].lx() + + # Add the data flops below the write mask flops. + self.data_pos[port] = vector(x_offset - self.data_dff_insts[port].width, + y_offset) + self.data_dff_insts[port].place(self.data_pos[port], mirror="MX") + else: + self.wmask_pos[port] = vector(x_offset, y_offset) + self.data_pos[port] = vector(x_offset, y_offset) + self.spare_wen_pos[port] = vector(x_offset, y_offset) + + def add_layout_pins(self, add_vias=True): + """ + Add the top-level pins for a single bank SRAM with control. + """ + for port in self.all_ports: + # Hack: If we are escape routing, set the pin layer to + # None so that we will start from the pin layer + # Otherwise, set it as the pin layer so that no vias are added. + # Otherwise, when we remove pins to move the dff array dynamically, + # we will leave some remaining vias when the pin locations change. + if add_vias: + pin_layer = None + else: + pin_layer = self.pwr_grid_layers[0] + + # Connect the control pins as inputs + for signal in self.control_logic_inputs[port]: + if signal.startswith("rbl"): + continue + self.add_io_pin(self.control_logic_insts[port], + signal, + signal + "{}".format(port), + start_layer=pin_layer) + + if port in self.write_ports: + for bit in range(self.word_size + self.num_spare_cols): + self.add_io_pin(self.data_dff_insts[port], + "din_{}".format(bit), + "din{0}[{1}]".format(port, bit), + start_layer=pin_layer) + + if port in self.readwrite_ports or port in self.read_ports: + for bit in range(self.word_size + self.num_spare_cols): + self.add_io_pin(self.bank_inst, + "dout{0}_{1}".format(port, bit), + "dout{0}[{1}]".format(port, bit), + start_layer=pin_layer) + + for bit in range(self.col_addr_size): + self.add_io_pin(self.col_addr_dff_insts[port], + "din_{}".format(bit), + "addr{0}[{1}]".format(port, bit), + start_layer=pin_layer) + + for bit in range(self.row_addr_size): + self.add_io_pin(self.row_addr_dff_insts[port], + "din_{}".format(bit), + "addr{0}[{1}]".format(port, bit + self.col_addr_size), + start_layer=pin_layer) + + if port in self.write_ports: + 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), + "wmask{0}[{1}]".format(port, bit), + start_layer=pin_layer) + + if port in self.write_ports: + if self.num_spare_cols == 1: + self.add_io_pin(self.spare_wen_dff_insts[port], + "din_{}".format(0), + "spare_wen{0}".format(port), + start_layer=pin_layer) + else: + for bit in range(self.num_spare_cols): + self.add_io_pin(self.spare_wen_dff_insts[port], + "din_{}".format(bit), + "spare_wen{0}[{1}]".format(port, bit), + start_layer=pin_layer) + + def route_layout(self): + """ Route a single bank SRAM """ + + self.route_clk() + + self.route_control_logic() + + self.route_row_addr_dff() + + self.route_dffs() + + # We add the vias to M3 before routing supplies because + # they might create some blockages + self.add_layout_pins() + + # Some technologies have an isolation + self.add_dnwell(inflate=2.5) + + init_bbox = self.get_bbox() + # Route the supplies together and/or to the ring/stripes. + # Route the pins to the perimeter + if OPTS.perimeter_pins: + # We now route the escape routes far enough out so that they will + # reach past the power ring or stripes on the sides + self.route_escape_pins(init_bbox) + if OPTS.route_supplies: + self.route_supplies(init_bbox) + + + def route_dffs(self, add_routes=True): + + for port in self.all_ports: + self.route_dff(port, add_routes) + + def route_dff(self, port, add_routes): + + # This is only done when we add_routes because the data channel will be larger + # so that can be used for area estimation. + if add_routes: + self.route_col_addr_dffs(port) + + self.route_data_dffs(port, add_routes) + + def route_col_addr_dffs(self, port): + + route_map = [] + + # column mux dff is routed on it's own since it is to the far end + # decoder inputs are min pitch M2, so need to use lower layer stack + if self.col_addr_size > 0: + dff_names = ["dout_{}".format(x) for x in range(self.col_addr_size)] + dff_pins = [self.col_addr_dff_insts[port].get_pin(x) for x in dff_names] + bank_names = ["addr{0}_{1}".format(port, x) for x in range(self.col_addr_size)] + bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] + route_map.extend(list(zip(bank_pins, dff_pins))) + + if len(route_map) > 0: + + # This layer stack must be different than the data dff layer stack + layer_stack = self.m1_stack + + if port == 0: + offset = vector(self.control_logic_insts[port].rx() + self.dff.width, + - self.data_bus_size[port] + 2 * self.m3_pitch) + cr = channel_route(netlist=route_map, + offset=offset, + layer_stack=layer_stack, + parent=self) + # This causes problem in magic since it sometimes cannot extract connectivity of instances + # with no active devices. + self.add_inst(cr.name, cr) + self.connect_inst([]) + # self.add_flat_inst(cr.name, cr) + else: + offset = vector(0, + self.bank.height + self.m3_pitch) + cr = channel_route(netlist=route_map, + offset=offset, + layer_stack=layer_stack, + parent=self) + # This causes problem in magic since it sometimes cannot extract connectivity of instances + # with no active devices. + self.add_inst(cr.name, cr) + self.connect_inst([]) + # self.add_flat_inst(cr.name, cr) + + def route_data_dffs(self, port, add_routes): + route_map = [] + + # wmask dff + if self.num_wmasks > 0 and port in self.write_ports: + dff_names = ["dout_{}".format(x) for x in range(self.num_wmasks)] + dff_pins = [self.wmask_dff_insts[port].get_pin(x) for x in dff_names] + bank_names = ["bank_wmask{0}_{1}".format(port, x) for x in range(self.num_wmasks)] + bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] + route_map.extend(list(zip(bank_pins, dff_pins))) + + if port in self.write_ports: + # synchronized inputs from data dff + dff_names = ["dout_{}".format(x) for x in range(self.word_size + self.num_spare_cols)] + dff_pins = [self.data_dff_insts[port].get_pin(x) for x in dff_names] + bank_names = ["din{0}_{1}".format(port, x) for x in range(self.word_size + self.num_spare_cols)] + bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] + route_map.extend(list(zip(bank_pins, dff_pins))) + + # spare wen dff + if self.num_spare_cols > 0 and port in self.write_ports: + dff_names = ["dout_{}".format(x) for x in range(self.num_spare_cols)] + dff_pins = [self.spare_wen_dff_insts[port].get_pin(x) for x in dff_names] + bank_names = ["bank_spare_wen{0}_{1}".format(port, x) for x in range(self.num_spare_cols)] + bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] + route_map.extend(list(zip(bank_pins, dff_pins))) + + if len(route_map) > 0: + + # This layer stack must be different than the column addr dff layer stack + layer_stack = self.m3_stack + if port == 0: + # This is relative to the bank at 0,0 or the s_en which is routed on M3 also + if "s_en" in self.control_logic_insts[port].mod.pin_map: + y_bottom = min(0, self.control_logic_insts[port].get_pin("s_en").by()) + else: + y_bottom = 0 + + y_offset = y_bottom - self.data_bus_size[port] + 2 * self.m3_pitch + offset = vector(self.control_logic_insts[port].rx() + self.dff.width, + y_offset) + cr = channel_route(netlist=route_map, + offset=offset, + layer_stack=layer_stack, + parent=self) + if add_routes: + # This causes problem in magic since it sometimes cannot extract connectivity of instances + # with no active devices. + self.add_inst(cr.name, cr) + self.connect_inst([]) + # self.add_flat_inst(cr.name, cr) + else: + self.data_bus_size[port] = max(cr.height, self.col_addr_bus_size[port]) + self.data_bus_gap + else: + if "s_en" in self.control_logic_insts[port].mod.pin_map: + y_top = max(self.bank.height, self.control_logic_insts[port].get_pin("s_en").uy()) + else: + y_top = self.bank.height + y_offset = y_top + self.m3_pitch + offset = vector(0, + y_offset) + cr = channel_route(netlist=route_map, + offset=offset, + layer_stack=layer_stack, + parent=self) + if add_routes: + # This causes problem in magic since it sometimes cannot extract connectivity of instances + # with no active devices. + self.add_inst(cr.name, cr) + self.connect_inst([]) + # self.add_flat_inst(cr.name, cr) + else: + self.data_bus_size[port] = max(cr.height, self.col_addr_bus_size[port]) + self.data_bus_gap + + def route_clk(self): + """ Route the clock network """ + + # This is the actual input to the SRAM + for port in self.all_ports: + # Connect all of these clock pins to the clock in the central bus + # This is something like a "spine" clock distribution. The two spines + # are clk_buf and clk_buf_bar + control_clk_buf_pin = self.control_logic_insts[port].get_pin("clk_buf") + control_clk_buf_pos = control_clk_buf_pin.center() + + # This uses a metal2 track to the right (for port0) of the control/row addr DFF + # to route vertically. For port1, it is to the left. + row_addr_clk_pin = self.row_addr_dff_insts[port].get_pin("clk") + if port % 2: + control_clk_buf_pos = control_clk_buf_pin.lc() + row_addr_clk_pos = row_addr_clk_pin.lc() + mid1_pos = vector(self.row_addr_dff_insts[port].lx() - self.m2_pitch, + row_addr_clk_pos.y) + else: + control_clk_buf_pos = control_clk_buf_pin.rc() + row_addr_clk_pos = row_addr_clk_pin.rc() + mid1_pos = vector(self.row_addr_dff_insts[port].rx() + self.m2_pitch, + row_addr_clk_pos.y) + + # This is the steiner point where the net branches out + clk_steiner_pos = vector(mid1_pos.x, control_clk_buf_pos.y) + self.add_path(control_clk_buf_pin.layer, [control_clk_buf_pos, clk_steiner_pos]) + self.add_via_stack_center(from_layer=control_clk_buf_pin.layer, + to_layer="m2", + offset=clk_steiner_pos) + + # Note, the via to the control logic is taken care of above + self.add_wire(self.m2_stack[::-1], + [row_addr_clk_pos, mid1_pos, clk_steiner_pos]) + + if self.col_addr_dff: + dff_clk_pin = self.col_addr_dff_insts[port].get_pin("clk") + dff_clk_pos = dff_clk_pin.center() + mid_pos = vector(clk_steiner_pos.x, dff_clk_pos.y) + self.add_wire(self.m2_stack[::-1], + [dff_clk_pos, mid_pos, clk_steiner_pos]) + elif port in self.write_ports: + data_dff_clk_pin = self.data_dff_insts[port].get_pin("clk") + data_dff_clk_pos = data_dff_clk_pin.center() + mid_pos = vector(clk_steiner_pos.x, data_dff_clk_pos.y) + # In some designs, the steiner via will be too close to the mid_pos via + # so make the wire as wide as the contacts + self.add_path("m2", + [mid_pos, clk_steiner_pos], + width=max(self.m2_via.width, self.m2_via.height)) + self.add_wire(self.m2_stack[::-1], + [data_dff_clk_pos, mid_pos, clk_steiner_pos]) + + def route_control_logic(self): + """ + Route the control logic pins that are not inputs + """ + + for port in self.all_ports: + for signal in self.control_logic_outputs[port]: + # The clock gets routed separately and is not a part of the bank + if "clk" in signal: + continue + src_pin = self.control_logic_insts[port].get_pin(signal) + dest_pin = self.bank_inst.get_pin(signal + "{}".format(port)) + self.connect_vbus(src_pin, dest_pin) + + if self.has_rbl: + for port in self.all_ports: + # Only input (besides pins) is the replica bitline + src_pin = self.control_logic_insts[port].get_pin("rbl_bl") + dest_pin = self.bank_inst.get_pin("rbl_bl_{0}_{0}".format(port)) + self.add_wire(self.m3_stack, + [src_pin.center(), vector(src_pin.cx(), dest_pin.cy()), dest_pin.rc()]) + self.add_via_stack_center(from_layer=src_pin.layer, + to_layer="m4", + offset=src_pin.center()) + self.add_via_stack_center(from_layer=dest_pin.layer, + to_layer="m3", + offset=dest_pin.center()) + + def route_row_addr_dff(self): + """ + Connect the output of the row flops to the bank pins + """ + for port in self.all_ports: + for bit in range(self.row_addr_size): + flop_name = "dout_{}".format(bit) + bank_name = "addr{0}_{1}".format(port, bit + self.col_addr_size) + flop_pin = self.row_addr_dff_insts[port].get_pin(flop_name) + bank_pin = self.bank_inst.get_pin(bank_name) + flop_pos = flop_pin.center() + bank_pos = bank_pin.center() + mid_pos = vector(bank_pos.x, flop_pos.y) + self.add_via_stack_center(from_layer=flop_pin.layer, + to_layer="m3", + offset=flop_pos) + self.add_path("m3", [flop_pos, mid_pos]) + self.add_via_stack_center(from_layer=bank_pin.layer, + to_layer="m3", + offset=mid_pos) + self.add_path(bank_pin.layer, [mid_pos, bank_pos]) + + 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. + """ + return + for n in self.control_logic_outputs[0]: + pin = self.control_logic_insts[0].get_pin(n) + self.add_label(text=n, + layer=pin.layer, + offset=pin.center()) + + def graph_exclude_data_dff(self): + """ + Removes data dff and wmask dff (if applicable) from search graph. + """ + # 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 != self.word_size: + for inst in self.wmask_dff_insts: + self.graph_inst_exclude.add(inst) + if self.num_spare_cols: + for inst in self.spare_wen_dff_insts: + self.graph_inst_exclude.add(inst) + + def graph_exclude_addr_dff(self): + """ + Removes data dff from search graph. + """ + # Address is considered not part of the critical path, subjectively removed + for inst in self.row_addr_dff_insts: + self.graph_inst_exclude.add(inst) + + if self.col_addr_dff: + for inst in self.col_addr_dff_insts: + self.graph_inst_exclude.add(inst) + + def graph_exclude_ctrl_dffs(self): + """ + Exclude dffs for CSB, WEB, etc from graph + """ + # Insts located in control logic, exclusion function called here + for inst in self.control_logic_insts: + inst.mod.graph_exclude_dffs() + + def get_cell_name(self, inst_name, row, col): + """ + Gets the spice name of the target bitcell. + """ + # Sanity check in case it was forgotten + if inst_name.find("x") != 0: + inst_name = "x" + inst_name + return self.bank_inst.mod.get_cell_name(inst_name + "{}x".format(OPTS.hier_seperator) + self.bank_inst.name, row, col) + + def get_bank_num(self, inst_name, row, col): + return 0 diff --git a/compiler/sram_new_test.py b/compiler/sram_new_test.py new file mode 100644 index 000000000..228b3874d --- /dev/null +++ b/compiler/sram_new_test.py @@ -0,0 +1,249 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2024 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 os +import shutil +import datetime +import copy +from openram import debug +from openram import sram_config as config +from openram import OPTS, print_time + + +class sram(): + """ + This is not a design module, but contains an SRAM design instance. + It could later try options of number of banks and organization to compare + results. + We can later add visualizer and other high-level functions as needed. + """ + def __init__(self, sram_config=None, name=None): + + # Create default configs if custom config isn't provided + if sram_config is None: + sram_config = config(word_size=OPTS.word_size, + num_words=OPTS.num_words, + write_size=OPTS.write_size, + num_banks=OPTS.num_banks, + words_per_row=OPTS.words_per_row, + num_spare_rows=OPTS.num_spare_rows, + num_spare_cols=OPTS.num_spare_cols) + + if name is None: + name = OPTS.output_name + + sram_config.set_local_config(self) + + # reset the static duplicate name checker for unit tests + # in case we create more than one SRAM + from openram.base import design + design.name_map=[] + + debug.info(2, "create sram of size {0} with {1} num of words {2} banks".format(self.word_size, + self.num_words, + self.num_banks)) + start_time = datetime.datetime.now() + + self.name = name + + from openram.modules.sram_new import sram_1bank as sram + + self.s = sram(name, sram_config) + self.s.create_netlist()# not placed & routed jet + #self.s_tmp = copy.deepcopy(self.s) # need deepcopy + if not OPTS.netlist_only: + i = 0 + supply_route_not_found = False + #self.s_tmp = copy.deepcopy(self.s) # need deepcopy + while i < 100: + print("current iteration: {0}".format(i)) + try: + self.s.create_layout_recrusive(position_add=i) + except AssertionError as e: + supply_route_not_found = True + if i == 99:# failed in rounting + debug.error("Failed in rounting", -1) + break + + + if (supply_route_not_found): + #self.s_tmp = copy.deepcopy(self.s) # need deepcopy + del self.s + self.s = sram(name, sram_config) + self.s.create_netlist() + i = i + 1 + supply_route_not_found = False + else:# successfully routed + #self.s = copy.deepcopy(self.s_tmp) # need deepcopy + break + + if not OPTS.is_unit_test: + print_time("SRAM creation", datetime.datetime.now(), start_time) + + def get_sp_name(self): + if OPTS.use_pex: + # Use the extracted spice file + return self.pex_name + else: + # Use generated spice file for characterization + return self.sp_name + + def sp_write(self, name, lvs=False, trim=False): + self.s.sp_write(name, lvs, trim) + + def lef_write(self, name): + self.s.lef_write(name) + + def gds_write(self, name): + self.s.gds_write(name) + + def verilog_write(self, name): + self.s.verilog_write(name) + if self.num_banks != 1: + from openram.modules.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. + Include defaults and anything changed by input config.""" + f = open(name, "w") + var_dict = dict((name, getattr(OPTS, name)) for name in dir(OPTS) if not name.startswith('__') and not callable(getattr(OPTS, name))) + for var_name, var_value in var_dict.items(): + if isinstance(var_value, str): + f.write(str(var_name) + " = " + "\"" + str(var_value) + "\"\n") + else: + f.write(str(var_name) + " = " + str(var_value)+ "\n") + f.close() + + def save(self): + """ Save all the output files while reporting time to do it as well. """ + + # Import this at the last minute so that the proper tech file + # is loaded and the right tools are selected + from openram import verify + from openram.characterizer import functional + from openram.characterizer import delay + + # Save the spice file + start_time = datetime.datetime.now() + spname = OPTS.output_path + self.s.name + ".sp" + debug.print_raw("SP: Writing to {0}".format(spname)) + self.sp_write(spname) + + # Save a functional simulation file with default period + functional(self.s, + spname, + cycles=200, + output_path=OPTS.output_path) + print_time("Spice writing", datetime.datetime.now(), start_time) + + # Save stimulus and measurement file + start_time = datetime.datetime.now() + debug.print_raw("DELAY: Writing stimulus...") + d = delay(self.s, spname, ("TT", 5, 25), output_path=OPTS.output_path) + if (self.s.num_spare_rows == 0): + probe_address = "1" * self.s.addr_size + else: + probe_address = "0" + "1" * (self.s.addr_size - 1) + probe_data = self.s.word_size - 1 + d.analysis_init(probe_address, probe_data) + d.targ_read_ports.extend(self.s.read_ports) + d.targ_write_ports = [self.s.write_ports[0]] + d.write_delay_stimulus() + print_time("DELAY", datetime.datetime.now(), start_time) + + # Save trimmed spice file + temp_trim_sp = "{0}trimmed.sp".format(OPTS.output_path) + self.sp_write(temp_trim_sp, lvs=False, trim=True) + + if not OPTS.netlist_only: + # Write the layout + start_time = datetime.datetime.now() + gdsname = OPTS.output_path + self.s.name + ".gds" + debug.print_raw("GDS: Writing to {0}".format(gdsname)) + self.gds_write(gdsname) + if OPTS.check_lvsdrc: + verify.write_drc_script(cell_name=self.s.name, + gds_name=os.path.basename(gdsname), + extract=True, + final_verification=True, + output_path=OPTS.output_path) + print_time("GDS", datetime.datetime.now(), start_time) + + # Create a LEF physical model + start_time = datetime.datetime.now() + lefname = OPTS.output_path + self.s.name + ".lef" + debug.print_raw("LEF: Writing to {0}".format(lefname)) + self.lef_write(lefname) + print_time("LEF", datetime.datetime.now(), start_time) + + # Save the LVS file + start_time = datetime.datetime.now() + lvsname = OPTS.output_path + self.s.name + ".lvs.sp" + debug.print_raw("LVS: Writing to {0}".format(lvsname)) + self.sp_write(lvsname, lvs=True) + if not OPTS.netlist_only and OPTS.check_lvsdrc: + verify.write_lvs_script(cell_name=self.s.name, + gds_name=os.path.basename(gdsname), + sp_name=os.path.basename(lvsname), + final_verification=True, + output_path=OPTS.output_path) + print_time("LVS writing", datetime.datetime.now(), start_time) + + # Save the extracted spice file + if OPTS.use_pex: + start_time = datetime.datetime.now() + # Output the extracted design if requested + pexname = OPTS.output_path + self.s.name + ".pex.sp" + spname = OPTS.output_path + self.s.name + ".sp" + verify.run_pex(self.s.name, gdsname, spname, output=pexname) + sp_file = pexname + print_time("Extraction", datetime.datetime.now(), start_time) + else: + # Use generated spice file for characterization + sp_file = spname + + # Characterize the design + start_time = datetime.datetime.now() + from openram.characterizer import lib + debug.print_raw("LIB: Characterizing... ") + lib(out_dir=OPTS.output_path, sram=self.s, sp_file=sp_file) + print_time("Characterization", datetime.datetime.now(), start_time) + + # Write the config file + start_time = datetime.datetime.now() + try: + from shutil import copyfile + copyfile(OPTS.config_file, OPTS.output_path + OPTS.output_name + '.py') + except shutil.SameFileError: + pass + debug.print_raw("Config: Writing to {0}".format(OPTS.output_path + OPTS.output_name + '.py')) + print_time("Config", datetime.datetime.now(), start_time) + + # Write the datasheet + start_time = datetime.datetime.now() + from openram.datasheet import datasheet_gen + dname = OPTS.output_path + self.s.name + ".html" + debug.print_raw("Datasheet: Writing to {0}".format(dname)) + datasheet_gen.datasheet_write(dname) + print_time("Datasheet", datetime.datetime.now(), start_time) + + # Write a verilog model + start_time = datetime.datetime.now() + 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) + + # Write out options if specified + if OPTS.output_extended_config: + start_time = datetime.datetime.now() + oname = OPTS.output_path + OPTS.output_name + "_extended.py" + debug.print_raw("Extended Config: Writing to {0}".format(oname)) + self.extended_config_write(oname) + print_time("Extended Config", datetime.datetime.now(), start_time) diff --git a/sram_compiler.py b/sram_compiler.py index 3d2b7bbdc..fc2f7f51e 100755 --- a/sram_compiler.py +++ b/sram_compiler.py @@ -71,11 +71,11 @@ # Create an SRAM (we can also pass sram_config, see documentation/tutorials for details) #from openram import sram #s = sram() -from openram import sram_road -s = sram_road.sram() +from openram import sram_new_test +s = sram_new_test.sram() # Output the files for the resulting SRAM s.save() - +''' del s s = sram_road.sram(mod=1) s.save(mod=1) @@ -99,7 +99,7 @@ del s s = sram_road.sram(mod=6) s.save(mod=6) - +''' # Delete temp files etc. openram.end_openram() openram.print_time("End", datetime.datetime.now(), start_time) From e3170abd0129cd73d143957e9c6d2a3cc25f3870 Mon Sep 17 00:00:00 2001 From: FriedrichWu Date: Tue, 30 Jul 2024 15:04:47 +0200 Subject: [PATCH 07/26] Simple version without lvs(rutiime problem with magic --- compiler/modules/sram_new.py | 2 +- compiler/router/signal_escape_router.py | 44 ++++++++--------------- compiler/sram_new_test.py | 38 +++++++++++++++++--- macros/sram_configs/sky130_sram_common.py | 4 ++- 4 files changed, 51 insertions(+), 37 deletions(-) diff --git a/compiler/modules/sram_new.py b/compiler/modules/sram_new.py index aa813084c..d16bd7ec0 100644 --- a/compiler/modules/sram_new.py +++ b/compiler/modules/sram_new.py @@ -1273,7 +1273,7 @@ def route_data_dffs(self, port, add_routes): if len(route_map) > 0: # This layer stack must be different than the column addr dff layer stack - layer_stack = self.m3_stack + layer_stack = self.m2_stack if port == 0: # This is relative to the bank at 0,0 or the s_en which is routed on M3 also if "s_en" in self.control_logic_insts[port].mod.pin_map: diff --git a/compiler/router/signal_escape_router.py b/compiler/router/signal_escape_router.py index 6f79509a5..c0b6749a3 100644 --- a/compiler/router/signal_escape_router.py +++ b/compiler/router/signal_escape_router.py @@ -24,6 +24,9 @@ def __init__(self, layers, design, bbox=None): # New pins are the side supply pins self.new_pins = {} + + # Use for add distance of dout pins at the perimeter + self.distance = 0 def route(self, pin_names): @@ -205,42 +208,23 @@ def create_fake_pin(self, pin): #fake_center = vector(ll.x - self.track_wire * 2, c.y) # test if here we could change the pin position at the layout # relocate the pin position + pattern = r'^dout' + if re.match(pattern, pin.name): + if edge == "bottom":# change to the east + vertical = True + fake_center = vector(ur.x + self.track_wire * 2, ll.y + 30 + self.distance) + self.distance += 1 + else: + if edge == "top":# change to the west + vertical = True + fake_center = vector(ll.x - self.track_wire * 2, ur.y - 30 - self.distance) + self.distance += 1 """ pattern = r'^addr0_1' if re.match(pattern, pin.name): vertical = True fake_center = vector(ll.x - self.track_wire * 2, c.y + self.track_wire * 4)# fix still do not know how to control the distance between every fake pin #do not know why after this, all fake out pins are put at the same position -> because the originl inside pin has same y? - pattern = r'^addr0_2' - if re.match(pattern, pin.name): - vertical = True - fake_center = vector(ll.x - self.track_wire * 2, c.y + self.track_wire * 8)# fix still do not know how to control the distance between every fake pin - #do not know why after this, all fake out pins are put at the same position -> because the originl inside pin has same y? - pattern = r'^addr0_3' - if re.match(pattern, pin.name): - vertical = True - fake_center = vector(ll.x - self.track_wire * 2, c.y + self.track_wire*12)# fix still do not know how to control the distance between every fake pin - #do not know why after this, all fake out pins are put at the same position -> because the originl inside pin has same y? - pattern = r'^addr0_4' - if re.match(pattern, pin.name): - vertical = True - fake_center = vector(ll.x - self.track_wire * 2, c.y + self.track_wire*16)# fix still do not know how to control the distance between every fake pin - #do not know why after this, all fake out pins are put at the same position -> because the originl inside pin has same y? - pattern = r'^p_en_bar0' - if re.match(pattern, pin.name): - vertical = True - fake_center = vector(ll.x - self.track_wire * 2, c.y)# fix still do not know how to control the distance between every fake pin - #do not know why after this, all fake out pins are put at the same position -> because the originl inside pin has same y? - pattern = r'^s_en0' - if re.match(pattern, pin.name): - vertical = True - fake_center = vector(ll.x - self.track_wire * 2, c.y + self.track_width *3)# fix still do not know how to control the distance between every fake pin - #do not know why after this, all fake out pins are put at the same position -> because the originl inside pin has same y? - pattern = r'^w_en0' - if re.match(pattern, pin.name): - vertical = True - fake_center = vector(ll.x - self.track_wire * 2, c.y + self.track_width * 6)# fix still do not know how to control the distance between every fake pin - #do not know why after this, all fake out pins are put at the same position -> because the originl inside pin has same y? """ # Create the fake pin shape layer = self.get_layer(int(not vertical)) diff --git a/compiler/sram_new_test.py b/compiler/sram_new_test.py index 228b3874d..01a0b814a 100644 --- a/compiler/sram_new_test.py +++ b/compiler/sram_new_test.py @@ -54,10 +54,38 @@ def __init__(self, sram_config=None, name=None): self.s = sram(name, sram_config) self.s.create_netlist()# not placed & routed jet - #self.s_tmp = copy.deepcopy(self.s) # need deepcopy if not OPTS.netlist_only: i = 0 supply_route_not_found = False + while i < (OPTS.word_size + 100): + print("current iteration: i = {0}".format(i)) + try: + self.s.create_layout_recrusive(position_add=i) + except AssertionError as e: + supply_route_not_found = True + if i == 99:# failed in rounting + debug.error("Failed in rounting", -1) + break + + + if (supply_route_not_found): + del self.s + self.s = sram(name, sram_config) + self.s.create_netlist() + if i == 0:# after first try + i = OPTS.word_size + 20 + else: + i = i + 1 + supply_route_not_found = False + else:# successfully routed + break + '''#old version + self.s = sram(name, sram_config) + self.s.create_netlist()# not placed & routed jet + #self.s_tmp = copy.deepcopy(self.s) # need deepcopy + if not OPTS.netlist_only: + i = 98 + supply_route_not_found = False #self.s_tmp = copy.deepcopy(self.s) # need deepcopy while i < 100: print("current iteration: {0}".format(i)) @@ -80,7 +108,7 @@ def __init__(self, sram_config=None, name=None): else:# successfully routed #self.s = copy.deepcopy(self.s_tmp) # need deepcopy break - + ''' if not OPTS.is_unit_test: print_time("SRAM creation", datetime.datetime.now(), start_time) @@ -134,14 +162,14 @@ def save(self): spname = OPTS.output_path + self.s.name + ".sp" debug.print_raw("SP: Writing to {0}".format(spname)) self.sp_write(spname) - + ''' # Save a functional simulation file with default period functional(self.s, spname, cycles=200, output_path=OPTS.output_path) print_time("Spice writing", datetime.datetime.now(), start_time) - + # Save stimulus and measurement file start_time = datetime.datetime.now() debug.print_raw("DELAY: Writing stimulus...") @@ -156,7 +184,7 @@ def save(self): d.targ_write_ports = [self.s.write_ports[0]] d.write_delay_stimulus() print_time("DELAY", datetime.datetime.now(), start_time) - + ''' # Save trimmed spice file temp_trim_sp = "{0}trimmed.sp".format(OPTS.output_path) self.sp_write(temp_trim_sp, lvs=False, trim=True) diff --git a/macros/sram_configs/sky130_sram_common.py b/macros/sram_configs/sky130_sram_common.py index 445c88ccb..415e80f24 100644 --- a/macros/sram_configs/sky130_sram_common.py +++ b/macros/sram_configs/sky130_sram_common.py @@ -10,8 +10,10 @@ #local_array_size = 16 route_supplies = "ring" +#supply_pin_type = "top" #route_supplies = "left" -check_lvsdrc = True +#route_supplies = False +check_lvsdrc = False uniquify = True #perimeter_pins = False #netlist_only = True From 725c4423cf1b5c0e18843fbc582f8a082bed29cb Mon Sep 17 00:00:00 2001 From: FriedrichWu Date: Tue, 30 Jul 2024 16:11:40 +0200 Subject: [PATCH 08/26] fix --- compiler/sram_new_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/sram_new_test.py b/compiler/sram_new_test.py index 01a0b814a..ec2b1a4e5 100644 --- a/compiler/sram_new_test.py +++ b/compiler/sram_new_test.py @@ -63,7 +63,7 @@ def __init__(self, sram_config=None, name=None): self.s.create_layout_recrusive(position_add=i) except AssertionError as e: supply_route_not_found = True - if i == 99:# failed in rounting + if i == (99 + OPTS.word_size):# failed in rounting debug.error("Failed in rounting", -1) break From 0ae194f17c78ea754ea775baf76cf086c37f48e4 Mon Sep 17 00:00:00 2001 From: FriedrichWu Date: Tue, 20 Aug 2024 09:28:48 +0200 Subject: [PATCH 09/26] Simple version fix --- compiler/base/channel_route_new.py | 427 ++++++++++++++++++ compiler/modules/sram_new.py | 37 +- compiler/router/signal_escape_router.py | 18 +- .../router/signal_escape_router_change.py | 311 +++++++++++++ compiler/sram_new_test.py | 65 ++- 5 files changed, 827 insertions(+), 31 deletions(-) create mode 100644 compiler/base/channel_route_new.py create mode 100644 compiler/router/signal_escape_router_change.py diff --git a/compiler/base/channel_route_new.py b/compiler/base/channel_route_new.py new file mode 100644 index 000000000..a1178bd10 --- /dev/null +++ b/compiler/base/channel_route_new.py @@ -0,0 +1,427 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2024 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. +# +# This version aims to keep the track out/vertical to the dff pins +# Now only consider the channel at the south +import collections +from openram import debug +from openram.tech import drc +from .vector import vector +from .design import design + + +class channel_net(): + def __init__(self, net_name, pins, vertical): + self.name = net_name + self.pins = pins + self.vertical = vertical + + # Keep track of the internval + if vertical: + self.min_value = min(i.by() for i in pins) + self.max_value = max(i.uy() for i in pins) + else: + self.min_value = min(i.lx() for i in pins) + self.max_value = max(i.rx() for i in pins) + + # Keep track of the conflicts + self.conflicts = [] + + def __str__(self): + return self.name + + def __repr__(self): + return self.name + + def __lt__(self, other): + return self.min_value < other.min_value + + def pin_overlap(self, pin1, pin2, pitch): + """ Check for vertical or horizontal overlap of the two pins """ + + # FIXME: If the pins are not in a row, this may break. + # However, a top pin shouldn't overlap another top pin, + # for example, so the extra comparison *shouldn't* matter. + + # Pin 1 must be in the "BOTTOM" set + x_overlap = pin1.by() < pin2.by() and abs(pin1.center().x - pin2.center().x) < pitch + + # Pin 1 must be in the "LEFT" set + y_overlap = pin1.lx() < pin2.lx() and abs(pin1.center().y - pin2.center().y) < pitch + overlaps = (not self.vertical and x_overlap) or (self.vertical and y_overlap) + return overlaps + + def pins_overlap(self, other, pitch): + """ + Check all the pin pairs on two nets and return a pin + overlap if any pin overlaps. + """ + + for pin1 in self.pins: + for pin2 in other.pins: + if self.pin_overlap(pin1, pin2, pitch): + return True + + return False + + def segment_overlap(self, other): + """ + Check if the horizontal span of the two nets overlaps eachother. + """ + min_overlap = self.min_value >= other.min_value and self.min_value <= other.max_value + max_overlap = self.max_value >= other.min_value and self.max_value <= other.max_value + return min_overlap or max_overlap + + +class channel_route(design): + + unique_id = 0 + + def __init__(self, + netlist, + offset, + layer_stack, + directions=None, + vertical=False, + parent=None): + """ + The net list is a list of the nets with each net being a list of pins + to be connected. The offset is the lower-left of where the + routing channel will start. This does NOT try to minimize the + number of tracks -- instead, it picks an order to avoid the + vertical conflicts between pins. The track size must be the number of + nets times the *nonpreferred* routing of the non-track layer pitch. + + """ + name = "cr_{0}".format(channel_route.unique_id) + channel_route.unique_id += 1 + super().__init__(name) + + self.netlist = netlist + self.offset = offset + self.layer_stack = layer_stack + self.directions = directions + self.vertical = vertical + # For debugging... + self.parent = parent + + if not directions or directions == "pref": + # Use the preferred layer directions + if self.get_preferred_direction(layer_stack[0]) == "V": + self.vertical_layer = layer_stack[0] + self.horizontal_layer = layer_stack[2] + else: + self.vertical_layer = layer_stack[2] + self.horizontal_layer = layer_stack[0] + elif directions == "nonpref": + # Use the preferred layer directions + if self.get_preferred_direction(layer_stack[0]) == "V": + self.vertical_layer = layer_stack[2] + self.horizontal_layer = layer_stack[0] + else: + self.vertical_layer = layer_stack[0] + self.horizontal_layer = layer_stack[2] + else: + # Use the layer directions specified to the router rather than + # the preferred directions + debug.check(directions[0] != directions[1], "Must have unique layer directions.") + if directions[0] == "V": + self.vertical_layer = layer_stack[0] + self.horizontal_layer = layer_stack[2] + else: + self.horizontal_layer = layer_stack[0] + self.vertical_layer = layer_stack[2] + + layer_stuff = self.get_layer_pitch(self.vertical_layer) + (self.vertical_nonpref_pitch, self.vertical_pitch, self.vertical_width, self.vertical_space) = layer_stuff + + layer_stuff = self.get_layer_pitch(self.horizontal_layer) + (self.horizontal_nonpref_pitch, self.horizontal_pitch, self.horizontal_width, self.horizontal_space) = layer_stuff + # For debug + + debug.warning("layer horizontal: {0}".format(self.horizontal_layer)) + debug.warning("horizontal_nonpref_pitch: {0}".format(self.horizontal_nonpref_pitch)) + debug.warning("horizontal_pitch: {0}".format(self.horizontal_pitch)) + debug.warning("horizontal_space: {0}".format(self.horizontal_space)) + debug.warning("layer vertical: {0}".format(self.vertical_layer)) + debug.warning("vertiacl_nonpref_pitch: {0}".format(self.vertical_pitch)) + debug.warning("vertical_pitch: {0}".format(self.vertical_pitch)) + debug.warning("vertical_space: {0}".format(self.vertical_space)) + + self.route() + + def remove_net_from_graph(self, pin, g): + """ + Remove the pin from the graph and all conflicts + """ + g.pop(pin, None) + + # Remove the pin from all conflicts + # FIXME: This is O(n^2), so maybe optimize it. + for other_pin, conflicts in g.items(): + if pin in conflicts: + g[other_pin].remove(pin) + return g + + def route(self): + # Create names for the nets for the graphs + nets = [] + index = 0 + # print(self.netlist) + for pin_list in self.netlist: + nets.append(channel_net("n{}".format(index), pin_list, self.vertical)) + index += 1 + + # Create the (undirected) horizontal constraint graph + hcg = collections.OrderedDict() + for net1 in nets: + for net2 in nets: + if net1.name == net2.name: + continue + if net1.segment_overlap(net2): + try: + hcg[net1.name].add(net2.name) + except KeyError: + hcg[net1.name] = set([net2.name]) + try: + hcg[net2.name].add(net1.name) + except KeyError: + hcg[net2.name] = set([net1.name]) + + + # Initialize the vertical conflict graph (vcg) + # and make a list of all pins + vcg = collections.OrderedDict() + + # print("Nets:") + # for net_name in nets: + # print(net_name, [x.name for x in nets[net_name]]) + + # Find the vertical pin conflicts + # FIXME: O(n^2) but who cares for now + if self.vertical: + pitch = self.horizontal_nonpref_pitch + else: + pitch = self.vertical_nonpref_pitch + + for net in nets: + vcg[net.name] = set() + + for net1 in nets: + for net2 in nets: + # Skip yourself + if net1.name == net2.name: + continue + + if net1.pins_overlap(net2, pitch): + vcg[net2.name].add(net1.name) + + # Check if there are any cycles net1 <---> net2 in the VCG + + + # Some of the pins may be to the left/below the channel offset, + # so adjust if this is the case + self.min_value = min([n.min_value for n in nets]) + self.max_value = min([n.max_value for n in nets]) + if self.vertical: + real_channel_offset = vector(self.offset.x, min(self.min_value, self.offset.y)) + else: + real_channel_offset = vector(min(self.min_value, self.offset.x), self.offset.y) + current_offset = real_channel_offset + current_offset = vector(real_channel_offset.x, current_offset.y + 5) # make route out of dffs area + + if self.layer_stack == self.m2_stack: + self.vertical_nonpref_pitch = self.horizontal_pitch + + if self.layer_stack == self.m1_stack: + current_offset = vector(real_channel_offset.x, current_offset.y + 14) # make sure no overlap between col_dffs & data_dffs + if real_channel_offset.y > 0: # which means this is channnel router for coldff at the top + current_offset = real_channel_offset # no offset to avoid overlap problem at the top + # Sort nets by left edge value + nets.sort() + while len(nets) > 0: + + current_offset_value = current_offset.y if self.vertical else current_offset.x + + # from pprint import pformat + # print("VCG:\n", pformat(vcg)) + # for name,net in vcg.items(): + # print(name, net.min_value, net.max_value, net.conflicts) + # print(current_offset) + # get a route from conflict graph with empty fanout set + for net in nets: + # If it has no conflicts and the interval is to the right of the current offset in the track + if net.min_value >= current_offset_value and len(vcg[net.name]) == 0: + # print("Routing {}".format(net.name)) + # Add the trunk routes from the bottom up for + # horizontal or the left to right for vertical + if self.vertical: + self.add_vertical_trunk_route(net.pins, + current_offset, + self.horizontal_pitch) + current_offset = vector(current_offset.x, net.max_value + self.horizontal_nonpref_pitch) + else: + self.add_horizontal_trunk_route(net.pins, + current_offset, + self.vertical_pitch) + current_offset = vector(net.max_value + self.vertical_nonpref_pitch, current_offset.y) + + # Remove the net from other constriants in the VCG + vcg = self.remove_net_from_graph(net.name, vcg) + nets.remove(net) + + break + else: + # If we made a full pass and the offset didn't change... + current_offset_value = current_offset.y if self.vertical else current_offset.x + initial_offset_value = real_channel_offset.y if self.vertical else real_channel_offset.x + if current_offset_value == initial_offset_value: + debug.info(0, "Channel offset: {}".format(real_channel_offset)) + debug.info(0, "Current offset: {}".format(current_offset)) + debug.info(0, "VCG {}".format(str(vcg))) + debug.info(0, "HCG {}".format(str(hcg))) + for net in nets: + debug.info(0, "{0} pin: {1}".format(net.name, str(net.pins))) + if self.parent: + debug.info(0, "Saving vcg.gds") + self.parent.gds_write("vcg.gds") + debug.error("Cyclic VCG in channel router.", -1) + + # Increment the track and reset the offset to the start (like a typewriter) + if self.vertical: + current_offset = vector(current_offset.x + self.horizontal_nonpref_pitch, real_channel_offset.y) + else: + current_offset = vector(real_channel_offset.x, current_offset.y + self.vertical_nonpref_pitch) + + # Return the size of the channel + if self.vertical: + self.width = current_offset.x + self.horizontal_nonpref_pitch - self.offset.x + self.height = self.max_value + self.vertical_nonpref_pitch - self.offset.y + else: + self.width = self.max_value + self.horizontal_nonpref_pitch - self.offset.x + self.height = current_offset.y + self.vertical_nonpref_pitch - self.offset.y + + def get_layer_pitch(self, layer): + """ Return the track pitch on a given layer """ + try: + # FIXME: Using non-pref pitch here due to overlap bug in VCG constraints. + # It should just result in inefficient channel width but will work. + pitch = getattr(self, "{}_pitch".format(layer)) + nonpref_pitch = getattr(self, "{}_nonpref_pitch".format(layer)) + space = getattr(self, "{}_space".format(layer)) + except AttributeError: + debug.error("Cannot find layer pitch.", -1) + return (nonpref_pitch, pitch, pitch - space, space) + + def add_horizontal_trunk_route(self, + pins, + trunk_offset, + pitch): + """ + Create a trunk route for all pins with + the trunk located at the given y offset. + """ + max_x = max([pin.center().x for pin in pins]) + min_x = min([pin.center().x for pin in pins]) + + # if we are less than a pitch, just create a non-preferred layer jog + non_preferred_route = max_x - min_x <= pitch + + if non_preferred_route: + half_layer_width = 0.5 * drc["minwidth_{0}".format(self.vertical_layer)] + # Add the horizontal trunk on the vertical layer! + self.add_path(self.vertical_layer, + [vector(min_x - half_layer_width, trunk_offset.y), + vector(max_x + half_layer_width, trunk_offset.y)]) + + # Route each pin to the trunk + for pin in pins: + if pin.cy() < trunk_offset.y: + pin_pos = pin.uc() + else: + pin_pos = pin.bc() + + # No bend needed here + mid = vector(pin_pos.x, trunk_offset.y) + self.add_path(self.vertical_layer, [pin_pos, mid]) + else: + # Add the horizontal trunk + self.add_path(self.horizontal_layer, + [vector(min_x, trunk_offset.y), + vector(max_x, trunk_offset.y)]) + + # Route each pin to the trunk + for pin in pins: + # Find the correct side of the pin + if pin.cy() < trunk_offset.y: + pin_pos = pin.uc() + else: + pin_pos = pin.bc() + mid = vector(pin_pos.x, trunk_offset.y) + self.add_path(self.vertical_layer, [pin_pos, mid]) + if not non_preferred_route: + self.add_via_center(layers=self.layer_stack, + offset=mid, + directions=self.directions) + self.add_via_stack_center(from_layer=pin.layer, + to_layer=self.vertical_layer, + offset=pin_pos) + + def add_vertical_trunk_route(self, + pins, + trunk_offset, + pitch): + """ + Create a trunk route for all pins with the + trunk located at the given x offset. + """ + max_y = max([pin.center().y for pin in pins]) + min_y = min([pin.center().y for pin in pins]) + + # if we are less than a pitch, just create a non-preferred layer jog + non_preferred_route = max_y - min_y <= pitch + + if non_preferred_route: + half_layer_width = 0.5 * drc["minwidth_{0}".format(self.horizontal_layer)] + # Add the vertical trunk on the horizontal layer! + self.add_path(self.horizontal_layer, + [vector(trunk_offset.x, min_y - half_layer_width), + vector(trunk_offset.x, max_y + half_layer_width)]) + + # Route each pin to the trunk + for pin in pins: + # Find the correct side of the pin + if pin.cx() < trunk_offset.x: + pin_pos = pin.rc() + else: + pin_pos = pin.lc() + # No bend needed here + mid = vector(trunk_offset.x, pin_pos.y) + self.add_path(self.horizontal_layer, [pin_pos, mid]) + else: + # Add the vertical trunk + self.add_path(self.vertical_layer, + [vector(trunk_offset.x, min_y), + vector(trunk_offset.x, max_y)]) + + # Route each pin to the trunk + for pin in pins: + # Find the correct side of the pin + if pin.cx() < trunk_offset.x: + pin_pos = pin.rc() + else: + pin_pos = pin.lc() + mid = vector(trunk_offset.x, pin_pos.y) + self.add_path(self.horizontal_layer, [pin_pos, mid]) + if not non_preferred_route: + self.add_via_center(layers=self.layer_stack, + offset=mid, + directions=self.directions) + self.add_via_stack_center(from_layer=pin.layer, + to_layer=self.horizontal_layer, + offset=pin_pos) + diff --git a/compiler/modules/sram_new.py b/compiler/modules/sram_new.py index d16bd7ec0..2454dcb60 100644 --- a/compiler/modules/sram_new.py +++ b/compiler/modules/sram_new.py @@ -11,7 +11,8 @@ from importlib import import_module, reload from openram import debug from openram.base import vector -from openram.base import channel_route +#from openram.base import channel_route +from openram.base.channel_route_new import channel_route as channel_route from openram.base import design from openram.base import verilog from openram.base import lef @@ -207,7 +208,7 @@ def create_netlist(self): if not OPTS.is_unit_test: print_time("Submodules", datetime.datetime.now(), start_time) - def create_layout_recrusive(self, position_add=0): + def create_layout_recrusive(self, position_add=0, mod=0): """ Layout creation """ start_time = datetime.datetime.now() self.place_instances_changeable(position_add=position_add) @@ -215,7 +216,12 @@ def create_layout_recrusive(self, position_add=0): print_time("Placement", datetime.datetime.now(), start_time) start_time = datetime.datetime.now() - self.route_layout() + + if position_add == 0: + change = False + else: + change = True + self.route_layout(change=change, mod=mod) if not OPTS.is_unit_test: print_time("Routing", datetime.datetime.now(), start_time) @@ -352,7 +358,7 @@ def route_supplies(self, bbox=None): # Grid is left with many top level pins pass - def route_escape_pins(self, bbox=None): + def route_escape_pins(self, bbox=None, change=False, mod=0): """ Add the top-level pins for a single bank SRAM with control. """ @@ -394,11 +400,18 @@ def route_escape_pins(self, bbox=None): else: for bit in range(self.num_spare_cols): pins_to_route.append("spare_wen{0}[{1}]".format(port, bit)) - - from openram.router import signal_escape_router as router - rtr = router(layers=self.m3_stack, - bbox=bbox, - design=self) + + if change == False: + from openram.router import signal_escape_router as router + rtr = router(layers=self.m3_stack, + bbox=bbox, + design=self) + else: + from openram.router.signal_escape_router_change import signal_escape_router_change as router + rtr = router(layers=self.m3_stack, + bbox=bbox, + design=self, + mod=mod) rtr.route(pins_to_route) def compute_bus_sizes(self): @@ -1157,7 +1170,7 @@ def add_layout_pins(self, add_vias=True): "spare_wen{0}[{1}]".format(port, bit), start_layer=pin_layer) - def route_layout(self): + def route_layout(self, change=False, mod=0): """ Route a single bank SRAM """ self.route_clk() @@ -1181,9 +1194,9 @@ def route_layout(self): if OPTS.perimeter_pins: # We now route the escape routes far enough out so that they will # reach past the power ring or stripes on the sides - self.route_escape_pins(init_bbox) + self.route_escape_pins(bbox=init_bbox, change=change, mod=mod) if OPTS.route_supplies: - self.route_supplies(init_bbox) + self.route_supplies(init_bbox) def route_dffs(self, add_routes=True): diff --git a/compiler/router/signal_escape_router.py b/compiler/router/signal_escape_router.py index c0b6749a3..96de12d18 100644 --- a/compiler/router/signal_escape_router.py +++ b/compiler/router/signal_escape_router.py @@ -24,9 +24,6 @@ def __init__(self, layers, design, bbox=None): # New pins are the side supply pins self.new_pins = {} - - # Use for add distance of dout pins at the perimeter - self.distance = 0 def route(self, pin_names): @@ -205,20 +202,7 @@ def create_fake_pin(self, pin): fake_center = vector(ur.x + self.track_wire * 2, c.y) if edge == "top": fake_center = vector(c.x, ur.y + self.track_wire * 2) - #fake_center = vector(ll.x - self.track_wire * 2, c.y) # test if here we could change the pin position at the layout - - # relocate the pin position - pattern = r'^dout' - if re.match(pattern, pin.name): - if edge == "bottom":# change to the east - vertical = True - fake_center = vector(ur.x + self.track_wire * 2, ll.y + 30 + self.distance) - self.distance += 1 - else: - if edge == "top":# change to the west - vertical = True - fake_center = vector(ll.x - self.track_wire * 2, ur.y - 30 - self.distance) - self.distance += 1 + #fake_center = vector(ll.x - self.track_wire * 2, c.y) # test if here we could change the pin position at the layout """ pattern = r'^addr0_1' if re.match(pattern, pin.name): diff --git a/compiler/router/signal_escape_router_change.py b/compiler/router/signal_escape_router_change.py new file mode 100644 index 000000000..89eab5721 --- /dev/null +++ b/compiler/router/signal_escape_router_change.py @@ -0,0 +1,311 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2024 Regents of the University of California, Santa Cruz +# All rights reserved. +# +from openram import debug +from openram.base.vector import vector +from openram.base.vector3d import vector3d +from openram import OPTS +from .graph import graph +from .graph_shape import graph_shape +from .router import router +import re + +class signal_escape_router_change(router): + """ + This is the signal escape router that uses the Hanan grid graph method. + """ + + def __init__(self, layers, design, bbox=None, mod=0): + + # `router` is the base router class + router.__init__(self, layers, design, bbox) + + # New pins are the side supply pins + self.new_pins = {} + + # Use for add distance of dout pins at the perimeter + self.distance_right = 0 + + self.distance_left = 0 + + # Use for control which edge/position the pins(dout) will be placed + # 0 -> default + # 1 -> all top/bottom + # 2 -> all left/right + self.state_mod = mod + + + def route(self, pin_names): + """ Route the given pins to the perimeter. """ + debug.info(1, "Running signal escape router...") + + # Prepare gdsMill to find pins and blockages + self.prepare_gds_reader() + + # Find pins to be routed + for name in pin_names: + self.find_pins(name) + + # Find blockages and vias + self.find_blockages() + self.find_vias() + + # Convert blockages and vias if they overlap a pin + self.convert_vias() + self.convert_blockages() + + # Add fake pins on the perimeter to do the escape routing on + self.add_perimeter_fake_pins() + + # Add vdd and gnd pins as blockages as well + # NOTE: This is done to make vdd and gnd pins DRC-safe + for pin in self.all_pins: + self.blockages.append(self.inflate_shape(pin)) + + # Route vdd and gnd + routed_count = 0 + routed_max = len(pin_names) + for source, target, _ in self.get_route_pairs(pin_names): + # Change fake pin's name so the graph will treat it as routable + target.name = source.name + # Create the graph + g = graph(self) + g.create_graph(source, target) + # Find the shortest path from source to target + path = g.find_shortest_path() + # If no path is found, throw an error + if path is None: + self.write_debug_gds(gds_name="{}error.gds".format(OPTS.openram_temp), g=g, source=source, target=target) + debug.error("Couldn't route from {} to {}.".format(source, target), -1) + # Create the path shapes on layout + new_wires, new_vias = self.add_path(path) + self.new_pins[source.name] = new_wires[-1] + # Find the recently added shapes + self.find_blockages(name, new_wires) + self.find_vias(new_vias) + routed_count += 1 + debug.info(2, "Routed {} of {} signal pins".format(routed_count, routed_max)) + print("route pins:") + print(source) + self.replace_layout_pins() + + + def get_closest_edge(self, point): + """ Return a point's the closest edge and the edge's axis direction. """ + + ll, ur = self.bbox + + # Snap the pin to the perimeter and break the iteration + ll_diff_x = abs(point.x - ll.x) + ll_diff_y = abs(point.y - ll.y) + ur_diff_x = abs(point.x - ur.x) + ur_diff_y = abs(point.y - ur.y) + min_diff = min(ll_diff_x, ll_diff_y, ur_diff_x, ur_diff_y) + + if min_diff == ll_diff_x: + return "left", True + if min_diff == ll_diff_y: + return "bottom", False + if min_diff == ur_diff_x: + return "right", True + return "top", False + + + def prepare_path(self, path): + """ + Override the `prepare_path` method from the `router` class to prevent + overflows from the SRAM layout area. + """ + + ll, ur = self.bbox + nodes = super().prepare_path(path) + new_nodes = [] + for i in range(len(nodes)): + node = nodes[i] + c = node.center + # Haven't overflown yet + if ll.x < c.x and c.x < ur.x and ll.y < c.y and c.y < ur.y: + new_nodes.append(node) + continue + # Snap the pin to the perimeter and break the iteration + edge, _ = self.get_closest_edge(c) + if edge == "left": + fake_center = vector3d(ll.x + self.half_wire, c.y, c.z) + if edge == "bottom": + fake_center = vector3d(c.x, ll.y + self.half_wire, c.z) + if edge == "right": + fake_center = vector3d(ur.x - self.half_wire, c.y, c.z) + if edge == "top": + fake_center = vector3d(c.x, ur.y - self.half_wire, c.z) + node.center = fake_center + new_nodes.append(node) + break + return new_nodes + + + def add_perimeter_fake_pins(self): + """ + Add the fake pins on the perimeter to where the signals will be routed. + These perimeter fake pins are only used to replace layout pins at the + end of routing. + """ + + ll, ur = self.bbox + wide = self.track_wire + + for side in ["top", "bottom", "left", "right"]: + vertical = side in ["left", "right"] + + # Calculate the lower left coordinate + if side == "top": + offset = vector(ll.x, ur.y - wide) + elif side == "bottom": + offset = vector(ll.x, ll.y) + elif side == "left": + offset = vector(ll.x, ll.y) + elif side == "right": + offset = vector(ur.x - wide, ll.y) + + # Calculate width and height + shape = ur - ll + if vertical: + shape_width = wide + shape_height = shape.y + else: + shape_width = shape.x + shape_height = wide + + # Add this new pin + # They must lie on the non-preferred direction since the side supply + # pins will lie on the preferred direction + layer = self.get_layer(int(not vertical)) + nll = vector(offset.x, offset.y) + nur = vector(offset.x + shape_width, offset.y + shape_height) + rect = [nll, nur] + pin = graph_shape(name="fake", + rect=rect, + layer_name_pp=layer) + self.fake_pins.append(pin) + print("this is add_per") + print(pin.name) + print(pin.center) + + def create_fake_pin(self, pin): + """ Create a fake pin on the perimeter orthogonal to the given pin. """ + + ll, ur = self.bbox + c = pin.center() + print("inside pin name") + print("----------------------------------------------------------") + print(pin.name) + # Find the closest edge + edge, vertical = self.get_closest_edge(c) + + # Keep the fake pin out of the SRAM layout are so that they won't be + # blocked by previous signals if they're on the same orthogonal line + if edge == "left": + fake_center = vector(ll.x - self.track_wire * 2, c.y) + if edge == "bottom": + fake_center = vector(c.x, ll.y - self.track_wire * 2) + if edge == "right": + fake_center = vector(ur.x + self.track_wire * 2, c.y) + if edge == "top": + fake_center = vector(c.x, ur.y + self.track_wire * 2) + #fake_center = vector(ll.x - self.track_wire * 2, c.y) # test if here we could change the pin position at the layout + + # relocate the pin position + pattern = r'^dout' + if re.match(pattern, pin.name): + + if self.state_mod == 0: + pass# do not change, default + + elif self.state_mod == 1: # all top/bottom + if edge == "right": + vertical = False + fake_center = vector(c.x, ll.y - self.track_wire * 2) + self.distance_right += 1 + else: + if edge == "left": + vertical = False + fake_center = vector(c.x, ll.y + self.track_wire * 2) + self.distance_left += 1 + + elif self.state_mod == 2: # all left/right + if (edge == "bottom") or (edge == "right"):# change to the east + vertical = True + fake_center = vector(ur.x + self.track_wire * 2, ll.y + 30 + self.distance_right) + self.distance_right += 1 + else: + if (edge == "top") or (edge == "left"):# change to the west + vertical = True + fake_center = vector(ll.x - self.track_wire * 2, ur.y - 30 - self.distance_left) + self.distance_left += 1 + else: + debug.error("wrong state mod!", -1) + ''' # old version + if edge == "bottom":# change to the east + vertical = True + fake_center = vector(ur.x + self.track_wire * 2, ll.y + 30 + self.distance_right) + self.distance_right += 1 + else: + if edge == "top":# change to the west + vertical = True + fake_center = vector(ll.x - self.track_wire * 2, ur.y - 30 - self.distance_left) + self.distance_left += 1 + ''' + """ + pattern = r'^addr0_1' + if re.match(pattern, pin.name): + vertical = True + fake_center = vector(ll.x - self.track_wire * 2, c.y + self.track_wire * 4)# fix still do not know how to control the distance between every fake pin + #do not know why after this, all fake out pins are put at the same position -> because the originl inside pin has same y? + """ + # Create the fake pin shape + layer = self.get_layer(int(not vertical)) + half_wire_vector = vector([self.half_wire] * 2) + nll = fake_center - half_wire_vector + nur = fake_center + half_wire_vector + #not test jet + #half_wire_vector = vector([self.half_wire] * 2)# out *2 means vector([self.half_wire, self.half_wire]) + #nll = fake_center - half_wire_vector - half_wire_vector + #nur = fake_center + half_wire_vector + half_wire_vector + rect = [nll, nur] + pin = graph_shape(name="fake", + rect=rect, + layer_name_pp=layer) + print("this create_fake_pin") + print(pin.name) + print(pin.center) + return pin + + + def get_route_pairs(self, pin_names): + """ Return the pairs to be routed. """ + + to_route = [] + for name in pin_names: + print("==============the pin names===================") + print(name) + pin = next(iter(self.pins[name])) + fake = self.create_fake_pin(pin) + to_route.append((pin, fake, pin.distance(fake))) + return sorted(to_route, key=lambda x: x[2]) + + + def replace_layout_pins(self): + """ Replace the old layout pins with new ones around the perimeter. """ + + for name, pin in self.new_pins.items(): + print("name:{0}".format(name)) + print("replace_pin->{0}".format(pin)) + pin = graph_shape(pin.name, pin.boundary, pin.lpp) + # Find the intersection of this pin on the perimeter + for fake in self.fake_pins: + edge = pin.intersection(fake) + if edge: + break + self.design.replace_layout_pin(name, edge) + print("pass!!!!!!!") diff --git a/compiler/sram_new_test.py b/compiler/sram_new_test.py index ec2b1a4e5..cbce85824 100644 --- a/compiler/sram_new_test.py +++ b/compiler/sram_new_test.py @@ -51,7 +51,67 @@ def __init__(self, sram_config=None, name=None): self.name = name from openram.modules.sram_new import sram_1bank as sram - + + num_ports = OPTS.num_rw_ports + OPTS.num_r_ports + OPTS.num_w_ports + self.s = sram(name, sram_config) + self.s.create_netlist()# not placed & routed jet + cur_state = "IDLE" + if not OPTS.netlist_only: + i = 0 + while i < (OPTS.word_size + 100): + + print("current iteration: i = {0}".format(i)) + debug.warning("current state: state = {0}".format(cur_state)) + + if cur_state == "IDLE":# default, fisrt try + try: + self.s.create_layout_recrusive(position_add=i) + except AssertionError as e: + cur_state = "ALL_TOP_BOTTOM" + del self.s + self.s = sram(name, sram_config) + self.s.create_netlist() + if num_ports > 1: + i = 16 + else: + i = 0 + continue + cur_state = "FINISH" + break + + elif cur_state == "ALL_TOP_BOTTOM": + try: + self.s.create_layout_recrusive(position_add=i, mod=1) + except AssertionError as e: + cur_state = "ALL_LEFT_RIGHT" + i = OPTS.word_size + 24 + del self.s + self.s = sram(name, sram_config) + self.s.create_netlist() + continue + cur_state = "FINISH" + break + + elif cur_state == "ALL_LEFT_RIGHT": + try: + self.s.create_layout_recrusive(position_add=i, mod=2) + except AssertionError as e: + cur_state = "ALL_LEFT_RIGHT" + i = i + 1 + if i == (99 + OPTS.word_size):# failed in rounting + debug.error("Failed in rounting", -1) + break + del self.s + self.s = sram(name, sram_config) + self.s.create_netlist() + continue + cur_state = "FINISH" + break + else: + cur_state = "FINISH" + break + + ''' # 2nd version self.s = sram(name, sram_config) self.s.create_netlist()# not placed & routed jet if not OPTS.netlist_only: @@ -78,7 +138,8 @@ def __init__(self, sram_config=None, name=None): i = i + 1 supply_route_not_found = False else:# successfully routed - break + break + ''' '''#old version self.s = sram(name, sram_config) self.s.create_netlist()# not placed & routed jet From 6743049d44989aa4807e4a1659e00df1e13ba36c Mon Sep 17 00:00:00 2001 From: FriedrichWu Date: Thu, 5 Sep 2024 10:08:59 +0200 Subject: [PATCH 10/26] clean version --- compiler/base/channel_route.py | 33 +- compiler/base/channel_route_new.py | 427 ----- compiler/modules/sram_1bank.py | 53 +- compiler/modules/sram_new.py | 1494 ----------------- compiler/router/signal_escape_router.py | 71 +- .../router/signal_escape_router_change.py | 311 ---- compiler/router/supply_router.py | 2 - compiler/sram.py | 66 +- compiler/sram_new_test.py | 338 ---- sram_compiler.py | 9 +- 10 files changed, 173 insertions(+), 2631 deletions(-) delete mode 100644 compiler/base/channel_route_new.py delete mode 100644 compiler/modules/sram_new.py delete mode 100644 compiler/router/signal_escape_router_change.py delete mode 100644 compiler/sram_new_test.py diff --git a/compiler/base/channel_route.py b/compiler/base/channel_route.py index 5eb4da63f..9b4c545cd 100644 --- a/compiler/base/channel_route.py +++ b/compiler/base/channel_route.py @@ -5,6 +5,8 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # +# This version aims to keep the track out/vertical to the dff pins +# Now only consider the channel at the south, but works fine with channel at the north import collections from openram import debug from openram.tech import drc @@ -85,7 +87,8 @@ def __init__(self, layer_stack, directions=None, vertical=False, - parent=None): + parent=None, + dff_area=False): """ The net list is a list of the nets with each net being a list of pins to be connected. The offset is the lower-left of where the @@ -106,6 +109,7 @@ def __init__(self, self.vertical = vertical # For debugging... self.parent = parent + self.dff_area = dff_area # this is a special value to handle dff areas, should be true when routing col_dff/dffs if not directions or directions == "pref": # Use the preferred layer directions @@ -139,7 +143,17 @@ def __init__(self, layer_stuff = self.get_layer_pitch(self.horizontal_layer) (self.horizontal_nonpref_pitch, self.horizontal_pitch, self.horizontal_width, self.horizontal_space) = layer_stuff - + # For debug + + debug.warning("layer horizontal: {0}".format(self.horizontal_layer)) + debug.warning("horizontal_nonpref_pitch: {0}".format(self.horizontal_nonpref_pitch)) + debug.warning("horizontal_pitch: {0}".format(self.horizontal_pitch)) + debug.warning("horizontal_space: {0}".format(self.horizontal_space)) + debug.warning("layer vertical: {0}".format(self.vertical_layer)) + debug.warning("vertiacl_nonpref_pitch: {0}".format(self.vertical_pitch)) + debug.warning("vertical_pitch: {0}".format(self.vertical_pitch)) + debug.warning("vertical_space: {0}".format(self.vertical_space)) + self.route() def remove_net_from_graph(self, pin, g): @@ -219,8 +233,19 @@ def route(self): real_channel_offset = vector(self.offset.x, min(self.min_value, self.offset.y)) else: real_channel_offset = vector(min(self.min_value, self.offset.x), self.offset.y) - current_offset = real_channel_offset - + + if self.dff_area == False: + current_offset = real_channel_offset + else: # special handle for dff area + current_offset = vector(real_channel_offset.x, real_channel_offset.y + 5) # make route out of dffs area + + if self.layer_stack == self.m2_stack: + self.vertical_nonpref_pitch = self.horizontal_pitch + + if self.layer_stack == self.m1_stack: + current_offset = vector(real_channel_offset.x, current_offset.y + 14) # make sure no overlap between col_dffs & data_dffs + if real_channel_offset.y > 0: # which means this is channnel router for coldff at the top + current_offset = real_channel_offset # no offset to avoid overlap problem at the top # Sort nets by left edge value nets.sort() while len(nets) > 0: diff --git a/compiler/base/channel_route_new.py b/compiler/base/channel_route_new.py deleted file mode 100644 index a1178bd10..000000000 --- a/compiler/base/channel_route_new.py +++ /dev/null @@ -1,427 +0,0 @@ -# See LICENSE for licensing information. -# -# Copyright (c) 2016-2024 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. -# -# This version aims to keep the track out/vertical to the dff pins -# Now only consider the channel at the south -import collections -from openram import debug -from openram.tech import drc -from .vector import vector -from .design import design - - -class channel_net(): - def __init__(self, net_name, pins, vertical): - self.name = net_name - self.pins = pins - self.vertical = vertical - - # Keep track of the internval - if vertical: - self.min_value = min(i.by() for i in pins) - self.max_value = max(i.uy() for i in pins) - else: - self.min_value = min(i.lx() for i in pins) - self.max_value = max(i.rx() for i in pins) - - # Keep track of the conflicts - self.conflicts = [] - - def __str__(self): - return self.name - - def __repr__(self): - return self.name - - def __lt__(self, other): - return self.min_value < other.min_value - - def pin_overlap(self, pin1, pin2, pitch): - """ Check for vertical or horizontal overlap of the two pins """ - - # FIXME: If the pins are not in a row, this may break. - # However, a top pin shouldn't overlap another top pin, - # for example, so the extra comparison *shouldn't* matter. - - # Pin 1 must be in the "BOTTOM" set - x_overlap = pin1.by() < pin2.by() and abs(pin1.center().x - pin2.center().x) < pitch - - # Pin 1 must be in the "LEFT" set - y_overlap = pin1.lx() < pin2.lx() and abs(pin1.center().y - pin2.center().y) < pitch - overlaps = (not self.vertical and x_overlap) or (self.vertical and y_overlap) - return overlaps - - def pins_overlap(self, other, pitch): - """ - Check all the pin pairs on two nets and return a pin - overlap if any pin overlaps. - """ - - for pin1 in self.pins: - for pin2 in other.pins: - if self.pin_overlap(pin1, pin2, pitch): - return True - - return False - - def segment_overlap(self, other): - """ - Check if the horizontal span of the two nets overlaps eachother. - """ - min_overlap = self.min_value >= other.min_value and self.min_value <= other.max_value - max_overlap = self.max_value >= other.min_value and self.max_value <= other.max_value - return min_overlap or max_overlap - - -class channel_route(design): - - unique_id = 0 - - def __init__(self, - netlist, - offset, - layer_stack, - directions=None, - vertical=False, - parent=None): - """ - The net list is a list of the nets with each net being a list of pins - to be connected. The offset is the lower-left of where the - routing channel will start. This does NOT try to minimize the - number of tracks -- instead, it picks an order to avoid the - vertical conflicts between pins. The track size must be the number of - nets times the *nonpreferred* routing of the non-track layer pitch. - - """ - name = "cr_{0}".format(channel_route.unique_id) - channel_route.unique_id += 1 - super().__init__(name) - - self.netlist = netlist - self.offset = offset - self.layer_stack = layer_stack - self.directions = directions - self.vertical = vertical - # For debugging... - self.parent = parent - - if not directions or directions == "pref": - # Use the preferred layer directions - if self.get_preferred_direction(layer_stack[0]) == "V": - self.vertical_layer = layer_stack[0] - self.horizontal_layer = layer_stack[2] - else: - self.vertical_layer = layer_stack[2] - self.horizontal_layer = layer_stack[0] - elif directions == "nonpref": - # Use the preferred layer directions - if self.get_preferred_direction(layer_stack[0]) == "V": - self.vertical_layer = layer_stack[2] - self.horizontal_layer = layer_stack[0] - else: - self.vertical_layer = layer_stack[0] - self.horizontal_layer = layer_stack[2] - else: - # Use the layer directions specified to the router rather than - # the preferred directions - debug.check(directions[0] != directions[1], "Must have unique layer directions.") - if directions[0] == "V": - self.vertical_layer = layer_stack[0] - self.horizontal_layer = layer_stack[2] - else: - self.horizontal_layer = layer_stack[0] - self.vertical_layer = layer_stack[2] - - layer_stuff = self.get_layer_pitch(self.vertical_layer) - (self.vertical_nonpref_pitch, self.vertical_pitch, self.vertical_width, self.vertical_space) = layer_stuff - - layer_stuff = self.get_layer_pitch(self.horizontal_layer) - (self.horizontal_nonpref_pitch, self.horizontal_pitch, self.horizontal_width, self.horizontal_space) = layer_stuff - # For debug - - debug.warning("layer horizontal: {0}".format(self.horizontal_layer)) - debug.warning("horizontal_nonpref_pitch: {0}".format(self.horizontal_nonpref_pitch)) - debug.warning("horizontal_pitch: {0}".format(self.horizontal_pitch)) - debug.warning("horizontal_space: {0}".format(self.horizontal_space)) - debug.warning("layer vertical: {0}".format(self.vertical_layer)) - debug.warning("vertiacl_nonpref_pitch: {0}".format(self.vertical_pitch)) - debug.warning("vertical_pitch: {0}".format(self.vertical_pitch)) - debug.warning("vertical_space: {0}".format(self.vertical_space)) - - self.route() - - def remove_net_from_graph(self, pin, g): - """ - Remove the pin from the graph and all conflicts - """ - g.pop(pin, None) - - # Remove the pin from all conflicts - # FIXME: This is O(n^2), so maybe optimize it. - for other_pin, conflicts in g.items(): - if pin in conflicts: - g[other_pin].remove(pin) - return g - - def route(self): - # Create names for the nets for the graphs - nets = [] - index = 0 - # print(self.netlist) - for pin_list in self.netlist: - nets.append(channel_net("n{}".format(index), pin_list, self.vertical)) - index += 1 - - # Create the (undirected) horizontal constraint graph - hcg = collections.OrderedDict() - for net1 in nets: - for net2 in nets: - if net1.name == net2.name: - continue - if net1.segment_overlap(net2): - try: - hcg[net1.name].add(net2.name) - except KeyError: - hcg[net1.name] = set([net2.name]) - try: - hcg[net2.name].add(net1.name) - except KeyError: - hcg[net2.name] = set([net1.name]) - - - # Initialize the vertical conflict graph (vcg) - # and make a list of all pins - vcg = collections.OrderedDict() - - # print("Nets:") - # for net_name in nets: - # print(net_name, [x.name for x in nets[net_name]]) - - # Find the vertical pin conflicts - # FIXME: O(n^2) but who cares for now - if self.vertical: - pitch = self.horizontal_nonpref_pitch - else: - pitch = self.vertical_nonpref_pitch - - for net in nets: - vcg[net.name] = set() - - for net1 in nets: - for net2 in nets: - # Skip yourself - if net1.name == net2.name: - continue - - if net1.pins_overlap(net2, pitch): - vcg[net2.name].add(net1.name) - - # Check if there are any cycles net1 <---> net2 in the VCG - - - # Some of the pins may be to the left/below the channel offset, - # so adjust if this is the case - self.min_value = min([n.min_value for n in nets]) - self.max_value = min([n.max_value for n in nets]) - if self.vertical: - real_channel_offset = vector(self.offset.x, min(self.min_value, self.offset.y)) - else: - real_channel_offset = vector(min(self.min_value, self.offset.x), self.offset.y) - current_offset = real_channel_offset - current_offset = vector(real_channel_offset.x, current_offset.y + 5) # make route out of dffs area - - if self.layer_stack == self.m2_stack: - self.vertical_nonpref_pitch = self.horizontal_pitch - - if self.layer_stack == self.m1_stack: - current_offset = vector(real_channel_offset.x, current_offset.y + 14) # make sure no overlap between col_dffs & data_dffs - if real_channel_offset.y > 0: # which means this is channnel router for coldff at the top - current_offset = real_channel_offset # no offset to avoid overlap problem at the top - # Sort nets by left edge value - nets.sort() - while len(nets) > 0: - - current_offset_value = current_offset.y if self.vertical else current_offset.x - - # from pprint import pformat - # print("VCG:\n", pformat(vcg)) - # for name,net in vcg.items(): - # print(name, net.min_value, net.max_value, net.conflicts) - # print(current_offset) - # get a route from conflict graph with empty fanout set - for net in nets: - # If it has no conflicts and the interval is to the right of the current offset in the track - if net.min_value >= current_offset_value and len(vcg[net.name]) == 0: - # print("Routing {}".format(net.name)) - # Add the trunk routes from the bottom up for - # horizontal or the left to right for vertical - if self.vertical: - self.add_vertical_trunk_route(net.pins, - current_offset, - self.horizontal_pitch) - current_offset = vector(current_offset.x, net.max_value + self.horizontal_nonpref_pitch) - else: - self.add_horizontal_trunk_route(net.pins, - current_offset, - self.vertical_pitch) - current_offset = vector(net.max_value + self.vertical_nonpref_pitch, current_offset.y) - - # Remove the net from other constriants in the VCG - vcg = self.remove_net_from_graph(net.name, vcg) - nets.remove(net) - - break - else: - # If we made a full pass and the offset didn't change... - current_offset_value = current_offset.y if self.vertical else current_offset.x - initial_offset_value = real_channel_offset.y if self.vertical else real_channel_offset.x - if current_offset_value == initial_offset_value: - debug.info(0, "Channel offset: {}".format(real_channel_offset)) - debug.info(0, "Current offset: {}".format(current_offset)) - debug.info(0, "VCG {}".format(str(vcg))) - debug.info(0, "HCG {}".format(str(hcg))) - for net in nets: - debug.info(0, "{0} pin: {1}".format(net.name, str(net.pins))) - if self.parent: - debug.info(0, "Saving vcg.gds") - self.parent.gds_write("vcg.gds") - debug.error("Cyclic VCG in channel router.", -1) - - # Increment the track and reset the offset to the start (like a typewriter) - if self.vertical: - current_offset = vector(current_offset.x + self.horizontal_nonpref_pitch, real_channel_offset.y) - else: - current_offset = vector(real_channel_offset.x, current_offset.y + self.vertical_nonpref_pitch) - - # Return the size of the channel - if self.vertical: - self.width = current_offset.x + self.horizontal_nonpref_pitch - self.offset.x - self.height = self.max_value + self.vertical_nonpref_pitch - self.offset.y - else: - self.width = self.max_value + self.horizontal_nonpref_pitch - self.offset.x - self.height = current_offset.y + self.vertical_nonpref_pitch - self.offset.y - - def get_layer_pitch(self, layer): - """ Return the track pitch on a given layer """ - try: - # FIXME: Using non-pref pitch here due to overlap bug in VCG constraints. - # It should just result in inefficient channel width but will work. - pitch = getattr(self, "{}_pitch".format(layer)) - nonpref_pitch = getattr(self, "{}_nonpref_pitch".format(layer)) - space = getattr(self, "{}_space".format(layer)) - except AttributeError: - debug.error("Cannot find layer pitch.", -1) - return (nonpref_pitch, pitch, pitch - space, space) - - def add_horizontal_trunk_route(self, - pins, - trunk_offset, - pitch): - """ - Create a trunk route for all pins with - the trunk located at the given y offset. - """ - max_x = max([pin.center().x for pin in pins]) - min_x = min([pin.center().x for pin in pins]) - - # if we are less than a pitch, just create a non-preferred layer jog - non_preferred_route = max_x - min_x <= pitch - - if non_preferred_route: - half_layer_width = 0.5 * drc["minwidth_{0}".format(self.vertical_layer)] - # Add the horizontal trunk on the vertical layer! - self.add_path(self.vertical_layer, - [vector(min_x - half_layer_width, trunk_offset.y), - vector(max_x + half_layer_width, trunk_offset.y)]) - - # Route each pin to the trunk - for pin in pins: - if pin.cy() < trunk_offset.y: - pin_pos = pin.uc() - else: - pin_pos = pin.bc() - - # No bend needed here - mid = vector(pin_pos.x, trunk_offset.y) - self.add_path(self.vertical_layer, [pin_pos, mid]) - else: - # Add the horizontal trunk - self.add_path(self.horizontal_layer, - [vector(min_x, trunk_offset.y), - vector(max_x, trunk_offset.y)]) - - # Route each pin to the trunk - for pin in pins: - # Find the correct side of the pin - if pin.cy() < trunk_offset.y: - pin_pos = pin.uc() - else: - pin_pos = pin.bc() - mid = vector(pin_pos.x, trunk_offset.y) - self.add_path(self.vertical_layer, [pin_pos, mid]) - if not non_preferred_route: - self.add_via_center(layers=self.layer_stack, - offset=mid, - directions=self.directions) - self.add_via_stack_center(from_layer=pin.layer, - to_layer=self.vertical_layer, - offset=pin_pos) - - def add_vertical_trunk_route(self, - pins, - trunk_offset, - pitch): - """ - Create a trunk route for all pins with the - trunk located at the given x offset. - """ - max_y = max([pin.center().y for pin in pins]) - min_y = min([pin.center().y for pin in pins]) - - # if we are less than a pitch, just create a non-preferred layer jog - non_preferred_route = max_y - min_y <= pitch - - if non_preferred_route: - half_layer_width = 0.5 * drc["minwidth_{0}".format(self.horizontal_layer)] - # Add the vertical trunk on the horizontal layer! - self.add_path(self.horizontal_layer, - [vector(trunk_offset.x, min_y - half_layer_width), - vector(trunk_offset.x, max_y + half_layer_width)]) - - # Route each pin to the trunk - for pin in pins: - # Find the correct side of the pin - if pin.cx() < trunk_offset.x: - pin_pos = pin.rc() - else: - pin_pos = pin.lc() - # No bend needed here - mid = vector(trunk_offset.x, pin_pos.y) - self.add_path(self.horizontal_layer, [pin_pos, mid]) - else: - # Add the vertical trunk - self.add_path(self.vertical_layer, - [vector(trunk_offset.x, min_y), - vector(trunk_offset.x, max_y)]) - - # Route each pin to the trunk - for pin in pins: - # Find the correct side of the pin - if pin.cx() < trunk_offset.x: - pin_pos = pin.rc() - else: - pin_pos = pin.lc() - mid = vector(trunk_offset.x, pin_pos.y) - self.add_path(self.horizontal_layer, [pin_pos, mid]) - if not non_preferred_route: - self.add_via_center(layers=self.layer_stack, - offset=mid, - directions=self.directions) - self.add_via_stack_center(from_layer=pin.layer, - to_layer=self.horizontal_layer, - offset=pin_pos) - diff --git a/compiler/modules/sram_1bank.py b/compiler/modules/sram_1bank.py index 7d1aec3fc..1d76e07c9 100644 --- a/compiler/modules/sram_1bank.py +++ b/compiler/modules/sram_1bank.py @@ -5,6 +5,7 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # +# This aim to change the position of the submodule import datetime from math import ceil from importlib import import_module, reload @@ -205,16 +206,16 @@ def create_netlist(self): if not OPTS.is_unit_test: print_time("Submodules", datetime.datetime.now(), start_time) - - def create_layout(self): + + def create_layout(self, position_add=0, mod=0): """ Layout creation """ start_time = datetime.datetime.now() - self.place_instances() + self.place_instances_changeable(position_add=position_add) if not OPTS.is_unit_test: print_time("Placement", datetime.datetime.now(), start_time) start_time = datetime.datetime.now() - self.route_layout() + self.route_layout(mod=mod) if not OPTS.is_unit_test: print_time("Routing", datetime.datetime.now(), start_time) @@ -238,7 +239,7 @@ def create_layout(self): # 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) @@ -318,7 +319,7 @@ def route_supplies(self, bbox=None): # Grid is left with many top level pins pass - def route_escape_pins(self, bbox=None): + def route_escape_pins(self, bbox=None, mod=0): """ Add the top-level pins for a single bank SRAM with control. """ @@ -360,11 +361,16 @@ def route_escape_pins(self, bbox=None): else: for bit in range(self.num_spare_cols): pins_to_route.append("spare_wen{0}[{1}]".format(port, bit)) - + from openram.router import signal_escape_router as router + # mod Use for control which edge/position the pins(dout) will be placed + # 0 -> default + # 1 -> all top/bottom + # 2 -> all left/right rtr = router(layers=self.m3_stack, bbox=bbox, - design=self) + design=self, + mod=mod) rtr.route(pins_to_route) def compute_bus_sizes(self): @@ -778,11 +784,11 @@ def create_modules(self): self.spare_wen_dff_insts = self.create_spare_wen_dff() else: self.num_spare_cols = 0 - - def place_instances(self): + + def place_instances_changeable(self, position_add=0): """ This places the instances for a single bank SRAM with control - logic and up to 2 ports. + logic and up to 2 ports, but be able to change the io the dff position """ # No orientation or offset @@ -843,7 +849,11 @@ def place_instances(self): self.add_layout_pins(add_vias=False) self.route_dffs(add_routes=False) self.remove_layout_pins() - + + for port in self.all_ports: + # Add the extra position + self.data_bus_size[port] += position_add + # Re-place with the new channel size self.place_dffs() @@ -1051,7 +1061,7 @@ def add_layout_pins(self, add_vias=True): "spare_wen{0}[{1}]".format(port, bit), start_layer=pin_layer) - def route_layout(self): + def route_layout(self, mod=0): """ Route a single bank SRAM """ self.route_clk() @@ -1075,7 +1085,8 @@ def route_layout(self): if OPTS.perimeter_pins: # We now route the escape routes far enough out so that they will # reach past the power ring or stripes on the sides - self.route_escape_pins(init_bbox) + self.route_escape_pins(bbox=init_bbox, mod=mod) + if OPTS.route_supplies: self.route_supplies(init_bbox) @@ -1118,7 +1129,8 @@ def route_col_addr_dffs(self, port): cr = channel_route(netlist=route_map, offset=offset, layer_stack=layer_stack, - parent=self) + parent=self, + dff_area=True)# this is a special value to handle dff areas, should be true when routing col_dff/dffs # This causes problem in magic since it sometimes cannot extract connectivity of instances # with no active devices. self.add_inst(cr.name, cr) @@ -1130,7 +1142,8 @@ def route_col_addr_dffs(self, port): cr = channel_route(netlist=route_map, offset=offset, layer_stack=layer_stack, - parent=self) + parent=self, + dff_area=True)# this is a special value to handle dff areas, should be true when routing col_dff/dffs # This causes problem in magic since it sometimes cannot extract connectivity of instances # with no active devices. self.add_inst(cr.name, cr) @@ -1167,7 +1180,7 @@ def route_data_dffs(self, port, add_routes): if len(route_map) > 0: # This layer stack must be different than the column addr dff layer stack - layer_stack = self.m3_stack + layer_stack = self.m2_stack if port == 0: # This is relative to the bank at 0,0 or the s_en which is routed on M3 also if "s_en" in self.control_logic_insts[port].mod.pin_map: @@ -1181,7 +1194,8 @@ def route_data_dffs(self, port, add_routes): cr = channel_route(netlist=route_map, offset=offset, layer_stack=layer_stack, - parent=self) + parent=self, + dff_area=True)# this is a special value to handle dff areas, should be true when routing col_dff/dffs if add_routes: # This causes problem in magic since it sometimes cannot extract connectivity of instances # with no active devices. @@ -1201,7 +1215,8 @@ def route_data_dffs(self, port, add_routes): cr = channel_route(netlist=route_map, offset=offset, layer_stack=layer_stack, - parent=self) + parent=self, + dff_area=True)# this is a special value to handle dff areas, should be true when routing col_dff/dffs if add_routes: # This causes problem in magic since it sometimes cannot extract connectivity of instances # with no active devices. diff --git a/compiler/modules/sram_new.py b/compiler/modules/sram_new.py deleted file mode 100644 index 2454dcb60..000000000 --- a/compiler/modules/sram_new.py +++ /dev/null @@ -1,1494 +0,0 @@ -# See LICENSE for licensing information. -# -# Copyright (c) 2016-2024 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. -# -# This aim to change the position of the submodule -import datetime -from math import ceil -from importlib import import_module, reload -from openram import debug -from openram.base import vector -#from openram.base import channel_route -from openram.base.channel_route_new import channel_route as channel_route -from openram.base import design -from openram.base import verilog -from openram.base import lef -from openram.sram_factory import factory -from openram.tech import spice -from openram import OPTS, print_time - - -class sram_1bank(design, verilog, lef): - """ - Procedures specific to a one bank SRAM. - """ - def __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 openram.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 - - # delay control logic does not have RBLs - self.has_rbl = OPTS.control_logic != "control_logic_delay" - - 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_recrusive(self, position_add=0, mod=0): - """ Layout creation """ - start_time = datetime.datetime.now() - self.place_instances_changeable(position_add=position_add) - if not OPTS.is_unit_test: - print_time("Placement", datetime.datetime.now(), start_time) - - start_time = datetime.datetime.now() - - if position_add == 0: - change = False - else: - change = True - self.route_layout(change=change, mod=mod) - - 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_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]) - - from openram.router import supply_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.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=None, change=False, mod=0): - """ - 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)) - - if change == False: - from openram.router import signal_escape_router as router - rtr = router(layers=self.m3_stack, - bbox=bbox, - design=self) - else: - from openram.router.signal_escape_router_change import signal_escape_router_change as router - rtr = router(layers=self.m3_stack, - bbox=bbox, - design=self, - mod=mod) - rtr.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_module("." + OPTS.control_logic, "openram.modules")) - 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)) - if self.has_rbl: - 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)) - if self.has_rbl: - 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): - """ - This adds the modules for a single bank SRAM with control - logic. - """ - - self.bank_inst=self.create_bank(0) - - self.control_logic_insts = self.create_control_logic() - - self.row_addr_dff_insts = self.create_row_addr_dff() - - if self.col_addr_dff: - self.col_addr_dff_insts = self.create_col_addr_dff() - - if self.write_size != self.word_size: - self.wmask_dff_insts = self.create_wmask_dff() - self.data_dff_insts = self.create_data_dff() - else: - self.data_dff_insts = self.create_data_dff() - - if self.num_spare_cols: - self.spare_wen_dff_insts = self.create_spare_wen_dff() - else: - self.num_spare_cols = 0 - - def place_instances_changeable(self, position_add=0): - """ - This places the instances for a single bank SRAM with control - logic and up to 2 ports, but recrusive edition - """ - - # No orientation or offset - self.place_bank(self.bank_inst, [0, 0], 1, 1) - - # The control logic is placed such that the vertical center (between the delay/RBL and - # the actual control logic is aligned with the vertical center of the bank (between - # the sense amps/column mux and cell array) - # The x-coordinate is placed to allow a single clock wire (plus an extra pitch) - # up to the row address DFFs. - self.control_pos = [None] * len(self.all_ports) - self.row_addr_pos = [None] * len(self.all_ports) - - # DFFs are placd on their own - self.col_addr_pos = [None] * len(self.all_ports) - self.wmask_pos = [None] * len(self.all_ports) - self.spare_wen_pos = [None] * len(self.all_ports) - self.data_pos = [None] * len(self.all_ports) - - # These positions utilize the channel route sizes. - # FIXME: Auto-compute these rather than manual computation. - # If a horizontal channel, they rely on the vertical channel non-preferred (contacted) pitch. - # If a vertical channel, they rely on the horizontal channel non-preferred (contacted) pitch. - # So, m3 non-pref pitch means that this is routed on the m2 layer. - self.data_bus_gap = self.m4_nonpref_pitch * 2 - - # Spare wen are on a separate layer so not included - # Start with 1 track minimum - self.data_bus_size = [1] * len(self.all_ports) - self.col_addr_bus_size = [1] * len(self.all_ports) - for port in self.all_ports: - # The column address wires are routed separately from the data bus and will always be smaller. - # All ports need the col addr flops - self.col_addr_bus_size[port] = self.col_addr_size * self.m4_nonpref_pitch - # Write ports need the data input flops and write mask flops - if port in self.write_ports: - self.data_bus_size[port] += self.num_wmasks + self.word_size - # This is for the din pins that get routed in the same channel - # when we have dout and din together - if port in self.readwrite_ports: - self.data_bus_size[port] += self.word_size - # Convert to length - self.data_bus_size[port] *= self.m4_nonpref_pitch - # Add the gap in unit length - self.data_bus_size[port] += self.data_bus_gap - - # The control and row addr flops are independent of any bus widths. - self.place_control() - self.place_row_addr_dffs() - - # Place with an initial wide channel (from above) - self.place_dffs() - - # Route the channel and set to the new data bus size - # We need to temporarily add some pins for the x offsets - # but we'll remove them so that they have the right y - # offsets after the DFF placement. - self.add_layout_pins(add_vias=False) - self.route_dffs(add_routes=False) - self.remove_layout_pins() - - for port in self.all_ports: - # Add the extra position - self.data_bus_size[port] += position_add - - # Re-place with the new channel size - self.place_dffs() - - def place_instances(self): - """ - This places the instances for a single bank SRAM with control - logic and up to 2 ports. - """ - - # No orientation or offset - self.place_bank(self.bank_inst, [0, 0], 1, 1) - - # The control logic is placed such that the vertical center (between the delay/RBL and - # the actual control logic is aligned with the vertical center of the bank (between - # the sense amps/column mux and cell array) - # The x-coordinate is placed to allow a single clock wire (plus an extra pitch) - # up to the row address DFFs. - self.control_pos = [None] * len(self.all_ports) - self.row_addr_pos = [None] * len(self.all_ports) - - # DFFs are placd on their own - self.col_addr_pos = [None] * len(self.all_ports) - self.wmask_pos = [None] * len(self.all_ports) - self.spare_wen_pos = [None] * len(self.all_ports) - self.data_pos = [None] * len(self.all_ports) - - # These positions utilize the channel route sizes. - # FIXME: Auto-compute these rather than manual computation. - # If a horizontal channel, they rely on the vertical channel non-preferred (contacted) pitch. - # If a vertical channel, they rely on the horizontal channel non-preferred (contacted) pitch. - # So, m3 non-pref pitch means that this is routed on the m2 layer. - self.data_bus_gap = self.m4_nonpref_pitch * 2 - - # Spare wen are on a separate layer so not included - # Start with 1 track minimum - self.data_bus_size = [1] * len(self.all_ports) - self.col_addr_bus_size = [1] * len(self.all_ports) - for port in self.all_ports: - # The column address wires are routed separately from the data bus and will always be smaller. - # All ports need the col addr flops - self.col_addr_bus_size[port] = self.col_addr_size * self.m4_nonpref_pitch - # Write ports need the data input flops and write mask flops - if port in self.write_ports: - self.data_bus_size[port] += self.num_wmasks + self.word_size - # This is for the din pins that get routed in the same channel - # when we have dout and din together - if port in self.readwrite_ports: - self.data_bus_size[port] += self.word_size - # Convert to length - self.data_bus_size[port] *= self.m4_nonpref_pitch - # Add the gap in unit length - self.data_bus_size[port] += self.data_bus_gap - - # The control and row addr flops are independent of any bus widths. - self.place_control() - self.place_row_addr_dffs() - - # Place with an initial wide channel (from above) - self.place_dffs() - - # Route the channel and set to the new data bus size - # We need to temporarily add some pins for the x offsets - # but we'll remove them so that they have the right y - # offsets after the DFF placement. - self.add_layout_pins(add_vias=False) - self.route_dffs(add_routes=False) - self.remove_layout_pins() - - # Re-place with the new channel size - self.place_dffs() - - def place_row_addr_dffs(self): - """ - Must be run after place control logic. - """ - port = 0 - # The row address bits are placed above the control logic aligned on the right. - x_offset = self.control_logic_insts[port].rx() - self.row_addr_dff_insts[port].width - # It is above the control logic and the predecoder array - y_offset = max(self.control_logic_insts[port].uy(), self.bank.predecoder_top) - - self.row_addr_pos[port] = vector(x_offset, y_offset) - self.row_addr_dff_insts[port].place(self.row_addr_pos[port]) - - if len(self.all_ports)>1: - port = 1 - # The row address bits are placed above the control logic aligned on the left. - x_offset = self.control_pos[port].x - self.control_logic_insts[port].width + self.row_addr_dff_insts[port].width - # If it can be placed above the predecoder and below the control logic, do it - y_offset = self.bank.predecoder_bottom - self.row_addr_pos[port] = vector(x_offset, y_offset) - self.row_addr_dff_insts[port].place(self.row_addr_pos[port], mirror="XY") - - def place_control(self): - port = 0 - - # This includes 2 M2 pitches for the row addr clock line. - # The delay line is aligned with the bitcell array while the control logic is aligned with the port_data - # using the control_logic_center value. - self.control_pos[port] = vector(-self.control_logic_insts[port].width - 2 * self.m2_pitch, - self.bank.bank_array_ll.y - self.control_logic_insts[port].mod.control_logic_center.y) - self.control_logic_insts[port].place(self.control_pos[port]) - if len(self.all_ports) > 1: - port = 1 - # This includes 2 M2 pitches for the row addr clock line - # The delay line is aligned with the bitcell array while the control logic is aligned with the port_data - # using the control_logic_center value. - self.control_pos[port] = vector(self.bank_inst.rx() + self.control_logic_insts[port].width + 2 * self.m2_pitch, - self.bank.bank_array_ur.y - + self.control_logic_insts[port].height - - self.control_logic_insts[port].height - + self.control_logic_insts[port].mod.control_logic_center.y) - self.control_logic_insts[port].place(self.control_pos[port], mirror="XY") - - def place_dffs(self): - """ - Place the col addr, data, wmask, and spare data DFFs. - This can be run more than once after we recompute the channel width. - """ - - port = 0 - # Add the col address flops below the bank to the right of the control logic - x_offset = self.control_logic_insts[port].rx() + self.dff.width - # Place it a data bus below the x-axis, but at least as low as the control logic to not block - # the control logic signals - y_offset = min(-self.data_bus_size[port] - self.dff.height, - self.control_logic_insts[port].by()) - if self.col_addr_dff: - self.col_addr_pos[port] = vector(x_offset, - y_offset) - self.col_addr_dff_insts[port].place(self.col_addr_pos[port]) - x_offset = self.col_addr_dff_insts[port].rx() - else: - self.col_addr_pos[port] = vector(x_offset, 0) - - if port in self.write_ports: - 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) - self.wmask_dff_insts[port].place(self.wmask_pos[port]) - x_offset = self.wmask_dff_insts[port].rx() - - # Add the data flops below the write mask flops. - self.data_pos[port] = vector(x_offset, - y_offset) - self.data_dff_insts[port].place(self.data_pos[port]) - x_offset = self.data_dff_insts[port].rx() - - # Add spare write enable flops to the right of data flops since the spare columns - # will be on the right - if self.num_spare_cols: - self.spare_wen_pos[port] = vector(x_offset, - y_offset) - self.spare_wen_dff_insts[port].place(self.spare_wen_pos[port]) - x_offset = self.spare_wen_dff_insts[port].rx() - - else: - self.wmask_pos[port] = vector(x_offset, y_offset) - self.data_pos[port] = vector(x_offset, y_offset) - self.spare_wen_pos[port] = vector(x_offset, y_offset) - - if len(self.all_ports) > 1: - port = 1 - - # Add the col address flops below the bank to the right of the control logic - x_offset = self.control_logic_insts[port].lx() - 2 * self.dff.width - # Place it a data bus below the x-axis, but at least as high as the control logic to not block - # the control logic signals - y_offset = max(self.bank.height + self.data_bus_size[port] + self.dff.height, - self.control_logic_insts[port].uy() - self.dff.height) - if self.col_addr_dff: - self.col_addr_pos[port] = vector(x_offset, - y_offset) - self.col_addr_dff_insts[port].place(self.col_addr_pos[port], mirror="XY") - x_offset = self.col_addr_dff_insts[port].lx() - else: - self.col_addr_pos[port] = vector(x_offset, y_offset) - - if port in self.write_ports: - # Add spare write enable flops to the right of the data flops since the spare - # columns will be on the left - if self.num_spare_cols: - self.spare_wen_pos[port] = vector(x_offset - self.spare_wen_dff_insts[port].width, - y_offset) - 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 != 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) - self.wmask_dff_insts[port].place(self.wmask_pos[port], mirror="MX") - x_offset = self.wmask_dff_insts[port].lx() - - # Add the data flops below the write mask flops. - self.data_pos[port] = vector(x_offset - self.data_dff_insts[port].width, - y_offset) - self.data_dff_insts[port].place(self.data_pos[port], mirror="MX") - else: - self.wmask_pos[port] = vector(x_offset, y_offset) - self.data_pos[port] = vector(x_offset, y_offset) - self.spare_wen_pos[port] = vector(x_offset, y_offset) - - def add_layout_pins(self, add_vias=True): - """ - Add the top-level pins for a single bank SRAM with control. - """ - for port in self.all_ports: - # Hack: If we are escape routing, set the pin layer to - # None so that we will start from the pin layer - # Otherwise, set it as the pin layer so that no vias are added. - # Otherwise, when we remove pins to move the dff array dynamically, - # we will leave some remaining vias when the pin locations change. - if add_vias: - pin_layer = None - else: - pin_layer = self.pwr_grid_layers[0] - - # Connect the control pins as inputs - for signal in self.control_logic_inputs[port]: - if signal.startswith("rbl"): - continue - self.add_io_pin(self.control_logic_insts[port], - signal, - signal + "{}".format(port), - start_layer=pin_layer) - - if port in self.write_ports: - for bit in range(self.word_size + self.num_spare_cols): - self.add_io_pin(self.data_dff_insts[port], - "din_{}".format(bit), - "din{0}[{1}]".format(port, bit), - start_layer=pin_layer) - - if port in self.readwrite_ports or port in self.read_ports: - for bit in range(self.word_size + self.num_spare_cols): - self.add_io_pin(self.bank_inst, - "dout{0}_{1}".format(port, bit), - "dout{0}[{1}]".format(port, bit), - start_layer=pin_layer) - - for bit in range(self.col_addr_size): - self.add_io_pin(self.col_addr_dff_insts[port], - "din_{}".format(bit), - "addr{0}[{1}]".format(port, bit), - start_layer=pin_layer) - - for bit in range(self.row_addr_size): - self.add_io_pin(self.row_addr_dff_insts[port], - "din_{}".format(bit), - "addr{0}[{1}]".format(port, bit + self.col_addr_size), - start_layer=pin_layer) - - if port in self.write_ports: - 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), - "wmask{0}[{1}]".format(port, bit), - start_layer=pin_layer) - - if port in self.write_ports: - if self.num_spare_cols == 1: - self.add_io_pin(self.spare_wen_dff_insts[port], - "din_{}".format(0), - "spare_wen{0}".format(port), - start_layer=pin_layer) - else: - for bit in range(self.num_spare_cols): - self.add_io_pin(self.spare_wen_dff_insts[port], - "din_{}".format(bit), - "spare_wen{0}[{1}]".format(port, bit), - start_layer=pin_layer) - - def route_layout(self, change=False, mod=0): - """ Route a single bank SRAM """ - - self.route_clk() - - self.route_control_logic() - - self.route_row_addr_dff() - - self.route_dffs() - - # We add the vias to M3 before routing supplies because - # they might create some blockages - self.add_layout_pins() - - # Some technologies have an isolation - self.add_dnwell(inflate=2.5) - - init_bbox = self.get_bbox() - # Route the supplies together and/or to the ring/stripes. - # Route the pins to the perimeter - if OPTS.perimeter_pins: - # We now route the escape routes far enough out so that they will - # reach past the power ring or stripes on the sides - self.route_escape_pins(bbox=init_bbox, change=change, mod=mod) - if OPTS.route_supplies: - self.route_supplies(init_bbox) - - - def route_dffs(self, add_routes=True): - - for port in self.all_ports: - self.route_dff(port, add_routes) - - def route_dff(self, port, add_routes): - - # This is only done when we add_routes because the data channel will be larger - # so that can be used for area estimation. - if add_routes: - self.route_col_addr_dffs(port) - - self.route_data_dffs(port, add_routes) - - def route_col_addr_dffs(self, port): - - route_map = [] - - # column mux dff is routed on it's own since it is to the far end - # decoder inputs are min pitch M2, so need to use lower layer stack - if self.col_addr_size > 0: - dff_names = ["dout_{}".format(x) for x in range(self.col_addr_size)] - dff_pins = [self.col_addr_dff_insts[port].get_pin(x) for x in dff_names] - bank_names = ["addr{0}_{1}".format(port, x) for x in range(self.col_addr_size)] - bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] - route_map.extend(list(zip(bank_pins, dff_pins))) - - if len(route_map) > 0: - - # This layer stack must be different than the data dff layer stack - layer_stack = self.m1_stack - - if port == 0: - offset = vector(self.control_logic_insts[port].rx() + self.dff.width, - - self.data_bus_size[port] + 2 * self.m3_pitch) - cr = channel_route(netlist=route_map, - offset=offset, - layer_stack=layer_stack, - parent=self) - # This causes problem in magic since it sometimes cannot extract connectivity of instances - # with no active devices. - self.add_inst(cr.name, cr) - self.connect_inst([]) - # self.add_flat_inst(cr.name, cr) - else: - offset = vector(0, - self.bank.height + self.m3_pitch) - cr = channel_route(netlist=route_map, - offset=offset, - layer_stack=layer_stack, - parent=self) - # This causes problem in magic since it sometimes cannot extract connectivity of instances - # with no active devices. - self.add_inst(cr.name, cr) - self.connect_inst([]) - # self.add_flat_inst(cr.name, cr) - - def route_data_dffs(self, port, add_routes): - route_map = [] - - # wmask dff - if self.num_wmasks > 0 and port in self.write_ports: - dff_names = ["dout_{}".format(x) for x in range(self.num_wmasks)] - dff_pins = [self.wmask_dff_insts[port].get_pin(x) for x in dff_names] - bank_names = ["bank_wmask{0}_{1}".format(port, x) for x in range(self.num_wmasks)] - bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] - route_map.extend(list(zip(bank_pins, dff_pins))) - - if port in self.write_ports: - # synchronized inputs from data dff - dff_names = ["dout_{}".format(x) for x in range(self.word_size + self.num_spare_cols)] - dff_pins = [self.data_dff_insts[port].get_pin(x) for x in dff_names] - bank_names = ["din{0}_{1}".format(port, x) for x in range(self.word_size + self.num_spare_cols)] - bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] - route_map.extend(list(zip(bank_pins, dff_pins))) - - # spare wen dff - if self.num_spare_cols > 0 and port in self.write_ports: - dff_names = ["dout_{}".format(x) for x in range(self.num_spare_cols)] - dff_pins = [self.spare_wen_dff_insts[port].get_pin(x) for x in dff_names] - bank_names = ["bank_spare_wen{0}_{1}".format(port, x) for x in range(self.num_spare_cols)] - bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] - route_map.extend(list(zip(bank_pins, dff_pins))) - - if len(route_map) > 0: - - # This layer stack must be different than the column addr dff layer stack - layer_stack = self.m2_stack - if port == 0: - # This is relative to the bank at 0,0 or the s_en which is routed on M3 also - if "s_en" in self.control_logic_insts[port].mod.pin_map: - y_bottom = min(0, self.control_logic_insts[port].get_pin("s_en").by()) - else: - y_bottom = 0 - - y_offset = y_bottom - self.data_bus_size[port] + 2 * self.m3_pitch - offset = vector(self.control_logic_insts[port].rx() + self.dff.width, - y_offset) - cr = channel_route(netlist=route_map, - offset=offset, - layer_stack=layer_stack, - parent=self) - if add_routes: - # This causes problem in magic since it sometimes cannot extract connectivity of instances - # with no active devices. - self.add_inst(cr.name, cr) - self.connect_inst([]) - # self.add_flat_inst(cr.name, cr) - else: - self.data_bus_size[port] = max(cr.height, self.col_addr_bus_size[port]) + self.data_bus_gap - else: - if "s_en" in self.control_logic_insts[port].mod.pin_map: - y_top = max(self.bank.height, self.control_logic_insts[port].get_pin("s_en").uy()) - else: - y_top = self.bank.height - y_offset = y_top + self.m3_pitch - offset = vector(0, - y_offset) - cr = channel_route(netlist=route_map, - offset=offset, - layer_stack=layer_stack, - parent=self) - if add_routes: - # This causes problem in magic since it sometimes cannot extract connectivity of instances - # with no active devices. - self.add_inst(cr.name, cr) - self.connect_inst([]) - # self.add_flat_inst(cr.name, cr) - else: - self.data_bus_size[port] = max(cr.height, self.col_addr_bus_size[port]) + self.data_bus_gap - - def route_clk(self): - """ Route the clock network """ - - # This is the actual input to the SRAM - for port in self.all_ports: - # Connect all of these clock pins to the clock in the central bus - # This is something like a "spine" clock distribution. The two spines - # are clk_buf and clk_buf_bar - control_clk_buf_pin = self.control_logic_insts[port].get_pin("clk_buf") - control_clk_buf_pos = control_clk_buf_pin.center() - - # This uses a metal2 track to the right (for port0) of the control/row addr DFF - # to route vertically. For port1, it is to the left. - row_addr_clk_pin = self.row_addr_dff_insts[port].get_pin("clk") - if port % 2: - control_clk_buf_pos = control_clk_buf_pin.lc() - row_addr_clk_pos = row_addr_clk_pin.lc() - mid1_pos = vector(self.row_addr_dff_insts[port].lx() - self.m2_pitch, - row_addr_clk_pos.y) - else: - control_clk_buf_pos = control_clk_buf_pin.rc() - row_addr_clk_pos = row_addr_clk_pin.rc() - mid1_pos = vector(self.row_addr_dff_insts[port].rx() + self.m2_pitch, - row_addr_clk_pos.y) - - # This is the steiner point where the net branches out - clk_steiner_pos = vector(mid1_pos.x, control_clk_buf_pos.y) - self.add_path(control_clk_buf_pin.layer, [control_clk_buf_pos, clk_steiner_pos]) - self.add_via_stack_center(from_layer=control_clk_buf_pin.layer, - to_layer="m2", - offset=clk_steiner_pos) - - # Note, the via to the control logic is taken care of above - self.add_wire(self.m2_stack[::-1], - [row_addr_clk_pos, mid1_pos, clk_steiner_pos]) - - if self.col_addr_dff: - dff_clk_pin = self.col_addr_dff_insts[port].get_pin("clk") - dff_clk_pos = dff_clk_pin.center() - mid_pos = vector(clk_steiner_pos.x, dff_clk_pos.y) - self.add_wire(self.m2_stack[::-1], - [dff_clk_pos, mid_pos, clk_steiner_pos]) - elif port in self.write_ports: - data_dff_clk_pin = self.data_dff_insts[port].get_pin("clk") - data_dff_clk_pos = data_dff_clk_pin.center() - mid_pos = vector(clk_steiner_pos.x, data_dff_clk_pos.y) - # In some designs, the steiner via will be too close to the mid_pos via - # so make the wire as wide as the contacts - self.add_path("m2", - [mid_pos, clk_steiner_pos], - width=max(self.m2_via.width, self.m2_via.height)) - self.add_wire(self.m2_stack[::-1], - [data_dff_clk_pos, mid_pos, clk_steiner_pos]) - - def route_control_logic(self): - """ - Route the control logic pins that are not inputs - """ - - for port in self.all_ports: - for signal in self.control_logic_outputs[port]: - # The clock gets routed separately and is not a part of the bank - if "clk" in signal: - continue - src_pin = self.control_logic_insts[port].get_pin(signal) - dest_pin = self.bank_inst.get_pin(signal + "{}".format(port)) - self.connect_vbus(src_pin, dest_pin) - - if self.has_rbl: - for port in self.all_ports: - # Only input (besides pins) is the replica bitline - src_pin = self.control_logic_insts[port].get_pin("rbl_bl") - dest_pin = self.bank_inst.get_pin("rbl_bl_{0}_{0}".format(port)) - self.add_wire(self.m3_stack, - [src_pin.center(), vector(src_pin.cx(), dest_pin.cy()), dest_pin.rc()]) - self.add_via_stack_center(from_layer=src_pin.layer, - to_layer="m4", - offset=src_pin.center()) - self.add_via_stack_center(from_layer=dest_pin.layer, - to_layer="m3", - offset=dest_pin.center()) - - def route_row_addr_dff(self): - """ - Connect the output of the row flops to the bank pins - """ - for port in self.all_ports: - for bit in range(self.row_addr_size): - flop_name = "dout_{}".format(bit) - bank_name = "addr{0}_{1}".format(port, bit + self.col_addr_size) - flop_pin = self.row_addr_dff_insts[port].get_pin(flop_name) - bank_pin = self.bank_inst.get_pin(bank_name) - flop_pos = flop_pin.center() - bank_pos = bank_pin.center() - mid_pos = vector(bank_pos.x, flop_pos.y) - self.add_via_stack_center(from_layer=flop_pin.layer, - to_layer="m3", - offset=flop_pos) - self.add_path("m3", [flop_pos, mid_pos]) - self.add_via_stack_center(from_layer=bank_pin.layer, - to_layer="m3", - offset=mid_pos) - self.add_path(bank_pin.layer, [mid_pos, bank_pos]) - - 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. - """ - return - for n in self.control_logic_outputs[0]: - pin = self.control_logic_insts[0].get_pin(n) - self.add_label(text=n, - layer=pin.layer, - offset=pin.center()) - - def graph_exclude_data_dff(self): - """ - Removes data dff and wmask dff (if applicable) from search graph. - """ - # 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 != self.word_size: - for inst in self.wmask_dff_insts: - self.graph_inst_exclude.add(inst) - if self.num_spare_cols: - for inst in self.spare_wen_dff_insts: - self.graph_inst_exclude.add(inst) - - def graph_exclude_addr_dff(self): - """ - Removes data dff from search graph. - """ - # Address is considered not part of the critical path, subjectively removed - for inst in self.row_addr_dff_insts: - self.graph_inst_exclude.add(inst) - - if self.col_addr_dff: - for inst in self.col_addr_dff_insts: - self.graph_inst_exclude.add(inst) - - def graph_exclude_ctrl_dffs(self): - """ - Exclude dffs for CSB, WEB, etc from graph - """ - # Insts located in control logic, exclusion function called here - for inst in self.control_logic_insts: - inst.mod.graph_exclude_dffs() - - def get_cell_name(self, inst_name, row, col): - """ - Gets the spice name of the target bitcell. - """ - # Sanity check in case it was forgotten - if inst_name.find("x") != 0: - inst_name = "x" + inst_name - return self.bank_inst.mod.get_cell_name(inst_name + "{}x".format(OPTS.hier_seperator) + self.bank_inst.name, row, col) - - def get_bank_num(self, inst_name, row, col): - return 0 diff --git a/compiler/router/signal_escape_router.py b/compiler/router/signal_escape_router.py index 96de12d18..764001d1d 100644 --- a/compiler/router/signal_escape_router.py +++ b/compiler/router/signal_escape_router.py @@ -17,13 +17,24 @@ class signal_escape_router(router): This is the signal escape router that uses the Hanan grid graph method. """ - def __init__(self, layers, design, bbox=None): + def __init__(self, layers, design, bbox=None, mod=0): # `router` is the base router class router.__init__(self, layers, design, bbox) # New pins are the side supply pins self.new_pins = {} + + # Use for add distance of dout pins at the perimeter + self.distance_right = 0 + + self.distance_left = 0 + + # Use for control which edge/position the pins(dout) will be placed + # 0 -> default + # 1 -> all top/bottom + # 2 -> all left/right + self.state_mod = mod def route(self, pin_names): @@ -76,8 +87,6 @@ def route(self, pin_names): self.find_vias(new_vias) routed_count += 1 debug.info(2, "Routed {} of {} signal pins".format(routed_count, routed_max)) - print("route pins:") - print(source) self.replace_layout_pins() @@ -177,18 +186,13 @@ def add_perimeter_fake_pins(self): rect=rect, layer_name_pp=layer) self.fake_pins.append(pin) - print("this is add_per") - print(pin.name) - print(pin.center) def create_fake_pin(self, pin): """ Create a fake pin on the perimeter orthogonal to the given pin. """ ll, ur = self.bbox c = pin.center() - print("inside pin name") - print("----------------------------------------------------------") - print(pin.name) + # Find the closest edge edge, vertical = self.get_closest_edge(c) @@ -202,30 +206,49 @@ def create_fake_pin(self, pin): fake_center = vector(ur.x + self.track_wire * 2, c.y) if edge == "top": fake_center = vector(c.x, ur.y + self.track_wire * 2) - #fake_center = vector(ll.x - self.track_wire * 2, c.y) # test if here we could change the pin position at the layout - """ - pattern = r'^addr0_1' + + # relocate the pin position + pattern = r'^dout' if re.match(pattern, pin.name): - vertical = True - fake_center = vector(ll.x - self.track_wire * 2, c.y + self.track_wire * 4)# fix still do not know how to control the distance between every fake pin - #do not know why after this, all fake out pins are put at the same position -> because the originl inside pin has same y? - """ + + if self.state_mod == 0: + pass# do not change, default + + elif self.state_mod == 1: # all top/bottom + if edge == "right": + vertical = False + fake_center = vector(c.x, ll.y - self.track_wire * 2) + self.distance_right += 1 + else: + if edge == "left": + vertical = False + fake_center = vector(c.x, ll.y + self.track_wire * 2) + self.distance_left += 1 + + elif self.state_mod == 2: # all left/right + if (edge == "bottom") or (edge == "right"):# change to the east + vertical = True + fake_center = vector(ur.x + self.track_wire * 2, ll.y + 30 + self.distance_right) + self.distance_right += 1 + else: + if (edge == "top") or (edge == "left"):# change to the west + vertical = True + fake_center = vector(ll.x - self.track_wire * 2, ur.y - 30 - self.distance_left) + self.distance_left += 1 + else: + debug.error("wrong state mod!", -1) + # Create the fake pin shape layer = self.get_layer(int(not vertical)) half_wire_vector = vector([self.half_wire] * 2) nll = fake_center - half_wire_vector nur = fake_center + half_wire_vector - #not test jet - #half_wire_vector = vector([self.half_wire] * 2)# out *2 means vector([self.half_wire, self.half_wire]) - #nll = fake_center - half_wire_vector - half_wire_vector - #nur = fake_center + half_wire_vector + half_wire_vector + rect = [nll, nur] pin = graph_shape(name="fake", rect=rect, layer_name_pp=layer) - print("this create_fake_pin") - print(pin.name) - print(pin.center) + return pin @@ -234,8 +257,6 @@ def get_route_pairs(self, pin_names): to_route = [] for name in pin_names: - print("==============the pin names===================") - print(name) pin = next(iter(self.pins[name])) fake = self.create_fake_pin(pin) to_route.append((pin, fake, pin.distance(fake))) diff --git a/compiler/router/signal_escape_router_change.py b/compiler/router/signal_escape_router_change.py deleted file mode 100644 index 89eab5721..000000000 --- a/compiler/router/signal_escape_router_change.py +++ /dev/null @@ -1,311 +0,0 @@ -# See LICENSE for licensing information. -# -# Copyright (c) 2016-2024 Regents of the University of California, Santa Cruz -# All rights reserved. -# -from openram import debug -from openram.base.vector import vector -from openram.base.vector3d import vector3d -from openram import OPTS -from .graph import graph -from .graph_shape import graph_shape -from .router import router -import re - -class signal_escape_router_change(router): - """ - This is the signal escape router that uses the Hanan grid graph method. - """ - - def __init__(self, layers, design, bbox=None, mod=0): - - # `router` is the base router class - router.__init__(self, layers, design, bbox) - - # New pins are the side supply pins - self.new_pins = {} - - # Use for add distance of dout pins at the perimeter - self.distance_right = 0 - - self.distance_left = 0 - - # Use for control which edge/position the pins(dout) will be placed - # 0 -> default - # 1 -> all top/bottom - # 2 -> all left/right - self.state_mod = mod - - - def route(self, pin_names): - """ Route the given pins to the perimeter. """ - debug.info(1, "Running signal escape router...") - - # Prepare gdsMill to find pins and blockages - self.prepare_gds_reader() - - # Find pins to be routed - for name in pin_names: - self.find_pins(name) - - # Find blockages and vias - self.find_blockages() - self.find_vias() - - # Convert blockages and vias if they overlap a pin - self.convert_vias() - self.convert_blockages() - - # Add fake pins on the perimeter to do the escape routing on - self.add_perimeter_fake_pins() - - # Add vdd and gnd pins as blockages as well - # NOTE: This is done to make vdd and gnd pins DRC-safe - for pin in self.all_pins: - self.blockages.append(self.inflate_shape(pin)) - - # Route vdd and gnd - routed_count = 0 - routed_max = len(pin_names) - for source, target, _ in self.get_route_pairs(pin_names): - # Change fake pin's name so the graph will treat it as routable - target.name = source.name - # Create the graph - g = graph(self) - g.create_graph(source, target) - # Find the shortest path from source to target - path = g.find_shortest_path() - # If no path is found, throw an error - if path is None: - self.write_debug_gds(gds_name="{}error.gds".format(OPTS.openram_temp), g=g, source=source, target=target) - debug.error("Couldn't route from {} to {}.".format(source, target), -1) - # Create the path shapes on layout - new_wires, new_vias = self.add_path(path) - self.new_pins[source.name] = new_wires[-1] - # Find the recently added shapes - self.find_blockages(name, new_wires) - self.find_vias(new_vias) - routed_count += 1 - debug.info(2, "Routed {} of {} signal pins".format(routed_count, routed_max)) - print("route pins:") - print(source) - self.replace_layout_pins() - - - def get_closest_edge(self, point): - """ Return a point's the closest edge and the edge's axis direction. """ - - ll, ur = self.bbox - - # Snap the pin to the perimeter and break the iteration - ll_diff_x = abs(point.x - ll.x) - ll_diff_y = abs(point.y - ll.y) - ur_diff_x = abs(point.x - ur.x) - ur_diff_y = abs(point.y - ur.y) - min_diff = min(ll_diff_x, ll_diff_y, ur_diff_x, ur_diff_y) - - if min_diff == ll_diff_x: - return "left", True - if min_diff == ll_diff_y: - return "bottom", False - if min_diff == ur_diff_x: - return "right", True - return "top", False - - - def prepare_path(self, path): - """ - Override the `prepare_path` method from the `router` class to prevent - overflows from the SRAM layout area. - """ - - ll, ur = self.bbox - nodes = super().prepare_path(path) - new_nodes = [] - for i in range(len(nodes)): - node = nodes[i] - c = node.center - # Haven't overflown yet - if ll.x < c.x and c.x < ur.x and ll.y < c.y and c.y < ur.y: - new_nodes.append(node) - continue - # Snap the pin to the perimeter and break the iteration - edge, _ = self.get_closest_edge(c) - if edge == "left": - fake_center = vector3d(ll.x + self.half_wire, c.y, c.z) - if edge == "bottom": - fake_center = vector3d(c.x, ll.y + self.half_wire, c.z) - if edge == "right": - fake_center = vector3d(ur.x - self.half_wire, c.y, c.z) - if edge == "top": - fake_center = vector3d(c.x, ur.y - self.half_wire, c.z) - node.center = fake_center - new_nodes.append(node) - break - return new_nodes - - - def add_perimeter_fake_pins(self): - """ - Add the fake pins on the perimeter to where the signals will be routed. - These perimeter fake pins are only used to replace layout pins at the - end of routing. - """ - - ll, ur = self.bbox - wide = self.track_wire - - for side in ["top", "bottom", "left", "right"]: - vertical = side in ["left", "right"] - - # Calculate the lower left coordinate - if side == "top": - offset = vector(ll.x, ur.y - wide) - elif side == "bottom": - offset = vector(ll.x, ll.y) - elif side == "left": - offset = vector(ll.x, ll.y) - elif side == "right": - offset = vector(ur.x - wide, ll.y) - - # Calculate width and height - shape = ur - ll - if vertical: - shape_width = wide - shape_height = shape.y - else: - shape_width = shape.x - shape_height = wide - - # Add this new pin - # They must lie on the non-preferred direction since the side supply - # pins will lie on the preferred direction - layer = self.get_layer(int(not vertical)) - nll = vector(offset.x, offset.y) - nur = vector(offset.x + shape_width, offset.y + shape_height) - rect = [nll, nur] - pin = graph_shape(name="fake", - rect=rect, - layer_name_pp=layer) - self.fake_pins.append(pin) - print("this is add_per") - print(pin.name) - print(pin.center) - - def create_fake_pin(self, pin): - """ Create a fake pin on the perimeter orthogonal to the given pin. """ - - ll, ur = self.bbox - c = pin.center() - print("inside pin name") - print("----------------------------------------------------------") - print(pin.name) - # Find the closest edge - edge, vertical = self.get_closest_edge(c) - - # Keep the fake pin out of the SRAM layout are so that they won't be - # blocked by previous signals if they're on the same orthogonal line - if edge == "left": - fake_center = vector(ll.x - self.track_wire * 2, c.y) - if edge == "bottom": - fake_center = vector(c.x, ll.y - self.track_wire * 2) - if edge == "right": - fake_center = vector(ur.x + self.track_wire * 2, c.y) - if edge == "top": - fake_center = vector(c.x, ur.y + self.track_wire * 2) - #fake_center = vector(ll.x - self.track_wire * 2, c.y) # test if here we could change the pin position at the layout - - # relocate the pin position - pattern = r'^dout' - if re.match(pattern, pin.name): - - if self.state_mod == 0: - pass# do not change, default - - elif self.state_mod == 1: # all top/bottom - if edge == "right": - vertical = False - fake_center = vector(c.x, ll.y - self.track_wire * 2) - self.distance_right += 1 - else: - if edge == "left": - vertical = False - fake_center = vector(c.x, ll.y + self.track_wire * 2) - self.distance_left += 1 - - elif self.state_mod == 2: # all left/right - if (edge == "bottom") or (edge == "right"):# change to the east - vertical = True - fake_center = vector(ur.x + self.track_wire * 2, ll.y + 30 + self.distance_right) - self.distance_right += 1 - else: - if (edge == "top") or (edge == "left"):# change to the west - vertical = True - fake_center = vector(ll.x - self.track_wire * 2, ur.y - 30 - self.distance_left) - self.distance_left += 1 - else: - debug.error("wrong state mod!", -1) - ''' # old version - if edge == "bottom":# change to the east - vertical = True - fake_center = vector(ur.x + self.track_wire * 2, ll.y + 30 + self.distance_right) - self.distance_right += 1 - else: - if edge == "top":# change to the west - vertical = True - fake_center = vector(ll.x - self.track_wire * 2, ur.y - 30 - self.distance_left) - self.distance_left += 1 - ''' - """ - pattern = r'^addr0_1' - if re.match(pattern, pin.name): - vertical = True - fake_center = vector(ll.x - self.track_wire * 2, c.y + self.track_wire * 4)# fix still do not know how to control the distance between every fake pin - #do not know why after this, all fake out pins are put at the same position -> because the originl inside pin has same y? - """ - # Create the fake pin shape - layer = self.get_layer(int(not vertical)) - half_wire_vector = vector([self.half_wire] * 2) - nll = fake_center - half_wire_vector - nur = fake_center + half_wire_vector - #not test jet - #half_wire_vector = vector([self.half_wire] * 2)# out *2 means vector([self.half_wire, self.half_wire]) - #nll = fake_center - half_wire_vector - half_wire_vector - #nur = fake_center + half_wire_vector + half_wire_vector - rect = [nll, nur] - pin = graph_shape(name="fake", - rect=rect, - layer_name_pp=layer) - print("this create_fake_pin") - print(pin.name) - print(pin.center) - return pin - - - def get_route_pairs(self, pin_names): - """ Return the pairs to be routed. """ - - to_route = [] - for name in pin_names: - print("==============the pin names===================") - print(name) - pin = next(iter(self.pins[name])) - fake = self.create_fake_pin(pin) - to_route.append((pin, fake, pin.distance(fake))) - return sorted(to_route, key=lambda x: x[2]) - - - def replace_layout_pins(self): - """ Replace the old layout pins with new ones around the perimeter. """ - - for name, pin in self.new_pins.items(): - print("name:{0}".format(name)) - print("replace_pin->{0}".format(pin)) - pin = graph_shape(pin.name, pin.boundary, pin.lpp) - # Find the intersection of this pin on the perimeter - for fake in self.fake_pins: - edge = pin.intersection(fake) - if edge: - break - self.design.replace_layout_pin(name, edge) - print("pass!!!!!!!") diff --git a/compiler/router/supply_router.py b/compiler/router/supply_router.py index e868c8b2b..fc61f4e64 100644 --- a/compiler/router/supply_router.py +++ b/compiler/router/supply_router.py @@ -76,8 +76,6 @@ def route(self, vdd_name="vdd", gnd_name="gnd"): # Create the graph g = graph(self) g.create_graph(source, target) - # debug - debug.warning("graph creat success!") # Find the shortest path from source to target path = g.find_shortest_path() # If no path is found, throw an error diff --git a/compiler/sram.py b/compiler/sram.py index 36ec253c4..6af99cb99 100644 --- a/compiler/sram.py +++ b/compiler/sram.py @@ -50,13 +50,65 @@ def __init__(self, sram_config=None, name=None): self.name = name from openram.modules.sram_1bank import sram_1bank as sram - #from openram.modules.sram_new import sram_1bank as sram - self.s = sram(name, sram_config) - self.s.create_netlist() + num_ports = OPTS.num_rw_ports + OPTS.num_r_ports + OPTS.num_w_ports + self.s = sram(name, sram_config) + self.s.create_netlist()# not placed & routed jet + cur_state = "IDLE" if not OPTS.netlist_only: - self.s.create_layout() - + i = 0 + while i < (OPTS.word_size + 100): + + debug.warning("current state: state = {0}".format(cur_state)) + + if cur_state == "IDLE":# default, fisrt try + try: + self.s.create_layout(position_add=i) + except AssertionError as e: + cur_state = "ALL_TOP_BOTTOM" + del self.s + self.s = sram(name, sram_config) + self.s.create_netlist() + if num_ports > 1: + i = 16 + else: + i = 0 + continue + cur_state = "FINISH" + break + + elif cur_state == "ALL_TOP_BOTTOM": + try: + self.s.create_layout(position_add=i, mod=1) + except AssertionError as e: + cur_state = "ALL_LEFT_RIGHT" + i = OPTS.word_size + 24 + del self.s + self.s = sram(name, sram_config) + self.s.create_netlist() + continue + cur_state = "FINISH" + break + + elif cur_state == "ALL_LEFT_RIGHT": + try: + self.s.create_layout(position_add=i, mod=2) + except AssertionError as e: + cur_state = "ALL_LEFT_RIGHT" + i = i + 1 + if i == (99 + OPTS.word_size):# failed in rounting + debug.error("Failed in rounting", -1) + break + del self.s + self.s = sram(name, sram_config) + self.s.create_netlist() + continue + cur_state = "FINISH" + break + else: + cur_state = "FINISH" + break + if not OPTS.is_unit_test: print_time("SRAM creation", datetime.datetime.now(), start_time) @@ -110,7 +162,7 @@ def save(self): spname = OPTS.output_path + self.s.name + ".sp" debug.print_raw("SP: Writing to {0}".format(spname)) self.sp_write(spname) - + #''' #comment the following state when generating big sram, and then disable drc/lvs, because maigc_ext stuck # Save a functional simulation file with default period functional(self.s, spname, @@ -132,7 +184,7 @@ def save(self): d.targ_write_ports = [self.s.write_ports[0]] d.write_delay_stimulus() print_time("DELAY", datetime.datetime.now(), start_time) - + #''' #comment the above when generating big sram, and then disable drc/;vs, bevause magic_ext stuck # Save trimmed spice file temp_trim_sp = "{0}trimmed.sp".format(OPTS.output_path) self.sp_write(temp_trim_sp, lvs=False, trim=True) diff --git a/compiler/sram_new_test.py b/compiler/sram_new_test.py deleted file mode 100644 index cbce85824..000000000 --- a/compiler/sram_new_test.py +++ /dev/null @@ -1,338 +0,0 @@ -# See LICENSE for licensing information. -# -# Copyright (c) 2016-2024 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 os -import shutil -import datetime -import copy -from openram import debug -from openram import sram_config as config -from openram import OPTS, print_time - - -class sram(): - """ - This is not a design module, but contains an SRAM design instance. - It could later try options of number of banks and organization to compare - results. - We can later add visualizer and other high-level functions as needed. - """ - def __init__(self, sram_config=None, name=None): - - # Create default configs if custom config isn't provided - if sram_config is None: - sram_config = config(word_size=OPTS.word_size, - num_words=OPTS.num_words, - write_size=OPTS.write_size, - num_banks=OPTS.num_banks, - words_per_row=OPTS.words_per_row, - num_spare_rows=OPTS.num_spare_rows, - num_spare_cols=OPTS.num_spare_cols) - - if name is None: - name = OPTS.output_name - - sram_config.set_local_config(self) - - # reset the static duplicate name checker for unit tests - # in case we create more than one SRAM - from openram.base import design - design.name_map=[] - - debug.info(2, "create sram of size {0} with {1} num of words {2} banks".format(self.word_size, - self.num_words, - self.num_banks)) - start_time = datetime.datetime.now() - - self.name = name - - from openram.modules.sram_new import sram_1bank as sram - - num_ports = OPTS.num_rw_ports + OPTS.num_r_ports + OPTS.num_w_ports - self.s = sram(name, sram_config) - self.s.create_netlist()# not placed & routed jet - cur_state = "IDLE" - if not OPTS.netlist_only: - i = 0 - while i < (OPTS.word_size + 100): - - print("current iteration: i = {0}".format(i)) - debug.warning("current state: state = {0}".format(cur_state)) - - if cur_state == "IDLE":# default, fisrt try - try: - self.s.create_layout_recrusive(position_add=i) - except AssertionError as e: - cur_state = "ALL_TOP_BOTTOM" - del self.s - self.s = sram(name, sram_config) - self.s.create_netlist() - if num_ports > 1: - i = 16 - else: - i = 0 - continue - cur_state = "FINISH" - break - - elif cur_state == "ALL_TOP_BOTTOM": - try: - self.s.create_layout_recrusive(position_add=i, mod=1) - except AssertionError as e: - cur_state = "ALL_LEFT_RIGHT" - i = OPTS.word_size + 24 - del self.s - self.s = sram(name, sram_config) - self.s.create_netlist() - continue - cur_state = "FINISH" - break - - elif cur_state == "ALL_LEFT_RIGHT": - try: - self.s.create_layout_recrusive(position_add=i, mod=2) - except AssertionError as e: - cur_state = "ALL_LEFT_RIGHT" - i = i + 1 - if i == (99 + OPTS.word_size):# failed in rounting - debug.error("Failed in rounting", -1) - break - del self.s - self.s = sram(name, sram_config) - self.s.create_netlist() - continue - cur_state = "FINISH" - break - else: - cur_state = "FINISH" - break - - ''' # 2nd version - self.s = sram(name, sram_config) - self.s.create_netlist()# not placed & routed jet - if not OPTS.netlist_only: - i = 0 - supply_route_not_found = False - while i < (OPTS.word_size + 100): - print("current iteration: i = {0}".format(i)) - try: - self.s.create_layout_recrusive(position_add=i) - except AssertionError as e: - supply_route_not_found = True - if i == (99 + OPTS.word_size):# failed in rounting - debug.error("Failed in rounting", -1) - break - - - if (supply_route_not_found): - del self.s - self.s = sram(name, sram_config) - self.s.create_netlist() - if i == 0:# after first try - i = OPTS.word_size + 20 - else: - i = i + 1 - supply_route_not_found = False - else:# successfully routed - break - ''' - '''#old version - self.s = sram(name, sram_config) - self.s.create_netlist()# not placed & routed jet - #self.s_tmp = copy.deepcopy(self.s) # need deepcopy - if not OPTS.netlist_only: - i = 98 - supply_route_not_found = False - #self.s_tmp = copy.deepcopy(self.s) # need deepcopy - while i < 100: - print("current iteration: {0}".format(i)) - try: - self.s.create_layout_recrusive(position_add=i) - except AssertionError as e: - supply_route_not_found = True - if i == 99:# failed in rounting - debug.error("Failed in rounting", -1) - break - - - if (supply_route_not_found): - #self.s_tmp = copy.deepcopy(self.s) # need deepcopy - del self.s - self.s = sram(name, sram_config) - self.s.create_netlist() - i = i + 1 - supply_route_not_found = False - else:# successfully routed - #self.s = copy.deepcopy(self.s_tmp) # need deepcopy - break - ''' - if not OPTS.is_unit_test: - print_time("SRAM creation", datetime.datetime.now(), start_time) - - def get_sp_name(self): - if OPTS.use_pex: - # Use the extracted spice file - return self.pex_name - else: - # Use generated spice file for characterization - return self.sp_name - - def sp_write(self, name, lvs=False, trim=False): - self.s.sp_write(name, lvs, trim) - - def lef_write(self, name): - self.s.lef_write(name) - - def gds_write(self, name): - self.s.gds_write(name) - - def verilog_write(self, name): - self.s.verilog_write(name) - if self.num_banks != 1: - from openram.modules.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. - Include defaults and anything changed by input config.""" - f = open(name, "w") - var_dict = dict((name, getattr(OPTS, name)) for name in dir(OPTS) if not name.startswith('__') and not callable(getattr(OPTS, name))) - for var_name, var_value in var_dict.items(): - if isinstance(var_value, str): - f.write(str(var_name) + " = " + "\"" + str(var_value) + "\"\n") - else: - f.write(str(var_name) + " = " + str(var_value)+ "\n") - f.close() - - def save(self): - """ Save all the output files while reporting time to do it as well. """ - - # Import this at the last minute so that the proper tech file - # is loaded and the right tools are selected - from openram import verify - from openram.characterizer import functional - from openram.characterizer import delay - - # Save the spice file - start_time = datetime.datetime.now() - spname = OPTS.output_path + self.s.name + ".sp" - debug.print_raw("SP: Writing to {0}".format(spname)) - self.sp_write(spname) - ''' - # Save a functional simulation file with default period - functional(self.s, - spname, - cycles=200, - output_path=OPTS.output_path) - print_time("Spice writing", datetime.datetime.now(), start_time) - - # Save stimulus and measurement file - start_time = datetime.datetime.now() - debug.print_raw("DELAY: Writing stimulus...") - d = delay(self.s, spname, ("TT", 5, 25), output_path=OPTS.output_path) - if (self.s.num_spare_rows == 0): - probe_address = "1" * self.s.addr_size - else: - probe_address = "0" + "1" * (self.s.addr_size - 1) - probe_data = self.s.word_size - 1 - d.analysis_init(probe_address, probe_data) - d.targ_read_ports.extend(self.s.read_ports) - d.targ_write_ports = [self.s.write_ports[0]] - d.write_delay_stimulus() - print_time("DELAY", datetime.datetime.now(), start_time) - ''' - # Save trimmed spice file - temp_trim_sp = "{0}trimmed.sp".format(OPTS.output_path) - self.sp_write(temp_trim_sp, lvs=False, trim=True) - - if not OPTS.netlist_only: - # Write the layout - start_time = datetime.datetime.now() - gdsname = OPTS.output_path + self.s.name + ".gds" - debug.print_raw("GDS: Writing to {0}".format(gdsname)) - self.gds_write(gdsname) - if OPTS.check_lvsdrc: - verify.write_drc_script(cell_name=self.s.name, - gds_name=os.path.basename(gdsname), - extract=True, - final_verification=True, - output_path=OPTS.output_path) - print_time("GDS", datetime.datetime.now(), start_time) - - # Create a LEF physical model - start_time = datetime.datetime.now() - lefname = OPTS.output_path + self.s.name + ".lef" - debug.print_raw("LEF: Writing to {0}".format(lefname)) - self.lef_write(lefname) - print_time("LEF", datetime.datetime.now(), start_time) - - # Save the LVS file - start_time = datetime.datetime.now() - lvsname = OPTS.output_path + self.s.name + ".lvs.sp" - debug.print_raw("LVS: Writing to {0}".format(lvsname)) - self.sp_write(lvsname, lvs=True) - if not OPTS.netlist_only and OPTS.check_lvsdrc: - verify.write_lvs_script(cell_name=self.s.name, - gds_name=os.path.basename(gdsname), - sp_name=os.path.basename(lvsname), - final_verification=True, - output_path=OPTS.output_path) - print_time("LVS writing", datetime.datetime.now(), start_time) - - # Save the extracted spice file - if OPTS.use_pex: - start_time = datetime.datetime.now() - # Output the extracted design if requested - pexname = OPTS.output_path + self.s.name + ".pex.sp" - spname = OPTS.output_path + self.s.name + ".sp" - verify.run_pex(self.s.name, gdsname, spname, output=pexname) - sp_file = pexname - print_time("Extraction", datetime.datetime.now(), start_time) - else: - # Use generated spice file for characterization - sp_file = spname - - # Characterize the design - start_time = datetime.datetime.now() - from openram.characterizer import lib - debug.print_raw("LIB: Characterizing... ") - lib(out_dir=OPTS.output_path, sram=self.s, sp_file=sp_file) - print_time("Characterization", datetime.datetime.now(), start_time) - - # Write the config file - start_time = datetime.datetime.now() - try: - from shutil import copyfile - copyfile(OPTS.config_file, OPTS.output_path + OPTS.output_name + '.py') - except shutil.SameFileError: - pass - debug.print_raw("Config: Writing to {0}".format(OPTS.output_path + OPTS.output_name + '.py')) - print_time("Config", datetime.datetime.now(), start_time) - - # Write the datasheet - start_time = datetime.datetime.now() - from openram.datasheet import datasheet_gen - dname = OPTS.output_path + self.s.name + ".html" - debug.print_raw("Datasheet: Writing to {0}".format(dname)) - datasheet_gen.datasheet_write(dname) - print_time("Datasheet", datetime.datetime.now(), start_time) - - # Write a verilog model - start_time = datetime.datetime.now() - 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) - - # Write out options if specified - if OPTS.output_extended_config: - start_time = datetime.datetime.now() - oname = OPTS.output_path + OPTS.output_name + "_extended.py" - debug.print_raw("Extended Config: Writing to {0}".format(oname)) - self.extended_config_write(oname) - print_time("Extended Config", datetime.datetime.now(), start_time) diff --git a/sram_compiler.py b/sram_compiler.py index fc2f7f51e..40480e301 100755 --- a/sram_compiler.py +++ b/sram_compiler.py @@ -69,12 +69,13 @@ debug.print_raw(path) # Create an SRAM (we can also pass sram_config, see documentation/tutorials for details) -#from openram import sram -#s = sram() -from openram import sram_new_test -s = sram_new_test.sram() +from openram import sram +s = sram() +#from openram import sram_openroad_test +#s = sram_openroad_test.sram() # Output the files for the resulting SRAM s.save() +#s.save_only() ''' del s s = sram_road.sram(mod=1) From 20e454925a8d5f7679302d89f6bdd6146b13e5b0 Mon Sep 17 00:00:00 2001 From: FriedrichWu Date: Thu, 5 Sep 2024 10:14:00 +0200 Subject: [PATCH 11/26] add io placer --- compiler/modules/sram_openroad.py | 1512 +++++++++++++++++++++++++++++ compiler/router/io_pin_placer.py | 164 ++++ compiler/sram_openroad_test.py | 247 +++++ 3 files changed, 1923 insertions(+) create mode 100644 compiler/modules/sram_openroad.py create mode 100644 compiler/router/io_pin_placer.py create mode 100644 compiler/sram_openroad_test.py diff --git a/compiler/modules/sram_openroad.py b/compiler/modules/sram_openroad.py new file mode 100644 index 000000000..ec8f95bf7 --- /dev/null +++ b/compiler/modules/sram_openroad.py @@ -0,0 +1,1512 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2024 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. +# +# This file aiming to create the sram module without routing +import datetime +from math import ceil +from importlib import import_module, reload +from openram import debug +from openram.base import vector +from openram.base import channel_route +from openram.base import design +from openram.base import verilog +from openram.base import lef +from openram.sram_factory import factory +from openram.tech import spice +from openram import OPTS, print_time + + +class sram_1bank(design, verilog, lef): + """ + Procedures specific to a one bank SRAM. + """ + def __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 openram.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 + + # delay control logic does not have RBLs + self.has_rbl = OPTS.control_logic != "control_logic_delay" + + 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 place_io_pins(self, bbox): + """Place IO pins""" + # 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 openram.router.io_pin_placer import io_pin_placer as placer + pl = placer(layers=self.m3_stack, + bbox=bbox, + design=self) + pl.add_io_pins(pins_to_route) + #pl.design.remove_layout_pin("wmask0[0]") # this could use for deleting pins + ''' + # add the corresponding pin to the edge, the io pin will be put defaut as the nearest edge + for pin_name in pins_to_route: + ll, ur = bbox + pin = self.get_pin(pin_name) + print(pin) + c = pin.center() + # Find the closest edge + edge, vertical = self.get_closest_edge(c,bbox=bbox) + + # Keep the fake pin out of the SRAM layout are so that they won't be + # blocked by previous signals if they're on the same orthogonal line + if edge == "left": + fake_center = vector(ll.x - rtr.track_wire * 2, c.y) + if edge == "bottom": + fake_center = vector(c.x, ll.y - rtr.track_wire * 2) + if edge == "right": + fake_center = vector(ur.x + rtr.track_wire * 2, c.y) + if edge == "top": + fake_center = vector(c.x, ur.y + rtr.track_wire * 2) + + # Create the fake pin shape + layer = rtr.get_layer(int(not vertical)) + + self.add_layout_pin_rect_center(text=pin_name + "_IO", + layer=layer, + offset=fake_center) + rtr.add_perimeter_fake_pins() + ''' + def get_closest_edge(self, point, bbox): + """ Return a point's the closest edge and the edge's axis direction. """ + + ll, ur = bbox + + # Snap the pin to the perimeter and break the iteration + ll_diff_x = abs(point.x - ll.x) + ll_diff_y = abs(point.y - ll.y) + ur_diff_x = abs(point.x - ur.x) + ur_diff_y = abs(point.y - ur.y) + min_diff = min(ll_diff_x, ll_diff_y, ur_diff_x, ur_diff_y) + + if min_diff == ll_diff_x: + return "left", True + if min_diff == ll_diff_y: + return "bottom", False + if min_diff == ur_diff_x: + return "right", True + return "top", False + + def create_layout_openroad(self): + # only placement of submoduels & io pins + # not routed inner-signal/escape-signal/pdn + start_time = datetime.datetime.now() + self.place_instances() + if not OPTS.is_unit_test: + print_time("Placement", datetime.datetime.now(), start_time) + + # Some technologies have an isolation + self.add_dnwell(inflate=2.5) + # add IO pins + init_bbox = self.get_bbox() + + self.add_layout_pins() + self.place_io_pins(bbox=init_bbox) + + # add power rings + # use current supply router + if OPTS.route_supplies: + self.route_supplies(init_bbox) + + 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_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]) + + from openram.router import supply_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.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=None): + """ + 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 openram.router import signal_escape_router as router + rtr = router(layers=self.m3_stack, + bbox=bbox, + design=self) + rtr.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_module("." + OPTS.control_logic, "openram.modules")) + 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)) + if self.has_rbl: + 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)) + if self.has_rbl: + 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): + """ + This adds the modules for a single bank SRAM with control + logic. + """ + + self.bank_inst=self.create_bank(0) + + self.control_logic_insts = self.create_control_logic() + + self.row_addr_dff_insts = self.create_row_addr_dff() + + if self.col_addr_dff: + self.col_addr_dff_insts = self.create_col_addr_dff() + + if self.write_size != self.word_size: + self.wmask_dff_insts = self.create_wmask_dff() + self.data_dff_insts = self.create_data_dff() + else: + self.data_dff_insts = self.create_data_dff() + + if self.num_spare_cols: + self.spare_wen_dff_insts = self.create_spare_wen_dff() + else: + self.num_spare_cols = 0 + + def place_instances(self): + """ + This places the instances for a single bank SRAM with control + logic and up to 2 ports. + """ + + # No orientation or offset + self.place_bank(self.bank_inst, [0, 0], 1, 1) + + # The control logic is placed such that the vertical center (between the delay/RBL and + # the actual control logic is aligned with the vertical center of the bank (between + # the sense amps/column mux and cell array) + # The x-coordinate is placed to allow a single clock wire (plus an extra pitch) + # up to the row address DFFs. + self.control_pos = [None] * len(self.all_ports) + self.row_addr_pos = [None] * len(self.all_ports) + + # DFFs are placd on their own + self.col_addr_pos = [None] * len(self.all_ports) + self.wmask_pos = [None] * len(self.all_ports) + self.spare_wen_pos = [None] * len(self.all_ports) + self.data_pos = [None] * len(self.all_ports) + + # These positions utilize the channel route sizes. + # FIXME: Auto-compute these rather than manual computation. + # If a horizontal channel, they rely on the vertical channel non-preferred (contacted) pitch. + # If a vertical channel, they rely on the horizontal channel non-preferred (contacted) pitch. + # So, m3 non-pref pitch means that this is routed on the m2 layer. + self.data_bus_gap = self.m4_nonpref_pitch * 2 + + # Spare wen are on a separate layer so not included + # Start with 1 track minimum + self.data_bus_size = [1] * len(self.all_ports) + self.col_addr_bus_size = [1] * len(self.all_ports) + for port in self.all_ports: + # The column address wires are routed separately from the data bus and will always be smaller. + # All ports need the col addr flops + self.col_addr_bus_size[port] = self.col_addr_size * self.m4_nonpref_pitch + # Write ports need the data input flops and write mask flops + if port in self.write_ports: + self.data_bus_size[port] += self.num_wmasks + self.word_size + # This is for the din pins that get routed in the same channel + # when we have dout and din together + if port in self.readwrite_ports: + self.data_bus_size[port] += self.word_size + # Convert to length + self.data_bus_size[port] *= self.m4_nonpref_pitch + # Add the gap in unit length + self.data_bus_size[port] += self.data_bus_gap + + # The control and row addr flops are independent of any bus widths. + self.place_control() + self.place_row_addr_dffs() + + # Place with an initial wide channel (from above) + self.place_dffs() + + # Route the channel and set to the new data bus size + # We need to temporarily add some pins for the x offsets + # but we'll remove them so that they have the right y + # offsets after the DFF placement. + self.add_layout_pins(add_vias=False) + self.route_dffs(add_routes=False) + self.remove_layout_pins() + + # Re-place with the new channel size + self.place_dffs() + + def place_row_addr_dffs(self): + """ + Must be run after place control logic. + """ + port = 0 + # The row address bits are placed above the control logic aligned on the right. + x_offset = self.control_logic_insts[port].rx() - self.row_addr_dff_insts[port].width + # It is above the control logic and the predecoder array + y_offset = max(self.control_logic_insts[port].uy(), self.bank.predecoder_top) + + self.row_addr_pos[port] = vector(x_offset, y_offset) + self.row_addr_dff_insts[port].place(self.row_addr_pos[port]) + + if len(self.all_ports)>1: + port = 1 + # The row address bits are placed above the control logic aligned on the left. + x_offset = self.control_pos[port].x - self.control_logic_insts[port].width + self.row_addr_dff_insts[port].width + # If it can be placed above the predecoder and below the control logic, do it + y_offset = self.bank.predecoder_bottom + self.row_addr_pos[port] = vector(x_offset, y_offset) + self.row_addr_dff_insts[port].place(self.row_addr_pos[port], mirror="XY") + + def place_control(self): + port = 0 + + # This includes 2 M2 pitches for the row addr clock line. + # The delay line is aligned with the bitcell array while the control logic is aligned with the port_data + # using the control_logic_center value. + self.control_pos[port] = vector(-self.control_logic_insts[port].width - 2 * self.m2_pitch, + self.bank.bank_array_ll.y - self.control_logic_insts[port].mod.control_logic_center.y) + self.control_logic_insts[port].place(self.control_pos[port]) + if len(self.all_ports) > 1: + port = 1 + # This includes 2 M2 pitches for the row addr clock line + # The delay line is aligned with the bitcell array while the control logic is aligned with the port_data + # using the control_logic_center value. + self.control_pos[port] = vector(self.bank_inst.rx() + self.control_logic_insts[port].width + 2 * self.m2_pitch, + self.bank.bank_array_ur.y + + self.control_logic_insts[port].height + - self.control_logic_insts[port].height + + self.control_logic_insts[port].mod.control_logic_center.y) + self.control_logic_insts[port].place(self.control_pos[port], mirror="XY") + + def place_dffs(self): + """ + Place the col addr, data, wmask, and spare data DFFs. + This can be run more than once after we recompute the channel width. + """ + + port = 0 + # Add the col address flops below the bank to the right of the control logic + x_offset = self.control_logic_insts[port].rx() + self.dff.width + # Place it a data bus below the x-axis, but at least as low as the control logic to not block + # the control logic signals + y_offset = min(-self.data_bus_size[port] - self.dff.height, + self.control_logic_insts[port].by()) + if self.col_addr_dff: + self.col_addr_pos[port] = vector(x_offset, + y_offset) + self.col_addr_dff_insts[port].place(self.col_addr_pos[port]) + x_offset = self.col_addr_dff_insts[port].rx() + else: + self.col_addr_pos[port] = vector(x_offset, 0) + + if port in self.write_ports: + 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) + self.wmask_dff_insts[port].place(self.wmask_pos[port]) + x_offset = self.wmask_dff_insts[port].rx() + + # Add the data flops below the write mask flops. + self.data_pos[port] = vector(x_offset, + y_offset) + self.data_dff_insts[port].place(self.data_pos[port]) + x_offset = self.data_dff_insts[port].rx() + + # Add spare write enable flops to the right of data flops since the spare columns + # will be on the right + if self.num_spare_cols: + self.spare_wen_pos[port] = vector(x_offset, + y_offset) + self.spare_wen_dff_insts[port].place(self.spare_wen_pos[port]) + x_offset = self.spare_wen_dff_insts[port].rx() + + else: + self.wmask_pos[port] = vector(x_offset, y_offset) + self.data_pos[port] = vector(x_offset, y_offset) + self.spare_wen_pos[port] = vector(x_offset, y_offset) + + if len(self.all_ports) > 1: + port = 1 + + # Add the col address flops below the bank to the right of the control logic + x_offset = self.control_logic_insts[port].lx() - 2 * self.dff.width + # Place it a data bus below the x-axis, but at least as high as the control logic to not block + # the control logic signals + y_offset = max(self.bank.height + self.data_bus_size[port] + self.dff.height, + self.control_logic_insts[port].uy() - self.dff.height) + if self.col_addr_dff: + self.col_addr_pos[port] = vector(x_offset, + y_offset) + self.col_addr_dff_insts[port].place(self.col_addr_pos[port], mirror="XY") + x_offset = self.col_addr_dff_insts[port].lx() + else: + self.col_addr_pos[port] = vector(x_offset, y_offset) + + if port in self.write_ports: + # Add spare write enable flops to the right of the data flops since the spare + # columns will be on the left + if self.num_spare_cols: + self.spare_wen_pos[port] = vector(x_offset - self.spare_wen_dff_insts[port].width, + y_offset) + 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 != 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) + self.wmask_dff_insts[port].place(self.wmask_pos[port], mirror="MX") + x_offset = self.wmask_dff_insts[port].lx() + + # Add the data flops below the write mask flops. + self.data_pos[port] = vector(x_offset - self.data_dff_insts[port].width, + y_offset) + self.data_dff_insts[port].place(self.data_pos[port], mirror="MX") + else: + self.wmask_pos[port] = vector(x_offset, y_offset) + self.data_pos[port] = vector(x_offset, y_offset) + self.spare_wen_pos[port] = vector(x_offset, y_offset) + + def add_layout_pins(self, add_vias=True): + """ + Add the top-level pins for a single bank SRAM with control. + """ + for port in self.all_ports: + # Hack: If we are escape routing, set the pin layer to + # None so that we will start from the pin layer + # Otherwise, set it as the pin layer so that no vias are added. + # Otherwise, when we remove pins to move the dff array dynamically, + # we will leave some remaining vias when the pin locations change. + if add_vias: + pin_layer = None + else: + pin_layer = self.pwr_grid_layers[0] + + # Connect the control pins as inputs + for signal in self.control_logic_inputs[port]: + if signal.startswith("rbl"): + continue + self.add_io_pin(self.control_logic_insts[port], + signal, + signal + "{}".format(port), + start_layer=pin_layer) + + if port in self.write_ports: + for bit in range(self.word_size + self.num_spare_cols): + self.add_io_pin(self.data_dff_insts[port], + "din_{}".format(bit), + "din{0}[{1}]".format(port, bit), + start_layer=pin_layer) + + if port in self.readwrite_ports or port in self.read_ports: + for bit in range(self.word_size + self.num_spare_cols): + self.add_io_pin(self.bank_inst, + "dout{0}_{1}".format(port, bit), + "dout{0}[{1}]".format(port, bit), + start_layer=pin_layer) + + for bit in range(self.col_addr_size): + self.add_io_pin(self.col_addr_dff_insts[port], + "din_{}".format(bit), + "addr{0}[{1}]".format(port, bit), + start_layer=pin_layer) + + for bit in range(self.row_addr_size): + self.add_io_pin(self.row_addr_dff_insts[port], + "din_{}".format(bit), + "addr{0}[{1}]".format(port, bit + self.col_addr_size), + start_layer=pin_layer) + + if port in self.write_ports: + 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), + "wmask{0}[{1}]".format(port, bit), + start_layer=pin_layer) + + if port in self.write_ports: + if self.num_spare_cols == 1: + self.add_io_pin(self.spare_wen_dff_insts[port], + "din_{}".format(0), + "spare_wen{0}".format(port), + start_layer=pin_layer) + else: + for bit in range(self.num_spare_cols): + self.add_io_pin(self.spare_wen_dff_insts[port], + "din_{}".format(bit), + "spare_wen{0}[{1}]".format(port, bit), + start_layer=pin_layer) + + def route_layout(self): + """ Route a single bank SRAM """ + + self.route_clk() + + self.route_control_logic() + + self.route_row_addr_dff() + + self.route_dffs() + + # We add the vias to M3 before routing supplies because + # they might create some blockages + self.add_layout_pins() + + # Some technologies have an isolation + self.add_dnwell(inflate=2.5) + + init_bbox = self.get_bbox() + # Route the supplies together and/or to the ring/stripes. + # Route the pins to the perimeter + if OPTS.perimeter_pins: + # We now route the escape routes far enough out so that they will + # reach past the power ring or stripes on the sides + self.route_escape_pins(init_bbox) + if OPTS.route_supplies: + self.route_supplies(init_bbox) + + + def route_dffs(self, add_routes=True): + + for port in self.all_ports: + self.route_dff(port, add_routes) + + def route_dff(self, port, add_routes): + + # This is only done when we add_routes because the data channel will be larger + # so that can be used for area estimation. + if add_routes: + self.route_col_addr_dffs(port) + + self.route_data_dffs(port, add_routes) + + def route_col_addr_dffs(self, port): + + route_map = [] + + # column mux dff is routed on it's own since it is to the far end + # decoder inputs are min pitch M2, so need to use lower layer stack + if self.col_addr_size > 0: + dff_names = ["dout_{}".format(x) for x in range(self.col_addr_size)] + dff_pins = [self.col_addr_dff_insts[port].get_pin(x) for x in dff_names] + bank_names = ["addr{0}_{1}".format(port, x) for x in range(self.col_addr_size)] + bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] + route_map.extend(list(zip(bank_pins, dff_pins))) + + if len(route_map) > 0: + + # This layer stack must be different than the data dff layer stack + layer_stack = self.m1_stack + + if port == 0: + offset = vector(self.control_logic_insts[port].rx() + self.dff.width, + - self.data_bus_size[port] + 2 * self.m3_pitch) + cr = channel_route(netlist=route_map, + offset=offset, + layer_stack=layer_stack, + parent=self) + # This causes problem in magic since it sometimes cannot extract connectivity of instances + # with no active devices. + self.add_inst(cr.name, cr) + self.connect_inst([]) + # self.add_flat_inst(cr.name, cr) + else: + offset = vector(0, + self.bank.height + self.m3_pitch) + cr = channel_route(netlist=route_map, + offset=offset, + layer_stack=layer_stack, + parent=self) + # This causes problem in magic since it sometimes cannot extract connectivity of instances + # with no active devices. + self.add_inst(cr.name, cr) + self.connect_inst([]) + # self.add_flat_inst(cr.name, cr) + + def route_data_dffs(self, port, add_routes): + route_map = [] + + # wmask dff + if self.num_wmasks > 0 and port in self.write_ports: + dff_names = ["dout_{}".format(x) for x in range(self.num_wmasks)] + dff_pins = [self.wmask_dff_insts[port].get_pin(x) for x in dff_names] + bank_names = ["bank_wmask{0}_{1}".format(port, x) for x in range(self.num_wmasks)] + bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] + route_map.extend(list(zip(bank_pins, dff_pins))) + + if port in self.write_ports: + # synchronized inputs from data dff + dff_names = ["dout_{}".format(x) for x in range(self.word_size + self.num_spare_cols)] + dff_pins = [self.data_dff_insts[port].get_pin(x) for x in dff_names] + bank_names = ["din{0}_{1}".format(port, x) for x in range(self.word_size + self.num_spare_cols)] + bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] + route_map.extend(list(zip(bank_pins, dff_pins))) + + # spare wen dff + if self.num_spare_cols > 0 and port in self.write_ports: + dff_names = ["dout_{}".format(x) for x in range(self.num_spare_cols)] + dff_pins = [self.spare_wen_dff_insts[port].get_pin(x) for x in dff_names] + bank_names = ["bank_spare_wen{0}_{1}".format(port, x) for x in range(self.num_spare_cols)] + bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] + route_map.extend(list(zip(bank_pins, dff_pins))) + + if len(route_map) > 0: + + # This layer stack must be different than the column addr dff layer stack + layer_stack = self.m3_stack + if port == 0: + # This is relative to the bank at 0,0 or the s_en which is routed on M3 also + if "s_en" in self.control_logic_insts[port].mod.pin_map: + y_bottom = min(0, self.control_logic_insts[port].get_pin("s_en").by()) + else: + y_bottom = 0 + + y_offset = y_bottom - self.data_bus_size[port] + 2 * self.m3_pitch + offset = vector(self.control_logic_insts[port].rx() + self.dff.width, + y_offset) + cr = channel_route(netlist=route_map, + offset=offset, + layer_stack=layer_stack, + parent=self) + if add_routes: + # This causes problem in magic since it sometimes cannot extract connectivity of instances + # with no active devices. + self.add_inst(cr.name, cr) + self.connect_inst([]) + # self.add_flat_inst(cr.name, cr) + else: + self.data_bus_size[port] = max(cr.height, self.col_addr_bus_size[port]) + self.data_bus_gap + else: + if "s_en" in self.control_logic_insts[port].mod.pin_map: + y_top = max(self.bank.height, self.control_logic_insts[port].get_pin("s_en").uy()) + else: + y_top = self.bank.height + y_offset = y_top + self.m3_pitch + offset = vector(0, + y_offset) + cr = channel_route(netlist=route_map, + offset=offset, + layer_stack=layer_stack, + parent=self) + if add_routes: + # This causes problem in magic since it sometimes cannot extract connectivity of instances + # with no active devices. + self.add_inst(cr.name, cr) + self.connect_inst([]) + # self.add_flat_inst(cr.name, cr) + else: + self.data_bus_size[port] = max(cr.height, self.col_addr_bus_size[port]) + self.data_bus_gap + + def route_clk(self): + """ Route the clock network """ + + # This is the actual input to the SRAM + for port in self.all_ports: + # Connect all of these clock pins to the clock in the central bus + # This is something like a "spine" clock distribution. The two spines + # are clk_buf and clk_buf_bar + control_clk_buf_pin = self.control_logic_insts[port].get_pin("clk_buf") + control_clk_buf_pos = control_clk_buf_pin.center() + + # This uses a metal2 track to the right (for port0) of the control/row addr DFF + # to route vertically. For port1, it is to the left. + row_addr_clk_pin = self.row_addr_dff_insts[port].get_pin("clk") + if port % 2: + control_clk_buf_pos = control_clk_buf_pin.lc() + row_addr_clk_pos = row_addr_clk_pin.lc() + mid1_pos = vector(self.row_addr_dff_insts[port].lx() - self.m2_pitch, + row_addr_clk_pos.y) + else: + control_clk_buf_pos = control_clk_buf_pin.rc() + row_addr_clk_pos = row_addr_clk_pin.rc() + mid1_pos = vector(self.row_addr_dff_insts[port].rx() + self.m2_pitch, + row_addr_clk_pos.y) + + # This is the steiner point where the net branches out + clk_steiner_pos = vector(mid1_pos.x, control_clk_buf_pos.y) + self.add_path(control_clk_buf_pin.layer, [control_clk_buf_pos, clk_steiner_pos]) + self.add_via_stack_center(from_layer=control_clk_buf_pin.layer, + to_layer="m2", + offset=clk_steiner_pos) + + # Note, the via to the control logic is taken care of above + self.add_wire(self.m2_stack[::-1], + [row_addr_clk_pos, mid1_pos, clk_steiner_pos]) + + if self.col_addr_dff: + dff_clk_pin = self.col_addr_dff_insts[port].get_pin("clk") + dff_clk_pos = dff_clk_pin.center() + mid_pos = vector(clk_steiner_pos.x, dff_clk_pos.y) + self.add_wire(self.m2_stack[::-1], + [dff_clk_pos, mid_pos, clk_steiner_pos]) + elif port in self.write_ports: + data_dff_clk_pin = self.data_dff_insts[port].get_pin("clk") + data_dff_clk_pos = data_dff_clk_pin.center() + mid_pos = vector(clk_steiner_pos.x, data_dff_clk_pos.y) + # In some designs, the steiner via will be too close to the mid_pos via + # so make the wire as wide as the contacts + self.add_path("m2", + [mid_pos, clk_steiner_pos], + width=max(self.m2_via.width, self.m2_via.height)) + self.add_wire(self.m2_stack[::-1], + [data_dff_clk_pos, mid_pos, clk_steiner_pos]) + + def route_control_logic(self): + """ + Route the control logic pins that are not inputs + """ + + for port in self.all_ports: + for signal in self.control_logic_outputs[port]: + # The clock gets routed separately and is not a part of the bank + if "clk" in signal: + continue + src_pin = self.control_logic_insts[port].get_pin(signal) + dest_pin = self.bank_inst.get_pin(signal + "{}".format(port)) + self.connect_vbus(src_pin, dest_pin) + + if self.has_rbl: + for port in self.all_ports: + # Only input (besides pins) is the replica bitline + src_pin = self.control_logic_insts[port].get_pin("rbl_bl") + dest_pin = self.bank_inst.get_pin("rbl_bl_{0}_{0}".format(port)) + self.add_wire(self.m3_stack, + [src_pin.center(), vector(src_pin.cx(), dest_pin.cy()), dest_pin.rc()]) + self.add_via_stack_center(from_layer=src_pin.layer, + to_layer="m4", + offset=src_pin.center()) + self.add_via_stack_center(from_layer=dest_pin.layer, + to_layer="m3", + offset=dest_pin.center()) + + def route_row_addr_dff(self): + """ + Connect the output of the row flops to the bank pins + """ + for port in self.all_ports: + for bit in range(self.row_addr_size): + flop_name = "dout_{}".format(bit) + bank_name = "addr{0}_{1}".format(port, bit + self.col_addr_size) + flop_pin = self.row_addr_dff_insts[port].get_pin(flop_name) + bank_pin = self.bank_inst.get_pin(bank_name) + flop_pos = flop_pin.center() + bank_pos = bank_pin.center() + mid_pos = vector(bank_pos.x, flop_pos.y) + self.add_via_stack_center(from_layer=flop_pin.layer, + to_layer="m3", + offset=flop_pos) + self.add_path("m3", [flop_pos, mid_pos]) + self.add_via_stack_center(from_layer=bank_pin.layer, + to_layer="m3", + offset=mid_pos) + self.add_path(bank_pin.layer, [mid_pos, bank_pos]) + + 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. + """ + return + for n in self.control_logic_outputs[0]: + pin = self.control_logic_insts[0].get_pin(n) + self.add_label(text=n, + layer=pin.layer, + offset=pin.center()) + + def graph_exclude_data_dff(self): + """ + Removes data dff and wmask dff (if applicable) from search graph. + """ + # 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 != self.word_size: + for inst in self.wmask_dff_insts: + self.graph_inst_exclude.add(inst) + if self.num_spare_cols: + for inst in self.spare_wen_dff_insts: + self.graph_inst_exclude.add(inst) + + def graph_exclude_addr_dff(self): + """ + Removes data dff from search graph. + """ + # Address is considered not part of the critical path, subjectively removed + for inst in self.row_addr_dff_insts: + self.graph_inst_exclude.add(inst) + + if self.col_addr_dff: + for inst in self.col_addr_dff_insts: + self.graph_inst_exclude.add(inst) + + def graph_exclude_ctrl_dffs(self): + """ + Exclude dffs for CSB, WEB, etc from graph + """ + # Insts located in control logic, exclusion function called here + for inst in self.control_logic_insts: + inst.mod.graph_exclude_dffs() + + def get_cell_name(self, inst_name, row, col): + """ + Gets the spice name of the target bitcell. + """ + # Sanity check in case it was forgotten + if inst_name.find("x") != 0: + inst_name = "x" + inst_name + return self.bank_inst.mod.get_cell_name(inst_name + "{}x".format(OPTS.hier_seperator) + self.bank_inst.name, row, col) + + def get_bank_num(self, inst_name, row, col): + return 0 diff --git a/compiler/router/io_pin_placer.py b/compiler/router/io_pin_placer.py new file mode 100644 index 000000000..e48f1f9d2 --- /dev/null +++ b/compiler/router/io_pin_placer.py @@ -0,0 +1,164 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2024 Regents of the University of California, Santa Cruz +# All rights reserved. +# +from openram import debug +from openram.base.vector import vector +from openram.base.vector3d import vector3d +from openram import OPTS +from .graph import graph +from .graph_shape import graph_shape +from .router import router +import re + +class io_pin_placer(router): + """ + This is the signal escape router that uses the Hanan grid graph method. + """ + + def __init__(self, layers, design, bbox=None): + + # `router` is the base router class + router.__init__(self, layers, design, bbox) + + # New pins are the side supply pins + self.new_pins = {} + + # added_io_pins + self.io_pins_added_left = [] + self.io_pins_added_right = [] + self.io_pins_added_up = [] + self.io_pins_added_down = [] + + + def get_closest_edge(self, point): + """ Return a point's the closest edge and the edge's axis direction. """ + + ll, ur = self.bbox + + # Snap the pin to the perimeter and break the iteration + ll_diff_x = abs(point.x - ll.x) + ll_diff_y = abs(point.y - ll.y) + ur_diff_x = abs(point.x - ur.x) + ur_diff_y = abs(point.y - ur.y) + min_diff = min(ll_diff_x, ll_diff_y, ur_diff_x, ur_diff_y) + + if min_diff == ll_diff_x: + return "left", True + if min_diff == ll_diff_y: + return "bottom", False + if min_diff == ur_diff_x: + return "right", True + return "top", False + + + def create_fake_pin(self, pin): + """ Create a fake pin on the perimeter orthogonal to the given pin. """ + + ll, ur = self.bbox + c = pin.center() + print("inside pin name") + print("----------------------------------------------------------") + print(pin.name) + # Find the closest edge + edge, vertical = self.get_closest_edge(c) + offset = 0.95 + 0.19 # FIX: this is the magic number to overcome the ovetflow problem at the boundary, may need a method + add_distance = 0 + # Keep the fake pin out of the SRAM layout are so that they won't be + # blocked by previous signals if they're on the same orthogonal line + if edge == "left": + fake_center = vector(ll.x - self.track_wire * 2 + offset, c.y) + is_too_close = any(abs(pin_added.center().y - fake_center.y) < (0.8 + self.half_wire * 4)for pin_added in self.io_pins_added_left) + while is_too_close: + debug.warning("overlap, changing position") + add_distance = add_distance + 0.8 + self.half_wire * 4 + fake_center = vector(ll.x - self.track_wire * 2 + offset, c.y + add_distance) + is_too_close = any(abs(pin_added.center().y - fake_center.y) < (0.8 + self.half_wire * 4)for pin_added in self.io_pins_added_left) + + if edge == "bottom": + fake_center = vector(c.x, ll.y - self.track_wire * 2 + offset) + is_too_close = any(abs(pin_added.center().x - fake_center.x) < (0.8 + self.half_wire * 4)for pin_added in self.io_pins_added_down) + while is_too_close: + debug.warning("overlap, changing position") + add_distance = add_distance + 0.8 + self.half_wire * 4 + fake_center = vector(c.x + add_distance, ll.y - self.track_wire * 2 + offset) + is_too_close = any(abs(pin_added.center().x - fake_center.x) < (0.8 + self.half_wire * 4)for pin_added in self.io_pins_added_down) + + if edge == "right": + fake_center = vector(ur.x + self.track_wire * 2 - offset, c.y) + is_too_close = any(abs(pin_added.center().y - fake_center.y) < (0.8 + self.half_wire * 4)for pin_added in self.io_pins_added_right) + while is_too_close: + debug.warning("overlap, changing position") + add_distance = add_distance + 0.8 + self.half_wire * 4 + fake_center = vector(ur.x + self.track_wire * 2 - offset, c.y + add_distance) + # debug + for pin_added in self.io_pins_added_right: + dis = abs(pin_added.center().y - fake_center.y) + debug.warning("current position is {0}".format(fake_center)) + debug.warning("distance from {0} is {1}".format(pin_added, dis)) + debug.warning("must disrance is {0}".format(0.8 + self.half_wire * 4)) + is_too_close = any(abs(pin_added.center().y - fake_center.y) < (0.8 + self.half_wire * 4)for pin_added in self.io_pins_added_right) + + if edge == "top": + fake_center = vector(c.x, ur.y + self.track_wire * 2 - offset) + is_too_close = any(abs(pin_added.center().x - fake_center.x) < (0.8 + self.half_wire * 4)for pin_added in self.io_pins_added_up) + while is_too_close: + debug.warning("overlap, changing position") + add_distance = add_distance + 0.8 + self.half_wire * 4 + fake_center = vector(c.x + add_distance, ur.y + self.track_wire * 2 - offset) + is_too_close = any(abs(pin_added.center().x - fake_center.x) < (0.8 + self.half_wire * 4)for pin_added in self.io_pins_added_up) + + # Create the fake pin shape + layer = self.get_layer(int(not vertical)) + half_wire_vector = vector([self.half_wire] * 2) + nll = fake_center - half_wire_vector - half_wire_vector + nur = fake_center + half_wire_vector + half_wire_vector + + rect = [nll, nur] + pin = graph_shape(name=pin.name + "_" + "fake", + rect=rect, + layer_name_pp=layer) + print("this create_fake_pin") + print(pin.name) + print(pin.center) + + if edge == "left": + self.io_pins_added_left.append(pin) + elif edge == "bottom": + self.io_pins_added_down.append(pin) + elif edge == "right": + self.io_pins_added_right.append(pin) + elif edge == "top": + self.io_pins_added_up.append(pin) + debug.warning("pin added: {0}".format(pin)) + + return pin + + + def add_io_pins(self, pin_names): + """ Add IO pins on the edges without routing them. """ + debug.info(1, "Adding IO pins on the perimeter...") + + # Prepare GDS reader (if necessary for pin/blockage identification) + self.prepare_gds_reader() + + # Find pins to be added (without routing) + for name in pin_names: + self.find_pins(name)# this will add the pins to the self.pins + + # Replace layout pins with those on the perimeter + for name in self.pins: + pin = next(iter(self.pins[name])) + fake_pin = self.create_fake_pin(pin) + self.design.replace_layout_pin(name, fake_pin) # if do not have replace_layout_pin, final gds won't have io pins + + + def remove_io_pins(self, pin_name): + # remove io pin in gds, so we could reroute + self.design.remove_layout_pin(pin_name) + + + + + diff --git a/compiler/sram_openroad_test.py b/compiler/sram_openroad_test.py new file mode 100644 index 000000000..98ee1583a --- /dev/null +++ b/compiler/sram_openroad_test.py @@ -0,0 +1,247 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2024 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 os +import shutil +import datetime +from openram import debug +from openram import sram_config as config +from openram import OPTS, print_time + + +class sram(): + """ + This is not a design module, but contains an SRAM design instance. + It could later try options of number of banks and organization to compare + results. + We can later add visualizer and other high-level functions as needed. + """ + def __init__(self, sram_config=None, name=None): + + # Create default configs if custom config isn't provided + if sram_config is None: + sram_config = config(word_size=OPTS.word_size, + num_words=OPTS.num_words, + write_size=OPTS.write_size, + num_banks=OPTS.num_banks, + words_per_row=OPTS.words_per_row, + num_spare_rows=OPTS.num_spare_rows, + num_spare_cols=OPTS.num_spare_cols) + + if name is None: + name = OPTS.output_name + + sram_config.set_local_config(self) + + # reset the static duplicate name checker for unit tests + # in case we create more than one SRAM + from openram.base import design + design.name_map=[] + + debug.info(2, "create sram of size {0} with {1} num of words {2} banks".format(self.word_size, + self.num_words, + self.num_banks)) + start_time = datetime.datetime.now() + + self.name = name + + from openram.modules.sram_openroad import sram_1bank as sram + #from openram.modules.sram_new import sram_1bank as sram + self.s = sram(name, sram_config) + + self.s.create_netlist() + if not OPTS.netlist_only: + self.s.create_layout_openroad() + + if not OPTS.is_unit_test: + print_time("SRAM creation", datetime.datetime.now(), start_time) + + def get_sp_name(self): + if OPTS.use_pex: + # Use the extracted spice file + return self.pex_name + else: + # Use generated spice file for characterization + return self.sp_name + + def sp_write(self, name, lvs=False, trim=False): + self.s.sp_write(name, lvs, trim) + + def lef_write(self, name): + self.s.lef_write(name) + + def gds_write(self, name): + self.s.gds_write(name) + + def verilog_write(self, name): + self.s.verilog_write(name) + if self.num_banks != 1: + from openram.modules.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. + Include defaults and anything changed by input config.""" + f = open(name, "w") + var_dict = dict((name, getattr(OPTS, name)) for name in dir(OPTS) if not name.startswith('__') and not callable(getattr(OPTS, name))) + for var_name, var_value in var_dict.items(): + if isinstance(var_value, str): + f.write(str(var_name) + " = " + "\"" + str(var_value) + "\"\n") + else: + f.write(str(var_name) + " = " + str(var_value)+ "\n") + f.close() + + def save_only(self): + if not OPTS.netlist_only: + # Write the layout + start_time = datetime.datetime.now() + gdsname = OPTS.output_path + self.s.name + ".gds" + debug.print_raw("GDS: Writing to {0}".format(gdsname)) + self.gds_write(gdsname) + if OPTS.check_lvsdrc: + verify.write_drc_script(cell_name=self.s.name, + gds_name=os.path.basename(gdsname), + extract=True, + final_verification=True, + output_path=OPTS.output_path) + print_time("GDS", datetime.datetime.now(), start_time) + + # Create a LEF physical model + start_time = datetime.datetime.now() + lefname = OPTS.output_path + self.s.name + ".lef" + debug.print_raw("LEF: Writing to {0}".format(lefname)) + self.lef_write(lefname) + print_time("LEF", datetime.datetime.now(), start_time) + + def save(self): + """ Save all the output files while reporting time to do it as well. """ + + # Import this at the last minute so that the proper tech file + # is loaded and the right tools are selected + from openram import verify + from openram.characterizer import functional + from openram.characterizer import delay + + # Save the spice file + start_time = datetime.datetime.now() + spname = OPTS.output_path + self.s.name + ".sp" + debug.print_raw("SP: Writing to {0}".format(spname)) + self.sp_write(spname) + + # Save a functional simulation file with default period + functional(self.s, + spname, + cycles=200, + output_path=OPTS.output_path) + print_time("Spice writing", datetime.datetime.now(), start_time) + + # Save stimulus and measurement file + start_time = datetime.datetime.now() + debug.print_raw("DELAY: Writing stimulus...") + d = delay(self.s, spname, ("TT", 5, 25), output_path=OPTS.output_path) + if (self.s.num_spare_rows == 0): + probe_address = "1" * self.s.addr_size + else: + probe_address = "0" + "1" * (self.s.addr_size - 1) + probe_data = self.s.word_size - 1 + d.analysis_init(probe_address, probe_data) + d.targ_read_ports.extend(self.s.read_ports) + d.targ_write_ports = [self.s.write_ports[0]] + d.write_delay_stimulus() + print_time("DELAY", datetime.datetime.now(), start_time) + + # Save trimmed spice file + temp_trim_sp = "{0}trimmed.sp".format(OPTS.output_path) + self.sp_write(temp_trim_sp, lvs=False, trim=True) + + if not OPTS.netlist_only: + # Write the layout + start_time = datetime.datetime.now() + gdsname = OPTS.output_path + self.s.name + ".gds" + debug.print_raw("GDS: Writing to {0}".format(gdsname)) + self.gds_write(gdsname) + if OPTS.check_lvsdrc: + verify.write_drc_script(cell_name=self.s.name, + gds_name=os.path.basename(gdsname), + extract=True, + final_verification=True, + output_path=OPTS.output_path) + print_time("GDS", datetime.datetime.now(), start_time) + + # Create a LEF physical model + start_time = datetime.datetime.now() + lefname = OPTS.output_path + self.s.name + ".lef" + debug.print_raw("LEF: Writing to {0}".format(lefname)) + self.lef_write(lefname) + print_time("LEF", datetime.datetime.now(), start_time) + + # Save the LVS file + start_time = datetime.datetime.now() + lvsname = OPTS.output_path + self.s.name + ".lvs.sp" + debug.print_raw("LVS: Writing to {0}".format(lvsname)) + self.sp_write(lvsname, lvs=True) + if not OPTS.netlist_only and OPTS.check_lvsdrc: + verify.write_lvs_script(cell_name=self.s.name, + gds_name=os.path.basename(gdsname), + sp_name=os.path.basename(lvsname), + final_verification=True, + output_path=OPTS.output_path) + print_time("LVS writing", datetime.datetime.now(), start_time) + + # Save the extracted spice file + if OPTS.use_pex: + start_time = datetime.datetime.now() + # Output the extracted design if requested + pexname = OPTS.output_path + self.s.name + ".pex.sp" + spname = OPTS.output_path + self.s.name + ".sp" + verify.run_pex(self.s.name, gdsname, spname, output=pexname) + sp_file = pexname + print_time("Extraction", datetime.datetime.now(), start_time) + else: + # Use generated spice file for characterization + sp_file = spname + + # Characterize the design + start_time = datetime.datetime.now() + from openram.characterizer import lib + debug.print_raw("LIB: Characterizing... ") + lib(out_dir=OPTS.output_path, sram=self.s, sp_file=sp_file) + print_time("Characterization", datetime.datetime.now(), start_time) + + # Write the config file + start_time = datetime.datetime.now() + try: + from shutil import copyfile + copyfile(OPTS.config_file, OPTS.output_path + OPTS.output_name + '.py') + except shutil.SameFileError: + pass + debug.print_raw("Config: Writing to {0}".format(OPTS.output_path + OPTS.output_name + '.py')) + print_time("Config", datetime.datetime.now(), start_time) + + # Write the datasheet + start_time = datetime.datetime.now() + from openram.datasheet import datasheet_gen + dname = OPTS.output_path + self.s.name + ".html" + debug.print_raw("Datasheet: Writing to {0}".format(dname)) + datasheet_gen.datasheet_write(dname) + print_time("Datasheet", datetime.datetime.now(), start_time) + + # Write a verilog model + start_time = datetime.datetime.now() + 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) + + # Write out options if specified + if OPTS.output_extended_config: + start_time = datetime.datetime.now() + oname = OPTS.output_path + OPTS.output_name + "_extended.py" + debug.print_raw("Extended Config: Writing to {0}".format(oname)) + self.extended_config_write(oname) + print_time("Extended Config", datetime.datetime.now(), start_time) From ee6be23cfaa1b9f6dc895e62f219f412a82d6a76 Mon Sep 17 00:00:00 2001 From: FriedrichWu Date: Tue, 17 Sep 2024 11:54:30 +0200 Subject: [PATCH 12/26] first comment --- compiler/modules/sram_for_road.py | 2378 +++++++++++++++++++++++++++++ compiler/modules/sram_openroad.py | 31 +- compiler/router/io_pin_placer.py | 2 +- compiler/sram.py | 4 +- compiler/sram_road.py | 3 +- sram_compiler.py | 3 + 6 files changed, 2388 insertions(+), 33 deletions(-) create mode 100644 compiler/modules/sram_for_road.py diff --git a/compiler/modules/sram_for_road.py b/compiler/modules/sram_for_road.py new file mode 100644 index 000000000..62d088478 --- /dev/null +++ b/compiler/modules/sram_for_road.py @@ -0,0 +1,2378 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2024 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. +# + +# This file aims to use openroad to do the routing +# No more rings for each moudle +# Provide methods for generating module verilog files & top verilog file +import datetime +from math import ceil +from importlib import import_module, reload +from openram import debug +from openram.base import vector +from openram.base import channel_route +from openram.base import design +from openram.base import verilog +from openram.base import lef +from openram.sram_factory import factory +from openram.tech import spice +from openram import OPTS, print_time + + +class sram_for_road(design, verilog, lef): + """ + Procedures specific to a one bank SRAM. + """ + def __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 openram.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 + + # delay control logic does not have RBLs + self.has_rbl = OPTS.control_logic != "control_logic_delay" + + def add_pin_bank(self): + # lef file use this pin name, should be same with new pin + """ Adding pins for Bank module""" + 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") + for port in self.all_ports: + if self.has_rbl: + self.add_pin("rbl_bl{0}".format(port), "OUTPUT") + for port in self.write_ports: + for bit in range(self.word_size + self.num_spare_cols): + self.add_pin("bank_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") + + # For more than one bank, we have a bank select and name + # the signals gated_*. + for port in self.read_ports: + self.add_pin("s_en{0}".format(port), "INPUT") + for port in self.all_ports: + self.add_pin("p_en_bar{0}".format(port), "INPUT") + for port in self.write_ports: + self.add_pin("w_en{0}".format(port), "INPUT") + for bit in range(self.num_wmasks): + self.add_pin("bank_wmask{0}[{1}]".format(port, bit), "INPUT") + for bit in range(self.num_spare_cols): + if self.num_spare_cols == 1: + self.add_pin("bank_spare_wen{0}".format(port), "INPUT") + else: + self.add_pin("bank_spare_wen{0}[{1}]".format(port, bit), "INPUT") + for port in self.all_ports: + self.add_pin("wl_en{0}".format(port), "INPUT") + + # 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_pin_row_addr_dff(self): + # lef file use this pin name, should be same with new pin + for port in self.all_ports: + for bit in range(self.row_addr_size): + #input + self.add_pin("addr{}[{}]".format(port, bit + self.col_addr_size), "INPUT") + print("addr{}[{}]".format(port, bit), "INPUT") + #output + self.add_pin("a{}[{}]".format(port, bit + self.col_addr_size), "OUTPUT") + #clk_buf, regard as input + self.add_pin("clk_buf{}".format(port), "INPUT") + # 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_pin_col_addr_dff(self): + # lef file use this pin name, should be same with new pin + for port in self.all_ports: + for bit in range(self.col_addr_size): + #input + self.add_pin("addr{}[{}]".format(port, bit), "INPUT") + #output + self.add_pin("a{}[{}]".format(port, bit), "OUTPUT") + #clk_buf, regard as input + self.add_pin("clk_buf{}".format(port), "INPUT") + # 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_pin_data_dff(self): + # lef file use this pin name, should be same with new pin + for port in self.all_ports: + for bit in range(self.word_size + self.num_spare_cols): + # input + self.add_pin("din{}[{}]".format(port, bit), "INPUT") + # output + self.add_pin("bank_din{}[{}]".format(port, bit), "OUTPUT") + #clk_buf, regard as input + self.add_pin("clk_buf{}".format(port), "INPUT") + # 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_pin_wmask_dff(self): + # lef file use this pin name, should be same with new pin + for port in self.all_ports: + for bit in range(self.num_wmasks): + # input + self.add_pin("wmask{}[{}]".format(port, bit), "INPUT") + # output + self.add_pin("bank_wmask{}[{}]".format(port, bit), "OUTPUT") + #clk_buf, regard as input + self.add_pin("clk_buf{}".format(port), "INPUT") + # 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_pin_spare_wen_dff(self): + # lef file use this pin name, should be same with new pin + for port in self.all_ports: + for bit in range(self.num_spare_cols): + if self.num_spare_cols == 1: + # input + self.add_pin("spare_wen{}".format(port), "INPUT") + # output + self.add_pin("bank_spare_wen{}".format(port), "OUTPUT") + else: + # input + self.add_pin("spare_wen{}[{}]".format(port, bit), "INPUT") + # output + self.add_pin("bank_spare_wen{}[{}]".format(port, bit), "OUTPUT") + #clk_buf, regard as input + self.add_pin("clk_buf{}".format(port), "INPUT") + # 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_pin_control(self): + # lef file use this pin name, should be same with new pin + for port in self.all_ports: + # Inputs + self.add_pin("csb{}".format(port), "INPUT") + if port in self.readwrite_ports: + self.add_pin("web{}".format(port), "INPUT") + self.add_pin("clk{}".format(port), "INPUT") + if self.has_rbl: + self.add_pin("rbl_bl{}".format(port), "INPUT") + # Outputs + if port in self.read_ports: + self.add_pin("s_en{}".format(port), "OUTPUT") + if port in self.write_ports: + self.add_pin("w_en{}".format(port), "OUTPUT") + self.add_pin("p_en_bar{}".format(port), "OUTPUT") + self.add_pin("wl_en{}".format(port), "OUTPUT") + self.add_pin("clk_buf{}".format(port), "OUTPUT") + + # Standard supply and ground names + # not sure if this part should stay inside the for loop + 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_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_netlist_bank(self): + """ Netlist creation """ + + start_time = datetime.datetime.now() + + # Must create the control logic before pins to get the pins + self.add_modules() + self.add_pin_bank() + self.bank_inst = self.create_bank(0) + + # 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 Bank", datetime.datetime.now(), start_time) + + def create_netlist_control(self): + """ Netlist creation """ + + start_time = datetime.datetime.now() + + # Must create the control logic before pins to get the pins + self.add_modules() + self.add_pin_control() + self.control_logic_insts = self.create_control_logic() # be aware, multi-insts possible + + # 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 Control", datetime.datetime.now(), start_time) + + def create_netlist_row_addr_dff(self): + """ Netlist creation """ + + start_time = datetime.datetime.now() + + # Must create the control logic before pins to get the pins + self.add_modules() + self.add_pin_row_addr_dff() + self.row_addr_dff_insts = self.create_row_addr_dff() # be aware, multi-insts possible + + # 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 row_addr_dff", datetime.datetime.now(), start_time) + + def create_netlist_col_addr_dff(self): + """ Netlist creation """ + + start_time = datetime.datetime.now() + + # Must create the control logic before pins to get the pins + self.add_modules() + if self.col_addr_dff: + self.add_pin_col_addr_dff() + self.col_addr_dff_insts = self.create_col_addr_dff() # be aware, multi-insts possible, or none + + # 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 col_addr_dff", datetime.datetime.now(), start_time) + else: + return False # no col_addr_dff will be generated + + def create_netlist_data_dff(self): + """ Netlist creation """ + + start_time = datetime.datetime.now() + + # Must create the control logic before pins to get the pins + self.add_modules() + self.add_pin_data_dff() + self.data_dff_insts = self.create_data_dff() # be aware, multi-insts possible + + # 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 data dff", datetime.datetime.now(), start_time) + + def create_netlist_wmask_dff(self): + """ Netlist creation """ + + start_time = datetime.datetime.now() + + # Must create the control logic before pins to get the pins + self.add_modules() + if self.write_size != self.word_size: + self.add_pin_wmask_dff() + self.wmask_dff_insts = self.create_wmask_dff() # be aware, multi-insts possible, or none + + # 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 wmask dff", datetime.datetime.now(), start_time) + else: + return False # no wmask_dff will be generated + + def create_netlist_spare_wen_dff(self): + """ Netlist creation """ + + start_time = datetime.datetime.now() + + # Must create the control logic before pins to get the pins + self.add_modules() + if self.num_spare_cols: + self.add_pin_spare_wen_dff() + self.spare_wen_dff_insts = self.create_spare_wen_dff() # be aware, multi-insts possible, or none + + # 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 spare wen dff", datetime.datetime.now(), start_time) + else: + self.num_spare_cols = 0 + return False # no spare_col will be generated + + 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_layout_bank_only(self): + start_time = datetime.datetime.now() + #Placement + self.place_bank(self.bank_inst, [0, 0], 1, 1) + if not OPTS.is_unit_test: + print_time("Bank Placement", datetime.datetime.now(), start_time) + start_time = datetime.datetime.now() + self.route_bank_only() + + 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_layout_control_only(self, instance_index=0): + start_time = datetime.datetime.now() + # Placement + # We will generate different layout for different port(if there are multi-port) + port = instance_index + self.control_logic_insts[port].place(vector(0,0)) + + if not OPTS.is_unit_test: + print_time("Control Placement", datetime.datetime.now(), start_time) + start_time = datetime.datetime.now() + self.route_control_only(instance_index=instance_index) + + 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_layout_row_addr_dff_only(self, instance_index=0): + start_time = datetime.datetime.now() + # Placement + # We will generate different layout for different port(if there are multi-port) + port = instance_index + self.row_addr_dff_insts[port].place(vector(0,0)) + + if not OPTS.is_unit_test: + print_time("row_addr_dff Placement", datetime.datetime.now(), start_time) + start_time = datetime.datetime.now() + self.route_row_addr_dff_only(instance_index=instance_index) + + 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_layout_col_addr_dff_only(self, instance_index=0): + start_time = datetime.datetime.now() + # Placement + # We will generate different layout for different port(if there are multi-port) + port = instance_index + self.col_addr_dff_insts[port].place(vector(0,0)) + + if not OPTS.is_unit_test: + print_time("col_addr_dff Placement", datetime.datetime.now(), start_time) + start_time = datetime.datetime.now() + self.route_col_addr_dff_only(instance_index=instance_index) + + 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_layout_data_dff_only(self, instance_index=0): + start_time = datetime.datetime.now() + # Placement + # We will generate different layout for different port(if there are multi-port) + port = instance_index + self.data_dff_insts[port].place(vector(0,0)) + + if not OPTS.is_unit_test: + print_time("data_dff Placement", datetime.datetime.now(), start_time) + start_time = datetime.datetime.now() + self.route_data_dff_only(instance_index=instance_index) + + 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_layout_wmask_dff_only(self, instance_index=0): + start_time = datetime.datetime.now() + # Placement + # We will generate different layout for different port(if there are multi-port) + port = instance_index + self.wmask_dff_insts[port].place(vector(0,0)) + + if not OPTS.is_unit_test: + print_time("wmask_dff Placement", datetime.datetime.now(), start_time) + start_time = datetime.datetime.now() + self.route_wmask_dff_only(instance_index=instance_index) + + 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_layout_spare_wen_dff_only(self, instance_index=0): + start_time = datetime.datetime.now() + # Placement + # We will generate different layout for different port(if there are multi-port) + port = instance_index + self.spare_wen_dff_insts[port].place(vector(0,0)) + + if not OPTS.is_unit_test: + print_time("spare_wen_dff Placement", datetime.datetime.now(), start_time) + start_time = datetime.datetime.now() + self.route_spare_wen_dff_only(instance_index=instance_index) + + 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 route_bank_only(self): + # We add the vias to M3 before routing supplies because + # they might create some blockages + self.add_layout_bank_only_pins() + print("====================================================") + for pin in self.pins: + print(pin) + + # Some technologies have an isolation + self.add_dnwell(inflate=2.5) + + def route_control_only(self, instance_index=0): + # We add the vias to M3 before routing supplies because + # they might create some blockages + self.add_layout_control_only_pins(instance_index=instance_index) + print("====================================================") + for pin in self.pins: + print(pin) + + # Some technologies have an isolation + self.add_dnwell(inflate=2.5) + + def route_row_addr_dff_only(self, instance_index=0): + # We add the vias to M3 before routing supplies because + # they might create some blockages + self.add_layout_row_addr_dff_only_pins(instance_index=instance_index) + print("====================================================") + for pin in self.pins: + print(pin) + + # Some technologies have an isolation + self.add_dnwell(inflate=2.5) + + def route_col_addr_dff_only(self, instance_index=0): + # We add the vias to M3 before routing supplies because + # they might create some blockages + self.add_layout_col_addr_dff_only_pins(instance_index=instance_index) + print("====================================================") + for pin in self.pins: + print(pin) + + # Some technologies have an isolation + self.add_dnwell(inflate=2.5) + + def route_data_dff_only(self, instance_index=0): + # We add the vias to M3 before routing supplies because + # they might create some blockages + self.add_layout_data_dff_only_pins(instance_index=instance_index) + print("====================================================") + for pin in self.pins: + print(pin) + + # Some technologies have an isolation + self.add_dnwell(inflate=2.5) + + def route_wmask_dff_only(self, instance_index=0): + # We add the vias to M3 before routing supplies because + # they might create some blockages + self.add_layout_wmask_dff_only_pins(instance_index=instance_index) + print("====================================================") + for pin in self.pins: + print(pin) + + # Some technologies have an isolation + self.add_dnwell(inflate=2.5) + + def route_spare_wen_dff_only(self, instance_index=0): + # We add the vias to M3 before routing supplies because + # they might create some blockages + self.add_layout_spare_wen_dff_only_pins(instance_index=instance_index) + print("====================================================") + for pin in self.pins: + print(pin) + + # Some technologies have an isolation + self.add_dnwell(inflate=2.5) + + def add_layout_spare_wen_dff_only_pins(self, add_vias=True, instance_index=0): + """ + Add the top-level pins for a single bank SRAM with control. + """ + port = instance_index + # Hack: If we are escape routing, set the pin layer to + # None so that we will start from the pin layer + # Otherwise, set it as the pin layer so that no vias are added. + # Otherwise, when we remove pins to move the dff array dynamically, + # we will leave some remaining vias when the pin locations change. + if add_vias: + pin_layer = None + else: + pin_layer = self.pwr_grid_layers[0] + + for bit in range(self.num_spare_cols): + if self.num_spare_cols == 1: + # input + self.add_io_pin(self.spare_wen_dff_insts[port], + "din_{}".format(bit), # old name + "spare_wen{}".format(port), # new name + start_layer=pin_layer) + # output + self.add_io_pin(self.spare_wen_dff_insts[port], + "dout_{}".format(bit), + "bank_spare_wen{}".format(port), + start_layer=pin_layer) + else: + # input + self.add_io_pin(self.spare_wen_dff_insts[port], + "din_{}".format(bit), # old name + "spare_wen{}[{}]".format(port, bit), # new name + start_layer=pin_layer) + # output + self.add_io_pin(self.spare_wen_dff_insts[port], + "dout_{}".format(bit), + "bank_spare_wen{}[{}]".format(port, bit), + start_layer=pin_layer) + #clk_buf, regard as input + self.add_io_pin(self.spare_wen_dff_insts[port], + "clk", + "clk_buf{}".format(port), + start_layer=pin_layer) + + def add_layout_wmask_dff_only_pins(self, add_vias=True, instance_index=0): + """ + Add the top-level pins for a single bank SRAM with control. + """ + port = instance_index + # Hack: If we are escape routing, set the pin layer to + # None so that we will start from the pin layer + # Otherwise, set it as the pin layer so that no vias are added. + # Otherwise, when we remove pins to move the dff array dynamically, + # we will leave some remaining vias when the pin locations change. + if add_vias: + pin_layer = None + else: + pin_layer = self.pwr_grid_layers[0] + + for bit in range(self.num_wmasks): + # input + self.add_io_pin(self.wmask_dff_insts[port], + "din_{}".format(bit), + "wmask{}[{}]".format(port, bit), + start_layer=pin_layer) + # output + self.add_io_pin(self.wmask_dff_insts[port], + "dout_{}".format(bit), + "bank_wmask{}[{}]".format(port, bit), + start_layer=pin_layer) + #clk_buf, regard as input + self.add_io_pin(self.wmask_dff_insts[port], + "clk", + "clk_buf{}".format(port), + start_layer=pin_layer) + + def add_layout_data_dff_only_pins(self, add_vias=True, instance_index=0): + """ + Add the top-level pins for a single bank SRAM with control. + """ + port = instance_index + # Hack: If we are escape routing, set the pin layer to + # None so that we will start from the pin layer + # Otherwise, set it as the pin layer so that no vias are added. + # Otherwise, when we remove pins to move the dff array dynamically, + # we will leave some remaining vias when the pin locations change. + if add_vias: + pin_layer = None + else: + pin_layer = self.pwr_grid_layers[0] + + for bit in range(self.word_size + self.num_spare_cols): + # input + self.add_io_pin(self.data_dff_insts[port], + "din_{}".format(bit), + "din{}[{}]".format(port, bit), + start_layer=pin_layer) + # output + self.add_io_pin(self.data_dff_insts[port], + "dout_{}".format(bit), + "bank_din{}[{}]".format(port, bit), + start_layer=pin_layer) + #clk_buf, regard as input + self.add_io_pin(self.data_dff_insts[port], + "clk", + "clk_buf{}".format(port), + start_layer=pin_layer) + + def add_layout_col_addr_dff_only_pins(self, add_vias=True, instance_index=0): + """ + Add the top-level pins for a single bank SRAM with control. + """ + port = instance_index + # Hack: If we are escape routing, set the pin layer to + # None so that we will start from the pin layer + # Otherwise, set it as the pin layer so that no vias are added. + # Otherwise, when we remove pins to move the dff array dynamically, + # we will leave some remaining vias when the pin locations change. + if add_vias: + pin_layer = None + else: + pin_layer = self.pwr_grid_layers[0] + + for bit in range(self.col_addr_size): + #input + self.add_io_pin(self.col_addr_dff_insts[port], + "din_{}".format(bit), # old name + "addr{}[{}]".format(port, bit), # new name + start_layer=pin_layer) + #output + self.add_io_pin(self.col_addr_dff_insts[port], + "dout_{}".format(bit), + "a{}[{}]".format(port, bit), + start_layer=pin_layer) + #clk_buf, regard as input + self.add_pin(self.col_addr_dff_insts[port], + "clk", + "clk_buf{}".format(port), + start_layer=pin_layer) + + def add_layout_row_addr_dff_only_pins(self, add_vias=True, instance_index=0): + """ + Add the top-level pins for a single bank SRAM with control. + """ + port = instance_index + # Hack: If we are escape routing, set the pin layer to + # None so that we will start from the pin layer + # Otherwise, set it as the pin layer so that no vias are added. + # Otherwise, when we remove pins to move the dff array dynamically, + # we will leave some remaining vias when the pin locations change. + if add_vias: + pin_layer = None + else: + pin_layer = self.pwr_grid_layers[0] + + for bit in range(self.row_addr_size): + #input + #debug + print("this is port number:{0}".format(port)) + print("insts:{0}".format(self.row_addr_dff_insts[port])) + self.add_io_pin(self.row_addr_dff_insts[port], + "din_{}".format(bit + self.col_addr_size), # old name + "addr{}[{}]".format(port, bit + self.col_addr_size), # new name + start_layer=pin_layer) + #output + self.add_io_pin(self.row_addr_dff_insts[port], + "dout_{}".format(bit + self.col_addr_size), + "a{}[{}]".format(port, bit + self.col_addr_size), + start_layer=pin_layer) + #clk_buf, regard as input + self.add_io_pin(self.row_addr_dff_insts[port], + "clk", + "clk_buf{}".format(port), + start_layer=pin_layer) + + def add_layout_control_only_pins(self, add_vias=True, instance_index=0): + """ + Add the top-level pins for a single bank SRAM with control. + """ + port = instance_index + # Hack: If we are escape routing, set the pin layer to + # None so that we will start from the pin layer + # Otherwise, set it as the pin layer so that no vias are added. + # Otherwise, when we remove pins to move the dff array dynamically, + # we will leave some remaining vias when the pin locations change. + if add_vias: + pin_layer = None + else: + pin_layer = self.pwr_grid_layers[0] + + # Inputs + self.add_io_pin(self.control_logic_insts[port], + "csb", #old name + "csb{}".format(port), #new name + start_layer=pin_layer) + if port in self.readwrite_ports: + self.add_io_pin(self.control_logic_insts[port], + "web", + "web{}".format(port), + start_layer=pin_layer) + self.add_io_pin(self.control_logic_insts[port], + "clk", + "clk{}".format(port), + start_layer=pin_layer) + if self.has_rbl: + self.add_io_pin(self.control_logic_insts[port], + "rbl_bl", + "rbl_bl{}".format(port), + start_layer=pin_layer) + # Outputs + if port in self.read_ports: + self.add_io_pin(self.control_logic_insts[port], + "s_en", + "s_en{}".format(port), + start_layer=pin_layer) + if port in self.write_ports: + self.add_io_pin(self.control_logic_insts[port], + "w_en", + "w_en{}".format(port), + start_layer=pin_layer) + self.add_io_pin(self.control_logic_insts[port], + "p_en_bar", + "p_en_bar{}".format(port), + start_layer=pin_layer) + self.add_io_pin(self.control_logic_insts[port], + "wl_en", + "wl_en{}".format(port), + start_layer=pin_layer) + self.add_io_pin(self.control_logic_insts[port], + "clk_buf", + "clk_buf{}".format(port), + start_layer=pin_layer) + + def add_layout_bank_only_pins(self, add_vias=True): + """ + Add the top-level pins for a single bank SRAM with control. + """ + # Hack: If we are escape routing, set the pin layer to + # None so that we will start from the pin layer + # Otherwise, set it as the pin layer so that no vias are added. + # Otherwise, when we remove pins to move the dff array dynamically, + # we will leave some remaining vias when the pin locations change. + if add_vias: + pin_layer = None + else: + pin_layer = self.pwr_grid_layers[0] + + for port in self.readwrite_ports or port in self.read_ports: + for bit in range(self.word_size + self.num_spare_cols): + self.add_io_pin(self.bank_inst, + "dout{0}_{1}".format(port, bit),#old name + "dout{0}[{1}]".format(port, bit),#new name + start_layer=pin_layer) + """ Adding pins for Bank module""" + for port in self.all_ports: + if self.has_rbl: + self.add_io_pin(self.bank_inst, + "rbl_bl_{0}_{0}".format(port), + "rbl_bl{0}".format(port), + start_layer=pin_layer) + for port in self.write_ports: + for bit in range(self.word_size + self.num_spare_cols): + self.add_io_pin(self.bank_inst, + "din{0}_{1}".format(port, bit), + "bank_din{0}[{1}]".format(port, bit), + start_layer=pin_layer) + # manuel change position, so not at same y + for port in self.all_ports: + for bit in range(self.bank_addr_size): + self.change_layout_pin_position(self.bank_inst, + "addr{0}_{1}".format(port, bit), + "addr{0}[{1}]".format(port, bit), + start_layer=pin_layer, + distance=bit) + for port in self.read_ports: + self.change_layout_pin_position(self.bank_inst, + "s_en{0}".format(port), + "s_en{0}".format(port), + start_layer=pin_layer, + distance=4) + for port in self.all_ports: + self.change_layout_pin_position(self.bank_inst, + "p_en_bar{0}".format(port), + "p_en_bar{0}".format(port), + start_layer=pin_layer, + distance=2) + for port in self.write_ports: + self.change_layout_pin_position(self.bank_inst, + "w_en{0}".format(port), + "w_en{0}".format(port), + start_layer=pin_layer) + + for bit in range(self.num_wmasks): + self.add_io_pin(self.bank_inst, + "bank_wmask{0}_{1}".format(port, bit), + "bank_wmask{0}[{1}]".format(port, bit), + start_layer=pin_layer) + for bit in range(self.num_spare_cols): + if self.num_spare_cols == 1: + self.add_io_pin(self.bank_inst, + "bank_spare_wen{0}_{1}".format(port, bit), + "bank_spare_wen{0}".format(port), + start_layer=pin_layer) + else: + self.add_io_pin(self.bank_inst, + "bank_spare_wen{0}_{1}".format(port, bit), + "bank_spare_wen{0}[{1}]".format(port, bit), + start_layer=pin_layer) + if port in self.all_ports: + self.add_io_pin(self.bank_inst, + "wl_en{0}".format(port), + "wl_en{0}".format(port), + start_layer=pin_layer) + + def change_layout_pin_position(self, instance, pin_name, new_name, start_layer=None, directions=None, distance=1): + """ + Add a signle input or output pin up to metal 3. + This additonal operation make sure pins are not at the same y + """ + pin = instance.get_pin(pin_name) + + if not start_layer: + start_layer = pin.layer + # Just use the power pin function for now to save code + self.add_power_pin(new_name, vector(pin.center()[0], pin.center()[1] + self.m3_pitch * distance * 2), start_layer=start_layer, directions=directions) + + 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]) + + from openram.router import supply_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.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=None): + """ + 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 openram.router import signal_escape_router as router + rtr = router(layers=self.m3_stack, + bbox=bbox, + design=self) + rtr.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_module("." + OPTS.control_logic, "openram.modules")) + 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)) + if self.has_rbl: + 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)) + if self.has_rbl: + 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): + """ + This adds the modules for a single bank SRAM with control + logic. + """ + + self.bank_inst = self.create_bank(0) + + self.control_logic_insts = self.create_control_logic() + + self.row_addr_dff_insts = self.create_row_addr_dff() + + if self.col_addr_dff: + self.col_addr_dff_insts = self.create_col_addr_dff() + + if self.write_size != self.word_size: + self.wmask_dff_insts = self.create_wmask_dff() + self.data_dff_insts = self.create_data_dff() + else: + self.data_dff_insts = self.create_data_dff() + + if self.num_spare_cols: + self.spare_wen_dff_insts = self.create_spare_wen_dff() + else: + self.num_spare_cols = 0 + + def place_instances(self): + """ + This places the instances for a single bank SRAM with control + logic and up to 2 ports. + """ + + # No orientation or offset + self.place_bank(self.bank_inst, [0, 0], 1, 1) + + # The control logic is placed such that the vertical center (between the delay/RBL and + # the actual control logic is aligned with the vertical center of the bank (between + # the sense amps/column mux and cell array) + # The x-coordinate is placed to allow a single clock wire (plus an extra pitch) + # up to the row address DFFs. + self.control_pos = [None] * len(self.all_ports) + self.row_addr_pos = [None] * len(self.all_ports) + + # DFFs are placd on their own + self.col_addr_pos = [None] * len(self.all_ports) + self.wmask_pos = [None] * len(self.all_ports) + self.spare_wen_pos = [None] * len(self.all_ports) + self.data_pos = [None] * len(self.all_ports) + + # These positions utilize the channel route sizes. + # FIXME: Auto-compute these rather than manual computation. + # If a horizontal channel, they rely on the vertical channel non-preferred (contacted) pitch. + # If a vertical channel, they rely on the horizontal channel non-preferred (contacted) pitch. + # So, m3 non-pref pitch means that this is routed on the m2 layer. + self.data_bus_gap = self.m4_nonpref_pitch * 2 + + # Spare wen are on a separate layer so not included + # Start with 1 track minimum + self.data_bus_size = [1] * len(self.all_ports) + self.col_addr_bus_size = [1] * len(self.all_ports) + for port in self.all_ports: + # The column address wires are routed separately from the data bus and will always be smaller. + # All ports need the col addr flops + self.col_addr_bus_size[port] = self.col_addr_size * self.m4_nonpref_pitch + # Write ports need the data input flops and write mask flops + if port in self.write_ports: + self.data_bus_size[port] += self.num_wmasks + self.word_size + # This is for the din pins that get routed in the same channel + # when we have dout and din together + if port in self.readwrite_ports: + self.data_bus_size[port] += self.word_size + # Convert to length + self.data_bus_size[port] *= self.m4_nonpref_pitch + # Add the gap in unit length + self.data_bus_size[port] += self.data_bus_gap + + # The control and row addr flops are independent of any bus widths. + self.place_control() + self.place_row_addr_dffs() + + # Place with an initial wide channel (from above) + self.place_dffs() + + # Route the channel and set to the new data bus size + # We need to temporarily add some pins for the x offsets + # but we'll remove them so that they have the right y + # offsets after the DFF placement. + self.add_layout_pins(add_vias=False) + self.route_dffs(add_routes=False) + self.remove_layout_pins() + + # Re-place with the new channel size + self.place_dffs() + + def place_row_addr_dffs(self): + """ + Must be run after place control logic. + """ + port = 0 + # The row address bits are placed above the control logic aligned on the right. + x_offset = self.control_logic_insts[port].rx() - self.row_addr_dff_insts[port].width + # It is above the control logic and the predecoder array + y_offset = max(self.control_logic_insts[port].uy(), self.bank.predecoder_top) + + self.row_addr_pos[port] = vector(x_offset, y_offset) + self.row_addr_dff_insts[port].place(self.row_addr_pos[port]) + + if len(self.all_ports)>1: + port = 1 + # The row address bits are placed above the control logic aligned on the left. + x_offset = self.control_pos[port].x - self.control_logic_insts[port].width + self.row_addr_dff_insts[port].width + # If it can be placed above the predecoder and below the control logic, do it + y_offset = self.bank.predecoder_bottom + self.row_addr_pos[port] = vector(x_offset, y_offset) + self.row_addr_dff_insts[port].place(self.row_addr_pos[port], mirror="XY") + + def place_control(self): + port = 0 + + # This includes 2 M2 pitches for the row addr clock line. + # The delay line is aligned with the bitcell array while the control logic is aligned with the port_data + # using the control_logic_center value. + self.control_pos[port] = vector(-self.control_logic_insts[port].width - 2 * self.m2_pitch, + self.bank.bank_array_ll.y - self.control_logic_insts[port].mod.control_logic_center.y) + self.control_logic_insts[port].place(self.control_pos[port]) + if len(self.all_ports) > 1: + port = 1 + # This includes 2 M2 pitches for the row addr clock line + # The delay line is aligned with the bitcell array while the control logic is aligned with the port_data + # using the control_logic_center value. + self.control_pos[port] = vector(self.bank_inst.rx() + self.control_logic_insts[port].width + 2 * self.m2_pitch, + self.bank.bank_array_ur.y + + self.control_logic_insts[port].height + - self.control_logic_insts[port].height + + self.control_logic_insts[port].mod.control_logic_center.y) + self.control_logic_insts[port].place(self.control_pos[port], mirror="XY") + + def place_dffs(self): + """ + Place the col addr, data, wmask, and spare data DFFs. + This can be run more than once after we recompute the channel width. + """ + + port = 0 + # Add the col address flops below the bank to the right of the control logic + x_offset = self.control_logic_insts[port].rx() + self.dff.width + # Place it a data bus below the x-axis, but at least as low as the control logic to not block + # the control logic signals + y_offset = min(-self.data_bus_size[port] - self.dff.height, + self.control_logic_insts[port].by()) + if self.col_addr_dff: + self.col_addr_pos[port] = vector(x_offset, + y_offset) + self.col_addr_dff_insts[port].place(self.col_addr_pos[port]) + x_offset = self.col_addr_dff_insts[port].rx() + else: + self.col_addr_pos[port] = vector(x_offset, 0) + + if port in self.write_ports: + 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) + self.wmask_dff_insts[port].place(self.wmask_pos[port]) + x_offset = self.wmask_dff_insts[port].rx() + + # Add the data flops below the write mask flops. + self.data_pos[port] = vector(x_offset, + y_offset) + self.data_dff_insts[port].place(self.data_pos[port]) + x_offset = self.data_dff_insts[port].rx() + + # Add spare write enable flops to the right of data flops since the spare columns + # will be on the right + if self.num_spare_cols: + self.spare_wen_pos[port] = vector(x_offset, + y_offset) + self.spare_wen_dff_insts[port].place(self.spare_wen_pos[port]) + x_offset = self.spare_wen_dff_insts[port].rx() + + else: + self.wmask_pos[port] = vector(x_offset, y_offset) + self.data_pos[port] = vector(x_offset, y_offset) + self.spare_wen_pos[port] = vector(x_offset, y_offset) + + if len(self.all_ports) > 1: + port = 1 + + # Add the col address flops below the bank to the right of the control logic + x_offset = self.control_logic_insts[port].lx() - 2 * self.dff.width + # Place it a data bus below the x-axis, but at least as high as the control logic to not block + # the control logic signals + y_offset = max(self.bank.height + self.data_bus_size[port] + self.dff.height, + self.control_logic_insts[port].uy() - self.dff.height) + if self.col_addr_dff: + self.col_addr_pos[port] = vector(x_offset, + y_offset) + self.col_addr_dff_insts[port].place(self.col_addr_pos[port], mirror="XY") + x_offset = self.col_addr_dff_insts[port].lx() + else: + self.col_addr_pos[port] = vector(x_offset, y_offset) + + if port in self.write_ports: + # Add spare write enable flops to the right of the data flops since the spare + # columns will be on the left + if self.num_spare_cols: + self.spare_wen_pos[port] = vector(x_offset - self.spare_wen_dff_insts[port].width, + y_offset) + 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 != 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) + self.wmask_dff_insts[port].place(self.wmask_pos[port], mirror="MX") + x_offset = self.wmask_dff_insts[port].lx() + + # Add the data flops below the write mask flops. + self.data_pos[port] = vector(x_offset - self.data_dff_insts[port].width, + y_offset) + self.data_dff_insts[port].place(self.data_pos[port], mirror="MX") + else: + self.wmask_pos[port] = vector(x_offset, y_offset) + self.data_pos[port] = vector(x_offset, y_offset) + self.spare_wen_pos[port] = vector(x_offset, y_offset) + + def add_layout_pins(self, add_vias=True): + """ + Add the top-level pins for a single bank SRAM with control. + """ + for port in self.all_ports: + # Hack: If we are escape routing, set the pin layer to + # None so that we will start from the pin layer + # Otherwise, set it as the pin layer so that no vias are added. + # Otherwise, when we remove pins to move the dff array dynamically, + # we will leave some remaining vias when the pin locations change. + if add_vias: + pin_layer = None + else: + pin_layer = self.pwr_grid_layers[0] + + # Connect the control pins as inputs + for signal in self.control_logic_inputs[port]: + if signal.startswith("rbl"): + continue + self.add_io_pin(self.control_logic_insts[port], + signal, + signal + "{}".format(port), + start_layer=pin_layer) + + if port in self.write_ports: + for bit in range(self.word_size + self.num_spare_cols): + self.add_io_pin(self.data_dff_insts[port], + "din_{}".format(bit), + "din{0}[{1}]".format(port, bit), + start_layer=pin_layer) + + if port in self.readwrite_ports or port in self.read_ports: + for bit in range(self.word_size + self.num_spare_cols): + self.add_io_pin(self.bank_inst, + "dout{0}_{1}".format(port, bit), + "dout{0}[{1}]".format(port, bit), + start_layer=pin_layer) + + for bit in range(self.col_addr_size): + self.add_io_pin(self.col_addr_dff_insts[port], + "din_{}".format(bit), + "addr{0}[{1}]".format(port, bit), + start_layer=pin_layer) + + for bit in range(self.row_addr_size): + self.add_io_pin(self.row_addr_dff_insts[port], + "din_{}".format(bit), + "addr{0}[{1}]".format(port, bit + self.col_addr_size), + start_layer=pin_layer) + + if port in self.write_ports: + 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), + "wmask{0}[{1}]".format(port, bit), + start_layer=pin_layer) + + if port in self.write_ports: + if self.num_spare_cols == 1: + self.add_io_pin(self.spare_wen_dff_insts[port], + "din_{}".format(0), + "spare_wen{0}".format(port), + start_layer=pin_layer) + else: + for bit in range(self.num_spare_cols): + self.add_io_pin(self.spare_wen_dff_insts[port], + "din_{}".format(bit), + "spare_wen{0}[{1}]".format(port, bit), + start_layer=pin_layer) + + def route_layout(self): + """ Route a single bank SRAM """ + + self.route_clk() + + self.route_control_logic() + + self.route_row_addr_dff() + + self.route_dffs() + + # We add the vias to M3 before routing supplies because + # they might create some blockages + self.add_layout_pins() + + # Some technologies have an isolation + self.add_dnwell(inflate=2.5) + + init_bbox = self.get_bbox() + # Route the supplies together and/or to the ring/stripes. + # Route the pins to the perimeter + # change the order + + if OPTS.perimeter_pins: + # We now route the escape routes far enough out so that they will + # reach past the power ring or stripes on the sides + self.route_escape_pins(init_bbox) + #if OPTS.route_supplies: + # self.route_supplies(init_bbox) + """ + if OPTS.route_supplies: + self.route_supplies(init_bbox) + if OPTS.perimeter_pins: + # We now route the escape routes far enough out so that they will + # reach past the power ring or stripes on the sides + self.route_escape_pins(init_bbox) + """ + + + def route_dffs(self, add_routes=True): + + for port in self.all_ports: + self.route_dff(port, add_routes) + + def route_dff(self, port, add_routes): + + # This is only done when we add_routes because the data channel will be larger + # so that can be used for area estimation. + if add_routes: + self.route_col_addr_dffs(port) + + self.route_data_dffs(port, add_routes) + + def route_col_addr_dffs(self, port): + + route_map = [] + + # column mux dff is routed on it's own since it is to the far end + # decoder inputs are min pitch M2, so need to use lower layer stack + if self.col_addr_size > 0: + dff_names = ["dout_{}".format(x) for x in range(self.col_addr_size)] + dff_pins = [self.col_addr_dff_insts[port].get_pin(x) for x in dff_names] + bank_names = ["addr{0}_{1}".format(port, x) for x in range(self.col_addr_size)] + bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] + route_map.extend(list(zip(bank_pins, dff_pins))) + + if len(route_map) > 0: + + # This layer stack must be different than the data dff layer stack + layer_stack = self.m1_stack + + if port == 0: + offset = vector(self.control_logic_insts[port].rx() + self.dff.width, + - self.data_bus_size[port] + 2 * self.m3_pitch) + cr = channel_route(netlist=route_map, + offset=offset, + layer_stack=layer_stack, + parent=self) + # This causes problem in magic since it sometimes cannot extract connectivity of instances + # with no active devices. + self.add_inst(cr.name, cr) + self.connect_inst([]) + # self.add_flat_inst(cr.name, cr) + else: + offset = vector(0, + self.bank.height + self.m3_pitch) + cr = channel_route(netlist=route_map, + offset=offset, + layer_stack=layer_stack, + parent=self) + # This causes problem in magic since it sometimes cannot extract connectivity of instances + # with no active devices. + self.add_inst(cr.name, cr) + self.connect_inst([]) + # self.add_flat_inst(cr.name, cr) + + def route_data_dffs(self, port, add_routes): + route_map = [] + + # wmask dff + if self.num_wmasks > 0 and port in self.write_ports: + dff_names = ["dout_{}".format(x) for x in range(self.num_wmasks)] + dff_pins = [self.wmask_dff_insts[port].get_pin(x) for x in dff_names] + bank_names = ["bank_wmask{0}_{1}".format(port, x) for x in range(self.num_wmasks)] + bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] + route_map.extend(list(zip(bank_pins, dff_pins))) + + if port in self.write_ports: + # synchronized inputs from data dff + dff_names = ["dout_{}".format(x) for x in range(self.word_size + self.num_spare_cols)] + dff_pins = [self.data_dff_insts[port].get_pin(x) for x in dff_names] + bank_names = ["din{0}_{1}".format(port, x) for x in range(self.word_size + self.num_spare_cols)] + bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] + route_map.extend(list(zip(bank_pins, dff_pins))) + + # spare wen dff + if self.num_spare_cols > 0 and port in self.write_ports: + dff_names = ["dout_{}".format(x) for x in range(self.num_spare_cols)] + dff_pins = [self.spare_wen_dff_insts[port].get_pin(x) for x in dff_names] + bank_names = ["bank_spare_wen{0}_{1}".format(port, x) for x in range(self.num_spare_cols)] + bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] + route_map.extend(list(zip(bank_pins, dff_pins))) + + if len(route_map) > 0: + + # This layer stack must be different than the column addr dff layer stack + layer_stack = self.m3_stack + if port == 0: + # This is relative to the bank at 0,0 or the s_en which is routed on M3 also + if "s_en" in self.control_logic_insts[port].mod.pin_map: + y_bottom = min(0, self.control_logic_insts[port].get_pin("s_en").by()) + else: + y_bottom = 0 + + y_offset = y_bottom - self.data_bus_size[port] + 2 * self.m3_pitch + offset = vector(self.control_logic_insts[port].rx() + self.dff.width, + y_offset) + cr = channel_route(netlist=route_map, + offset=offset, + layer_stack=layer_stack, + parent=self) + if add_routes: + # This causes problem in magic since it sometimes cannot extract connectivity of instances + # with no active devices. + self.add_inst(cr.name, cr) + self.connect_inst([]) + # self.add_flat_inst(cr.name, cr) + else: + self.data_bus_size[port] = max(cr.height, self.col_addr_bus_size[port]) + self.data_bus_gap + else: + if "s_en" in self.control_logic_insts[port].mod.pin_map: + y_top = max(self.bank.height, self.control_logic_insts[port].get_pin("s_en").uy()) + else: + y_top = self.bank.height + y_offset = y_top + self.m3_pitch + offset = vector(0, + y_offset) + cr = channel_route(netlist=route_map, + offset=offset, + layer_stack=layer_stack, + parent=self) + if add_routes: + # This causes problem in magic since it sometimes cannot extract connectivity of instances + # with no active devices. + self.add_inst(cr.name, cr) + self.connect_inst([]) + # self.add_flat_inst(cr.name, cr) + else: + self.data_bus_size[port] = max(cr.height, self.col_addr_bus_size[port]) + self.data_bus_gap + + def route_clk(self): + """ Route the clock network """ + + # This is the actual input to the SRAM + for port in self.all_ports: + # Connect all of these clock pins to the clock in the central bus + # This is something like a "spine" clock distribution. The two spines + # are clk_buf and clk_buf_bar + control_clk_buf_pin = self.control_logic_insts[port].get_pin("clk_buf") + control_clk_buf_pos = control_clk_buf_pin.center() + + # This uses a metal2 track to the right (for port0) of the control/row addr DFF + # to route vertically. For port1, it is to the left. + row_addr_clk_pin = self.row_addr_dff_insts[port].get_pin("clk") + if port % 2: + control_clk_buf_pos = control_clk_buf_pin.lc() + row_addr_clk_pos = row_addr_clk_pin.lc() + mid1_pos = vector(self.row_addr_dff_insts[port].lx() - self.m2_pitch, + row_addr_clk_pos.y) + else: + control_clk_buf_pos = control_clk_buf_pin.rc() + row_addr_clk_pos = row_addr_clk_pin.rc() + mid1_pos = vector(self.row_addr_dff_insts[port].rx() + self.m2_pitch, + row_addr_clk_pos.y) + + # This is the steiner point where the net branches out + clk_steiner_pos = vector(mid1_pos.x, control_clk_buf_pos.y) + self.add_path(control_clk_buf_pin.layer, [control_clk_buf_pos, clk_steiner_pos]) + self.add_via_stack_center(from_layer=control_clk_buf_pin.layer, + to_layer="m2", + offset=clk_steiner_pos) + + # Note, the via to the control logic is taken care of above + self.add_wire(self.m2_stack[::-1], + [row_addr_clk_pos, mid1_pos, clk_steiner_pos]) + + if self.col_addr_dff: + dff_clk_pin = self.col_addr_dff_insts[port].get_pin("clk") + dff_clk_pos = dff_clk_pin.center() + mid_pos = vector(clk_steiner_pos.x, dff_clk_pos.y) + self.add_wire(self.m2_stack[::-1], + [dff_clk_pos, mid_pos, clk_steiner_pos]) + elif port in self.write_ports: + data_dff_clk_pin = self.data_dff_insts[port].get_pin("clk") + data_dff_clk_pos = data_dff_clk_pin.center() + mid_pos = vector(clk_steiner_pos.x, data_dff_clk_pos.y) + # In some designs, the steiner via will be too close to the mid_pos via + # so make the wire as wide as the contacts + self.add_path("m2", + [mid_pos, clk_steiner_pos], + width=max(self.m2_via.width, self.m2_via.height)) + self.add_wire(self.m2_stack[::-1], + [data_dff_clk_pos, mid_pos, clk_steiner_pos]) + + def route_control_logic(self): + """ + Route the control logic pins that are not inputs + """ + + for port in self.all_ports: + for signal in self.control_logic_outputs[port]: + # The clock gets routed separately and is not a part of the bank + if "clk" in signal: + continue + src_pin = self.control_logic_insts[port].get_pin(signal) + dest_pin = self.bank_inst.get_pin(signal + "{}".format(port)) + self.connect_vbus(src_pin, dest_pin) + + if self.has_rbl: + for port in self.all_ports: + # Only input (besides pins) is the replica bitline + src_pin = self.control_logic_insts[port].get_pin("rbl_bl") + dest_pin = self.bank_inst.get_pin("rbl_bl_{0}_{0}".format(port)) + self.add_wire(self.m3_stack, + [src_pin.center(), vector(src_pin.cx(), dest_pin.cy()), dest_pin.rc()]) + self.add_via_stack_center(from_layer=src_pin.layer, + to_layer="m4", + offset=src_pin.center()) + self.add_via_stack_center(from_layer=dest_pin.layer, + to_layer="m3", + offset=dest_pin.center()) + + def route_row_addr_dff(self): + """ + Connect the output of the row flops to the bank pins + """ + for port in self.all_ports: + for bit in range(self.row_addr_size): + flop_name = "dout_{}".format(bit) + bank_name = "addr{0}_{1}".format(port, bit + self.col_addr_size) + flop_pin = self.row_addr_dff_insts[port].get_pin(flop_name) + bank_pin = self.bank_inst.get_pin(bank_name) + flop_pos = flop_pin.center() + bank_pos = bank_pin.center() + mid_pos = vector(bank_pos.x, flop_pos.y) + self.add_via_stack_center(from_layer=flop_pin.layer, + to_layer="m3", + offset=flop_pos) + self.add_path("m3", [flop_pos, mid_pos]) + self.add_via_stack_center(from_layer=bank_pin.layer, + to_layer="m3", + offset=mid_pos) + self.add_path(bank_pin.layer, [mid_pos, bank_pos]) + + 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. + """ + return + for n in self.control_logic_outputs[0]: + pin = self.control_logic_insts[0].get_pin(n) + self.add_label(text=n, + layer=pin.layer, + offset=pin.center()) + + def graph_exclude_data_dff(self): + """ + Removes data dff and wmask dff (if applicable) from search graph. + """ + # 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 != self.word_size: + for inst in self.wmask_dff_insts: + self.graph_inst_exclude.add(inst) + if self.num_spare_cols: + for inst in self.spare_wen_dff_insts: + self.graph_inst_exclude.add(inst) + + def graph_exclude_addr_dff(self): + """ + Removes data dff from search graph. + """ + # Address is considered not part of the critical path, subjectively removed + for inst in self.row_addr_dff_insts: + self.graph_inst_exclude.add(inst) + + if self.col_addr_dff: + for inst in self.col_addr_dff_insts: + self.graph_inst_exclude.add(inst) + + def graph_exclude_ctrl_dffs(self): + """ + Exclude dffs for CSB, WEB, etc from graph + """ + # Insts located in control logic, exclusion function called here + for inst in self.control_logic_insts: + inst.mod.graph_exclude_dffs() + + def get_cell_name(self, inst_name, row, col): + """ + Gets the spice name of the target bitcell. + """ + # Sanity check in case it was forgotten + if inst_name.find("x") != 0: + inst_name = "x" + inst_name + return self.bank_inst.mod.get_cell_name(inst_name + "{}x".format(OPTS.hier_seperator) + self.bank_inst.name, row, col) + + def get_bank_num(self, inst_name, row, col): + return 0 diff --git a/compiler/modules/sram_openroad.py b/compiler/modules/sram_openroad.py index ec8f95bf7..514a042b9 100644 --- a/compiler/modules/sram_openroad.py +++ b/compiler/modules/sram_openroad.py @@ -6,6 +6,7 @@ # All rights reserved. # # This file aiming to create the sram module without routing +# provide methods of seperated lef file generation import datetime from math import ceil from importlib import import_module, reload @@ -253,35 +254,7 @@ def place_io_pins(self, bbox): design=self) pl.add_io_pins(pins_to_route) #pl.design.remove_layout_pin("wmask0[0]") # this could use for deleting pins - ''' - # add the corresponding pin to the edge, the io pin will be put defaut as the nearest edge - for pin_name in pins_to_route: - ll, ur = bbox - pin = self.get_pin(pin_name) - print(pin) - c = pin.center() - # Find the closest edge - edge, vertical = self.get_closest_edge(c,bbox=bbox) - - # Keep the fake pin out of the SRAM layout are so that they won't be - # blocked by previous signals if they're on the same orthogonal line - if edge == "left": - fake_center = vector(ll.x - rtr.track_wire * 2, c.y) - if edge == "bottom": - fake_center = vector(c.x, ll.y - rtr.track_wire * 2) - if edge == "right": - fake_center = vector(ur.x + rtr.track_wire * 2, c.y) - if edge == "top": - fake_center = vector(c.x, ur.y + rtr.track_wire * 2) - - # Create the fake pin shape - layer = rtr.get_layer(int(not vertical)) - - self.add_layout_pin_rect_center(text=pin_name + "_IO", - layer=layer, - offset=fake_center) - rtr.add_perimeter_fake_pins() - ''' + def get_closest_edge(self, point, bbox): """ Return a point's the closest edge and the edge's axis direction. """ diff --git a/compiler/router/io_pin_placer.py b/compiler/router/io_pin_placer.py index e48f1f9d2..48f0f722e 100644 --- a/compiler/router/io_pin_placer.py +++ b/compiler/router/io_pin_placer.py @@ -109,7 +109,7 @@ def create_fake_pin(self, pin): fake_center = vector(c.x + add_distance, ur.y + self.track_wire * 2 - offset) is_too_close = any(abs(pin_added.center().x - fake_center.x) < (0.8 + self.half_wire * 4)for pin_added in self.io_pins_added_up) - # Create the fake pin shape + # Create the fake pin shape, here make sure the pin in the gds will be big enough layer = self.get_layer(int(not vertical)) half_wire_vector = vector([self.half_wire] * 2) nll = fake_center - half_wire_vector - half_wire_vector diff --git a/compiler/sram.py b/compiler/sram.py index 6af99cb99..fcce2acdd 100644 --- a/compiler/sram.py +++ b/compiler/sram.py @@ -162,7 +162,7 @@ def save(self): spname = OPTS.output_path + self.s.name + ".sp" debug.print_raw("SP: Writing to {0}".format(spname)) self.sp_write(spname) - #''' #comment the following state when generating big sram, and then disable drc/lvs, because maigc_ext stuck + ''' #comment the following state when generating big sram, and then disable drc/lvs, because maigc_ext stuck # Save a functional simulation file with default period functional(self.s, spname, @@ -184,7 +184,7 @@ def save(self): d.targ_write_ports = [self.s.write_ports[0]] d.write_delay_stimulus() print_time("DELAY", datetime.datetime.now(), start_time) - #''' #comment the above when generating big sram, and then disable drc/;vs, bevause magic_ext stuck + ''' #comment the above when generating big sram, and then disable drc/lvs, bevause magic_ext stuck # Save trimmed spice file temp_trim_sp = "{0}trimmed.sp".format(OPTS.output_path) self.sp_write(temp_trim_sp, lvs=False, trim=True) diff --git a/compiler/sram_road.py b/compiler/sram_road.py index 77e7261fd..87e133de5 100644 --- a/compiler/sram_road.py +++ b/compiler/sram_road.py @@ -66,7 +66,8 @@ def __init__(self, sram_config=None, name=None, mod=0): self.name = name #from openram.modules.sram_1bank import sram_1bank as sram - from openram.modules.sram_part import sram_1bank as sram + from openram.modules.sram_for_road import sram_for_road as sram + self.s = sram(name, sram_config) def get_sp_name(self): diff --git a/sram_compiler.py b/sram_compiler.py index 40480e301..e1404745a 100755 --- a/sram_compiler.py +++ b/sram_compiler.py @@ -77,6 +77,9 @@ s.save() #s.save_only() ''' +from openram import sram_road +s = sram_road.sram(mod=0) +s.save(mod=0) del s s = sram_road.sram(mod=1) s.save(mod=1) From 61f5ff6ec44de5686ea89920f088105d5a07441a Mon Sep 17 00:00:00 2001 From: FriedrichWu Date: Wed, 30 Oct 2024 13:24:53 +0100 Subject: [PATCH 13/26] updata io_pin_placer --- compiler/router/io_pin_placer.py | 393 +++++++++++++++++++++++++++++-- 1 file changed, 369 insertions(+), 24 deletions(-) diff --git a/compiler/router/io_pin_placer.py b/compiler/router/io_pin_placer.py index 48f0f722e..48aaa0d00 100644 --- a/compiler/router/io_pin_placer.py +++ b/compiler/router/io_pin_placer.py @@ -13,9 +13,6 @@ import re class io_pin_placer(router): - """ - This is the signal escape router that uses the Hanan grid graph method. - """ def __init__(self, layers, design, bbox=None): @@ -30,6 +27,9 @@ def __init__(self, layers, design, bbox=None): self.io_pins_added_right = [] self.io_pins_added_up = [] self.io_pins_added_down = [] + + # fake_pins, use for rename + self.io_pins_fake =[] def get_closest_edge(self, point): @@ -51,8 +51,162 @@ def get_closest_edge(self, point): if min_diff == ur_diff_x: return "right", True return "top", False - + + def initial_position(self, pins): # pins is list [] + """ Set the IO pin center at the perimeter """ + pattern_clk = r'^clk' + pattern_addr0 = r'^addr0' + pattern_addr1 = r'^addr1' + pattern_dout0 = r'^dout0' + pattern_dout1 = r'^dout1' + pattern_din = r'^din' + pattern_wmask = r'^wmask' + pattern_spare_wen = r'^spare_wen' + pattern_web = r'^web' + pattern_csb = r'^csb' + for pin in pins: + c = pin.center() + # Find the closest edge + edge, vertical = self.get_closest_edge(c) + if re.match(pattern_clk, pin.name):# clk, should be placed at horizontal edge + if edge == "bottom" or edge == "left": + edge = "bottom" + elif edge == "top" or edge == "right": + edge = "top" + self.store_position(pin, edge, vertical) + if re.match(pattern_addr0, pin.name): # all the addr0[] should be placed at left edge + if edge == "top" or edge == "left": + edge = "left" + vertical = True + elif edge == "bottom": # but for big sram, addr0[] may have pins at bottom, which is allowed + vertical = False + self.store_position(pin, edge, vertical) + if re.match(pattern_addr1, pin.name): # all the addr1[] should be placed at right edge + if edge == "bottom" or edge == "right": + edge = "right" + vertical = True + elif edge == "top": # but for big sram, addr1[] may have pins at top, which is allowed + vertical = False + self.store_position(pin, edge, vertical) + if re.match(pattern_din, pin.name): # din + self.store_position(pin, edge, vertical) + if re.match(pattern_wmask, pin.name): # wmask + self.store_position(pin, edge, vertical) + if re.match(pattern_spare_wen, pin.name): # spare_wen + self.store_position(pin, edge, vertical) + if re.match(pattern_csb, pin.name):# csb + self.store_position(pin, edge, vertical) + if re.match(pattern_web, pin.name): # web + self.store_position(pin, edge, vertical) + # special handle the dout pins, if r/rw ports + for pin in pins: + if re.match(pattern_dout0, pin.name): + edge = "bottom" + vertical = False + self.store_dout_position(pin, edge, vertical) + if re.match(pattern_dout1, pin.name): + edge = "top" + vertical = False + self.store_dout_position(pin, edge, vertical) + + + def check_overlap(self, pin_position, edge): + """ Return the suggested position to aviod overlap """ + ll, ur = self.bbox + c = pin_position # original source pin center + offset = 0.95 + 0.19 # FIX: this is the magic number to overcome the ovetflow problem at the boundary, may need a method + add_distance = 0 + + if edge == "bottom": + fake_center = vector(c.x, ll.y - self.track_wire * 2 + offset) + pin_to_close = any(abs(pin_added.center().x - fake_center.x) < (0.4 + self.half_wire * 4)for pin_added in self.io_pins_added_down) + via_to_close = False + # if cannot direct place below the source pin, need move towards right, and ensure the min. distance between vias + while pin_to_close or via_to_close: + debug.warning("overlap, changing position") + add_distance = add_distance + 0.1 + fake_center = vector(c.x + add_distance, ll.y - self.track_wire * 2 + offset) + pin_to_close = any(abs(pin_added.center().x - fake_center.x) < (0.4 + self.half_wire * 4)for pin_added in self.io_pins_added_down) + via_to_close = abs(fake_center.x - c.x) < 0.6 + if edge == "top": + fake_center = vector(c.x, ur.y + self.track_wire * 2 - offset) + pin_to_close = any(abs(pin_added.center().x - fake_center.x) < (0.4 + self.half_wire * 4)for pin_added in self.io_pins_added_up) + via_to_close = False + # if cannot direct place below the source pin, need move towards right, and ensure the min. distance between vias + while pin_to_close or via_to_close: + debug.warning("overlap, changing position") + add_distance = add_distance + 0.1 + fake_center = vector(c.x + add_distance, ur.y + self.track_wire * 2 - offset) + pin_to_close = any(abs(pin_added.center().x - fake_center.x) < (0.4 + self.half_wire * 4)for pin_added in self.io_pins_added_up) + via_to_close = abs(fake_center.x - c.x) < 0.6 + + return fake_center + + + def store_dout_position(self, pin, edge, vertical): + pin_position = pin.center() + pin_position = self.check_overlap(pin_position, edge) + # store the center position, rect, layer of fake pin, here make sure the pin in the gds will be big enough + layer = self.get_layer(int(not vertical)) + half_wire_vector = vector([self.half_wire] * 2) + nll = pin_position - half_wire_vector - half_wire_vector + nur = pin_position + half_wire_vector + half_wire_vector + rect = [nll, nur] + #fake_pin = [pin.name() + "_" + "fake", pin_position, rect, layer] + fake_pin = graph_shape(name=pin.name + "_" + "fake", + rect=rect, + layer_name_pp=layer) + + if edge == "left": + self.io_pins_added_left.append(fake_pin) + elif edge == "bottom": + self.io_pins_added_down.append(fake_pin) + elif edge == "right": + self.io_pins_added_right.append(fake_pin) + elif edge == "top": + self.io_pins_added_up.append(fake_pin) + + self.io_pins_fake.append(fake_pin) + debug.warning("pin added: {0}".format(fake_pin)) + + + def store_position(self, pin, edge, vertical): # also need to store the source pin + ll, ur = self.bbox + c = pin.center() + offset = 0.95 + 0.19 # FIX: this is the magic number to overcome the ovetflow problem at the boundary, may need a method + if edge == "left": + fake_center = vector(ll.x - self.track_wire * 2 + offset, c.y) + if edge == "bottom": + fake_center = vector(c.x, ll.y - self.track_wire * 2 + offset) + if edge == "right": + fake_center = vector(ur.x + self.track_wire * 2 - offset, c.y) + if edge == "top": + fake_center = vector(c.x, ur.y + self.track_wire * 2 - offset) + # store the center position, rect, layer of fake pin, here make sure the pin in the gds will be big enough + layer = self.get_layer(int(not vertical)) + half_wire_vector = vector([self.half_wire] * 2) + nll = fake_center - half_wire_vector - half_wire_vector + nur = fake_center + half_wire_vector + half_wire_vector + rect = [nll, nur] + #fake_pin = [pin.name() + "_" + "fake", fake_center, rect, layer] + fake_pin = graph_shape(name=pin.name + "_" + "fake", + rect=rect, + layer_name_pp=layer) + + if edge == "left": + self.io_pins_added_left.append(fake_pin) + elif edge == "bottom": + self.io_pins_added_down.append(fake_pin) + elif edge == "right": + self.io_pins_added_right.append(fake_pin) + elif edge == "top": + self.io_pins_added_up.append(fake_pin) + + self.io_pins_fake.append(fake_pin) + debug.warning("pin added: {0}".format(fake_pin)) + + def create_fake_pin(self, pin): """ Create a fake pin on the perimeter orthogonal to the given pin. """ @@ -63,51 +217,70 @@ def create_fake_pin(self, pin): print(pin.name) # Find the closest edge edge, vertical = self.get_closest_edge(c) + # Relocate the pin position of addr/dout + pattern_addr = r'^addr' + pattern_dout = r'^dout' + if re.match(pattern_addr, pin.name):# all the addr[] should be placed at vertical edge + if edge == "top" or edge == "left": + edge = "left" + vertical = True + elif edge == "bottom" or edge == "right": + edge = "right" + vertical = True + + if re.match(pattern_dout, pin.name):# all the dout[] should be placed at horizontal edge + if edge == "bottom" or edge == "right": + edge = "bottom" + vertical = False + elif edge == "top" or edge == "left": + edge = "top" + vertical = False + offset = 0.95 + 0.19 # FIX: this is the magic number to overcome the ovetflow problem at the boundary, may need a method add_distance = 0 # Keep the fake pin out of the SRAM layout are so that they won't be # blocked by previous signals if they're on the same orthogonal line if edge == "left": fake_center = vector(ll.x - self.track_wire * 2 + offset, c.y) - is_too_close = any(abs(pin_added.center().y - fake_center.y) < (0.8 + self.half_wire * 4)for pin_added in self.io_pins_added_left) + is_too_close = any(abs(pin_added.center().y - fake_center.y) < (0.4 + self.half_wire * 4)for pin_added in self.io_pins_added_left) while is_too_close: debug.warning("overlap, changing position") - add_distance = add_distance + 0.8 + self.half_wire * 4 + add_distance = add_distance + 0.1#+ 0.4 + self.half_wire * 4 fake_center = vector(ll.x - self.track_wire * 2 + offset, c.y + add_distance) - is_too_close = any(abs(pin_added.center().y - fake_center.y) < (0.8 + self.half_wire * 4)for pin_added in self.io_pins_added_left) + is_too_close = any(abs(pin_added.center().y - fake_center.y) < (0.4 + self.half_wire * 4)for pin_added in self.io_pins_added_left) if edge == "bottom": fake_center = vector(c.x, ll.y - self.track_wire * 2 + offset) - is_too_close = any(abs(pin_added.center().x - fake_center.x) < (0.8 + self.half_wire * 4)for pin_added in self.io_pins_added_down) + is_too_close = any(abs(pin_added.center().x - fake_center.x) < (0.4 + self.half_wire * 4)for pin_added in self.io_pins_added_down) while is_too_close: debug.warning("overlap, changing position") - add_distance = add_distance + 0.8 + self.half_wire * 4 + add_distance = add_distance + 0.1#0.4 + self.half_wire * 4 fake_center = vector(c.x + add_distance, ll.y - self.track_wire * 2 + offset) - is_too_close = any(abs(pin_added.center().x - fake_center.x) < (0.8 + self.half_wire * 4)for pin_added in self.io_pins_added_down) + is_too_close = any(abs(pin_added.center().x - fake_center.x) < (0.4 + self.half_wire * 4)for pin_added in self.io_pins_added_down) if edge == "right": fake_center = vector(ur.x + self.track_wire * 2 - offset, c.y) - is_too_close = any(abs(pin_added.center().y - fake_center.y) < (0.8 + self.half_wire * 4)for pin_added in self.io_pins_added_right) + is_too_close = any(abs(pin_added.center().y - fake_center.y) < (0.4 + self.half_wire * 4)for pin_added in self.io_pins_added_right) while is_too_close: debug.warning("overlap, changing position") - add_distance = add_distance + 0.8 + self.half_wire * 4 + add_distance = add_distance + 0.1#0.4 + self.half_wire * 4 fake_center = vector(ur.x + self.track_wire * 2 - offset, c.y + add_distance) # debug for pin_added in self.io_pins_added_right: dis = abs(pin_added.center().y - fake_center.y) debug.warning("current position is {0}".format(fake_center)) debug.warning("distance from {0} is {1}".format(pin_added, dis)) - debug.warning("must disrance is {0}".format(0.8 + self.half_wire * 4)) - is_too_close = any(abs(pin_added.center().y - fake_center.y) < (0.8 + self.half_wire * 4)for pin_added in self.io_pins_added_right) + debug.warning("must disrance is {0}".format(0.4 + self.half_wire * 4)) + is_too_close = any(abs(pin_added.center().y - fake_center.y) < (0.4 + self.half_wire * 4)for pin_added in self.io_pins_added_right) if edge == "top": fake_center = vector(c.x, ur.y + self.track_wire * 2 - offset) - is_too_close = any(abs(pin_added.center().x - fake_center.x) < (0.8 + self.half_wire * 4)for pin_added in self.io_pins_added_up) + is_too_close = any(abs(pin_added.center().x - fake_center.x) < (0.4 + self.half_wire * 4)for pin_added in self.io_pins_added_up) while is_too_close: debug.warning("overlap, changing position") - add_distance = add_distance + 0.8 + self.half_wire * 4 + add_distance = add_distance + 0.1#0.4 + self.half_wire * 4 fake_center = vector(c.x + add_distance, ur.y + self.track_wire * 2 - offset) - is_too_close = any(abs(pin_added.center().x - fake_center.x) < (0.8 + self.half_wire * 4)for pin_added in self.io_pins_added_up) + is_too_close = any(abs(pin_added.center().x - fake_center.x) < (0.4 + self.half_wire * 4)for pin_added in self.io_pins_added_up) # Create the fake pin shape, here make sure the pin in the gds will be big enough layer = self.get_layer(int(not vertical)) @@ -133,11 +306,34 @@ def create_fake_pin(self, pin): self.io_pins_added_up.append(pin) debug.warning("pin added: {0}".format(pin)) - return pin + return pin, vertical, add_distance def add_io_pins(self, pin_names): - """ Add IO pins on the edges without routing them. """ + """ Add IO pins on the edges WITHOUT routing them. """ + debug.info(1, "Adding IO pins on the perimeter...") + + # Prepare GDS reader (if necessary for pin/blockage identification) + self.prepare_gds_reader() + + # Find pins to be added (without routing) + for name in pin_names: + self.find_pins(name)# this will add the pins to the self.pins + + # inital position + pin_list = [] + for name in self.pins: + pin = next(iter(self.pins[name])) + pin_list.append(pin) + + self.initial_position(pin_list) + + # Change IO pin names, which means "internal name" will be used, and internal io pins will not have label such as "dout0[0]" + self.replace_layout_pins(pin_names) + + + def add_io_pins_connected(self, pin_names): + """ Add IO pins on the edges WITH routing them. """ debug.info(1, "Adding IO pins on the perimeter...") # Prepare GDS reader (if necessary for pin/blockage identification) @@ -146,17 +342,166 @@ def add_io_pins(self, pin_names): # Find pins to be added (without routing) for name in pin_names: self.find_pins(name)# this will add the pins to the self.pins + debug.warning("the pins in pin_name -> {0}".format(name)) - # Replace layout pins with those on the perimeter + # inital position + pin_list = [] for name in self.pins: pin = next(iter(self.pins[name])) - fake_pin = self.create_fake_pin(pin) - self.design.replace_layout_pin(name, fake_pin) # if do not have replace_layout_pin, final gds won't have io pins - - + pin_list.append(pin) + debug.warning("the pins in self.pins -> {0}".format(name)) + + self.initial_position(pin_list) + + # add fake io pins at the perimeter, which will be used for routing + for fake_pin in self.io_pins_fake: + self.design.add_layout_pin(text=fake_pin.name, + layer=fake_pin.layer, + offset=fake_pin.ll(), + width=fake_pin.width(), + height=fake_pin.height()) + + # connect the source_pin and io_pin(target) + self.connect_pins(pin_names) + + # remove the fake pin before change the name, in order to avoid possible problem + for fake_pin in self.io_pins_fake: + self.remove_io_pins(fake_pin.name) + + # Change IO pin names, which means "internal name" will be used, and internal io pins will not have label such as "dout0[0]" + self.replace_layout_pins(pin_names) + + + def connect_pins(self, pin_names): # pin_names should be a list + """ Add IO pins on the edges, and connect them to the internal one, not-graph like process """ + debug.info(1, "connecting to io pins...") + pattern_dout = r'^dout' + for pin_name in pin_names: + # get pin pairs ready + source_pin = next(iter(self.pins[pin_name])) + for fake_pin in self.io_pins_fake: + if pin_name + "_" + "fake" == fake_pin.name: + target_pin = fake_pin + break + # special hanlde dout pins + if re.match(pattern_dout, pin_name): + number_str = re.findall(r'\[(\d+)\]', pin_name) + if number_str: + number = int(number_str[0]) + if number % 2 == 0: + is_up = True + else: + is_up = False + point_list = self.decide_point(source_pin, target_pin, is_up) + self.add_wire(point_list) + # other pins + else: + debug.warning("source{0}".format(source_pin.center())) + debug.warning("target{0}".format(target_pin.center())) + point_list = self.decide_point(source_pin, target_pin) + debug.warning("point_list->{0}".format(point_list)) + self.add_wire(point_list) + + + def add_wire(self, point_list): + if len(point_list) == 2: + # direct connect + self.add_line(point_list[0], point_list[1]) + elif len(point_list) == 4: + # intermediate points + self.add_line(point_list[0], point_list[1]) + self.add_via(point_list[1]) + self.add_line(point_list[1], point_list[2]) + self.add_via(point_list[2]) + self.add_line(point_list[2], point_list[3]) + + + def add_line(self, point_1, point_2): + if round(point_1.y, 3) == round(point_2.y, 3): + # horizontal m3 + self.design.add_path(self.get_layer(False), [point_1, point_2]) + else: + # vertical m4 + self.design.add_path(self.get_layer(True), [point_1, point_2]) + + + def add_via(self, point): + # currently only m3-m4 vias are supported in this method + # usd in order to make "z" shape routing only + self.design.add_via_stack_center(from_layer=self.get_layer(False), + to_layer=self.get_layer(True), + offset=point) + + + def add_start_via(self, point): + # currently only m3-m4 vias are supported in this method + # used in source_pin only + self.design.add_via_stack_center(from_layer=self.get_layer(False), + to_layer=self.get_layer(True), + offset=point) + + + def decide_point(self, source_pin, target_pin, is_up=False): + ll, ur = self.bbox + offset = 0.95 + 0.19 # FIX: this is the magic number to overcome the ovetflow problem at the boundary, may need a method + # internal -> left + if round(target_pin.center().x, 3) == round(ll.x - self.track_wire * 2 + offset, 3): + # direct connect possible + return [source_pin.rc(), target_pin.lc()] # FIX: not sure if shape overlap in met3 allowed, but seems OK + # internal -> right + if round(target_pin.center().x, 3) == round(ur.x + self.track_wire * 2 - offset, 3): + # direct connect possible + return [source_pin.lc(), target_pin.rc()] + # internal -> top, need to add start_via m3->m4 + if round(target_pin.center().y, 3) == round(ur.y + self.track_wire * 2 - offset, 3): + self.add_start_via(source_pin.center()) + if round(target_pin.center().x, 3) == round(source_pin.center().x, 3): + # direct connect possible + return [source_pin.bc(), target_pin.uc()] + else: + # need intermediate point + via_basic_y = self.design.bank.height + 3 # 3 is magic number, make sure out of bank area + is_up = not is_up# Be attention, for channel at the top, the is_up should be inverted! Otherwise will cause overlap! + if is_up: + via_basic_y = via_basic_y + 0.5 + else: + via_basic_y = via_basic_y - 0.5 + point_1 = vector(source_pin.center().x, via_basic_y) + point_2 = vector(target_pin.center().x, via_basic_y) + return [source_pin.bc(), point_1, point_2, target_pin.uc()] + # internal -> bottom, need to add start_via m3->m4 + if round(target_pin.center().y, 3) == round(ll.y - self.track_wire * 2 + offset, 3): + self.add_start_via(source_pin.center()) + if round(target_pin.center().x, 3) == round(source_pin.center().x, 3): + # direct connect possible + return [source_pin.uc(), target_pin.bc()] + else: + # need intermediate point + via_basic_y = ll.y + 21 # 21 is magic number, make sure out of dff area + if is_up: + via_basic_y = via_basic_y + 0.5 + else: + via_basic_y = via_basic_y - 0.5 + point_1 = vector(source_pin.center().x, via_basic_y) + point_2 = vector(target_pin.center().x, via_basic_y) + return [source_pin.uc(), point_1, point_2, target_pin.bc()] + + def remove_io_pins(self, pin_name): # remove io pin in gds, so we could reroute self.design.remove_layout_pin(pin_name) + + + def replace_layout_pins(self, pin_names): + """ Change the IO pin names with new ones around the perimeter. """ + for pin_name in pin_names: + perimeter_pin_name = pin_name + "_" + "fake" + for pin in self.io_pins_fake: + if pin.name == perimeter_pin_name: + perimeter_pin = pin + self.design.replace_layout_pin(pin_name, perimeter_pin) + break + From 434063656f61fa46e25215df096493b9799d4634 Mon Sep 17 00:00:00 2001 From: FriedrichWu Date: Thu, 31 Oct 2024 09:43:34 +0100 Subject: [PATCH 14/26] fix bugs in channel routing, which will add strange shape in m2 at dff pins --- compiler/base/channel_route.py | 140 +++++++++++++++++++++++++++++++-- 1 file changed, 135 insertions(+), 5 deletions(-) diff --git a/compiler/base/channel_route.py b/compiler/base/channel_route.py index 9b4c545cd..6c8a874b9 100644 --- a/compiler/base/channel_route.py +++ b/compiler/base/channel_route.py @@ -240,7 +240,7 @@ def route(self): current_offset = vector(real_channel_offset.x, real_channel_offset.y + 5) # make route out of dffs area if self.layer_stack == self.m2_stack: - self.vertical_nonpref_pitch = self.horizontal_pitch + self.vertical_nonpref_pitch = self.horizontal_pitch + 0.1 # 0.1 make sure even if via at same col, fulfill m3-m3 spacing if self.layer_stack == self.m1_stack: current_offset = vector(real_channel_offset.x, current_offset.y + 14) # make sure no overlap between col_dffs & data_dffs @@ -270,9 +270,14 @@ def route(self): self.horizontal_pitch) current_offset = vector(current_offset.x, net.max_value + self.horizontal_nonpref_pitch) else: - self.add_horizontal_trunk_route(net.pins, - current_offset, - self.vertical_pitch) + if self.dff_area == True: # only use in dff channel routing + self.add_horizontal_trunk_with_jog(net.pins, + current_offset, + self.vertical_pitch) + else: + self.add_horizontal_trunk_route(net.pins, + current_offset, + self.vertical_pitch) current_offset = vector(net.max_value + self.vertical_nonpref_pitch, current_offset.y) # Remove the net from other constriants in the VCG @@ -321,7 +326,132 @@ def get_layer_pitch(self, layer): except AttributeError: debug.error("Cannot find layer pitch.", -1) return (nonpref_pitch, pitch, pitch - space, space) - + + def add_horizontal_trunk_with_jog(self, + pins, + trunk_offset, + pitch): + """ Special for connecting channel of dffs & bank, avoid too close with vdd pins """ + max_x = max([pin.center().x for pin in pins]) + min_x = min([pin.center().x for pin in pins]) + min_y = min([pin.center().y for pin in pins]) + # see the channel is at top or bottom + if min_y < 0: # port0, min_x need to change + min_x = min_x - 0.1 # in order to add jog at the dff pins, avoid overlap with vdd pins, left shift vertical line at dout pin + port = 0 + else: # port1, max_x need to change + max_x = max_x - 0.1 # in order to add jog at the dff pins, avoid overlap with vdd pins, left shift vertical line at dout pin + port = 1 + # if we are less than a pitch, just create a non-preferred layer jog + non_preferred_route = max_x - min_x <= pitch + half_layer_width = 0.5 * drc["minwidth_{0}".format(self.vertical_layer)] + if port == 0: # bottom need shift + if non_preferred_route: + # Add the horizontal trunk on the vertical layer! + self.add_path(self.vertical_layer, + [vector(min_x - half_layer_width, trunk_offset.y), + vector(max_x + half_layer_width, trunk_offset.y)]) + + # Route each pin to the trunk + for pin in pins: + if pin.cy() < trunk_offset.y: + pin_pos = pin.center() + mid = vector(pin_pos.x - 0.1, trunk_offset.y) + self.add_path(self.vertical_layer, [vector(pin.bc().x - 0.1, pin.bc().y), mid]) + self.add_via_stack_center(from_layer=pin.layer, + to_layer=self.vertical_layer, + offset=pin_pos) + else: + pin_pos = pin.bc() + # No bend needed here + mid = vector(pin_pos.x, trunk_offset.y) + self.add_path(self.vertical_layer, [pin_pos, mid]) + self.add_via_stack_center(from_layer=pin.layer, + to_layer=self.vertical_layer, + offset=pin.bc()) + else: + # Add the horizontal trunk + self.add_path(self.horizontal_layer, + [vector(min_x, trunk_offset.y), + vector(max_x, trunk_offset.y)]) + + # Route each pin to the trunk + for pin in pins: + # Find the correct side of the pin + if pin.cy() < trunk_offset.y: + pin_pos = pin.center() + mid = vector(pin_pos.x - 0.1, trunk_offset.y) + self.add_path(self.vertical_layer, [vector(pin.bc().x - 0.1, pin.bc().y), mid]) + self.add_via_center(layers=self.layer_stack, + offset=mid, + directions=self.directions) + self.add_via_stack_center(from_layer=pin.layer, + to_layer=self.vertical_layer, + offset=pin_pos) + else: + pin_pos = pin.bc() + mid = vector(pin_pos.x, trunk_offset.y) + self.add_path(self.vertical_layer, [pin_pos, mid]) + self.add_via_center(layers=self.layer_stack, + offset=mid, + directions=self.directions) + self.add_via_stack_center(from_layer=pin.layer, + to_layer=self.vertical_layer, + offset=pin.bc()) + else: # port 1, situation different, top need shift + if non_preferred_route: + # Add the horizontal trunk on the vertical layer! + self.add_path(self.vertical_layer, + [vector(min_x - half_layer_width, trunk_offset.y), + vector(max_x + half_layer_width, trunk_offset.y)]) + + # Route each pin to the trunk + for pin in pins: + if pin.cy() < trunk_offset.y: + pin_pos = pin.uc() + # No bend needed here + mid = vector(pin_pos.x, trunk_offset.y) + self.add_path(self.vertical_layer, [pin_pos, mid]) + self.add_via_stack_center(from_layer=pin.layer, + to_layer=self.vertical_layer, + offset=pin.uc()) + else: + pin_pos = pin.center() + mid = vector(pin_pos.x - 0.1, trunk_offset.y) + self.add_path(self.vertical_layer, [vector(pin.uc().x - 0.1, pin.uc().y), mid]) + self.add_via_stack_center(from_layer=pin.layer, + to_layer=self.vertical_layer, + offset=pin_pos) + else: + # Add the horizontal trunk + self.add_path(self.horizontal_layer, + [vector(min_x, trunk_offset.y), + vector(max_x, trunk_offset.y)]) + + # Route each pin to the trunk + for pin in pins: + # Find the correct side of the pin + if pin.cy() < trunk_offset.y: + pin_pos = pin.uc() + mid = vector(pin_pos.x, trunk_offset.y) + self.add_path(self.vertical_layer, [pin_pos, mid]) + self.add_via_center(layers=self.layer_stack, + offset=mid, + directions=self.directions) + self.add_via_stack_center(from_layer=pin.layer, + to_layer=self.vertical_layer, + offset=pin.uc()) + else: + pin_pos = pin.center() + mid = vector(pin_pos.x - 0.1, trunk_offset.y) + self.add_path(self.vertical_layer, [vector(pin.uc().x - 0.1, pin.uc().y), mid]) + self.add_via_center(layers=self.layer_stack, + offset=mid, + directions=self.directions) + self.add_via_stack_center(from_layer=pin.layer, + to_layer=self.vertical_layer, + offset=pin_pos) + def add_horizontal_trunk_route(self, pins, trunk_offset, From 1f1f064036742e39b575fdd58c8b1878dba80020 Mon Sep 17 00:00:00 2001 From: FriedrichWu Date: Thu, 31 Oct 2024 09:44:11 +0100 Subject: [PATCH 15/26] fix clk csb overlap problem --- compiler/router/io_pin_placer.py | 57 ++++++++++++++++++++++++++------ 1 file changed, 47 insertions(+), 10 deletions(-) diff --git a/compiler/router/io_pin_placer.py b/compiler/router/io_pin_placer.py index 48aaa0d00..ae6677b9f 100644 --- a/compiler/router/io_pin_placer.py +++ b/compiler/router/io_pin_placer.py @@ -69,11 +69,12 @@ def initial_position(self, pins): # pins is list [] c = pin.center() # Find the closest edge edge, vertical = self.get_closest_edge(c) - if re.match(pattern_clk, pin.name):# clk, should be placed at horizontal edge - if edge == "bottom" or edge == "left": - edge = "bottom" - elif edge == "top" or edge == "right": - edge = "top" + if re.match(pattern_clk, pin.name):# clk, should be placed at vertical edge + if edge == "bottom" or edge == "left":#clk0 + edge = "left" + elif edge == "top" or edge == "right":#clk1 + edge = "right" + vertical = True self.store_position(pin, edge, vertical) if re.match(pattern_addr0, pin.name): # all the addr0[] should be placed at left edge if edge == "top" or edge == "left": @@ -174,13 +175,23 @@ def store_dout_position(self, pin, edge, vertical): def store_position(self, pin, edge, vertical): # also need to store the source pin ll, ur = self.bbox c = pin.center() + pattern_clk = r'^clk' + pattern_csb = r'^csb' offset = 0.95 + 0.19 # FIX: this is the magic number to overcome the ovetflow problem at the boundary, may need a method if edge == "left": fake_center = vector(ll.x - self.track_wire * 2 + offset, c.y) + if re.match(pattern_clk, pin.name): # clk0 need to be higher at left edge, 0.32 is magic number + fake_center = vector(ll.x - self.track_wire * 2 + offset, c.y + 0.32) + if re.match(pattern_csb, pin.name): # csb0 need to be lower at left edge, 0.32 is magic number + fake_center = vector(ll.x - self.track_wire * 2 + offset, c.y - 0.32) if edge == "bottom": fake_center = vector(c.x, ll.y - self.track_wire * 2 + offset) if edge == "right": fake_center = vector(ur.x + self.track_wire * 2 - offset, c.y) + if re.match(pattern_clk, pin.name): # clk1 need to be lower at right edge, 0.32 is magic number + fake_center = vector(ur.x + self.track_wire * 2 - offset, c.y - 0.32) + if re.match(pattern_csb, pin.name): # csb0 need to be higher at right edge, 0.32 is magic number + fake_center = vector(ur.x + self.track_wire * 2 - offset, c.y + 0.32) if edge == "top": fake_center = vector(c.x, ur.y + self.track_wire * 2 - offset) # store the center position, rect, layer of fake pin, here make sure the pin in the gds will be big enough @@ -441,17 +452,43 @@ def add_start_via(self, point): offset=point) + def add_big_plate(self, layer, offset, width, height): + # add rectagle at internal source pin, which avoid jog/non-preferred routing, but could implement shift-routing + # used in source_pin only + # offset->vertor(...), it is bottom-left position + self.design.add_rect(layer=layer, + offset=offset, + width=width, + height=height) + + def decide_point(self, source_pin, target_pin, is_up=False): ll, ur = self.bbox offset = 0.95 + 0.19 # FIX: this is the magic number to overcome the ovetflow problem at the boundary, may need a method - # internal -> left + pattern_clk = r'^clk' + pattern_csb = r'^csb' + # internal -> left if round(target_pin.center().x, 3) == round(ll.x - self.track_wire * 2 + offset, 3): - # direct connect possible - return [source_pin.rc(), target_pin.lc()] # FIX: not sure if shape overlap in met3 allowed, but seems OK + # special handle clk0 + if re.match(pattern_clk, source_pin.name): + return [vector(source_pin.rc().x, source_pin.rc().y + 0.32), target_pin.lc()]# 0.32 should be same in initial_position + # special handel csb0 + elif re.match(pattern_csb, source_pin.name): + return [vector(source_pin.rc().x, source_pin.rc().y - 0.32), target_pin.lc()]# 0.32 should be same in initial_position + else: + # direct connect possible + return [source_pin.rc(), target_pin.lc()] # FIX: not sure if shape overlap in met3 allowed, but seems OK # internal -> right if round(target_pin.center().x, 3) == round(ur.x + self.track_wire * 2 - offset, 3): - # direct connect possible - return [source_pin.lc(), target_pin.rc()] + # special handel clk1 + if re.match(pattern_clk, source_pin.name): + return [vector(source_pin.lc().x, source_pin.lc().y - 0.32), target_pin.rc()]# 0.32 should be same in initial_position + # special handle csb0 + elif re.match(pattern_csb, source_pin.name): + return [vector(source_pin.lc().x, source_pin.lc().y + 0.32), target_pin.rc()]# 0.32 should be same in initial_position + else: + # direct connect possible + return [source_pin.lc(), target_pin.rc()] # internal -> top, need to add start_via m3->m4 if round(target_pin.center().y, 3) == round(ur.y + self.track_wire * 2 - offset, 3): self.add_start_via(source_pin.center()) From f56460bb9404cb46dc5981130bb89a5d5174999f Mon Sep 17 00:00:00 2001 From: FriedrichWu Date: Tue, 12 Nov 2024 17:02:02 +0100 Subject: [PATCH 16/26] make code clean --- compiler/base/channel_route.py | 26 +- compiler/modules/sram_1bank.py | 63 +- compiler/modules/sram_for_road.py | 2378 -------------------- compiler/modules/sram_openroad.py | 1485 ------------- compiler/modules/sram_part.py | 2623 ----------------------- compiler/router/io_pin_placer.py | 239 +-- compiler/router/signal_escape_router.py | 26 +- compiler/sram.py | 123 +- compiler/sram_openroad_test.py | 247 --- compiler/sram_road.py | 268 --- sram_compiler.py | 34 +- 11 files changed, 198 insertions(+), 7314 deletions(-) delete mode 100644 compiler/modules/sram_for_road.py delete mode 100644 compiler/modules/sram_openroad.py delete mode 100644 compiler/modules/sram_part.py delete mode 100644 compiler/sram_openroad_test.py delete mode 100644 compiler/sram_road.py diff --git a/compiler/base/channel_route.py b/compiler/base/channel_route.py index 6c8a874b9..e1f9bedc1 100644 --- a/compiler/base/channel_route.py +++ b/compiler/base/channel_route.py @@ -144,16 +144,16 @@ def __init__(self, layer_stuff = self.get_layer_pitch(self.horizontal_layer) (self.horizontal_nonpref_pitch, self.horizontal_pitch, self.horizontal_width, self.horizontal_space) = layer_stuff # For debug - + debug.warning("layer horizontal: {0}".format(self.horizontal_layer)) debug.warning("horizontal_nonpref_pitch: {0}".format(self.horizontal_nonpref_pitch)) debug.warning("horizontal_pitch: {0}".format(self.horizontal_pitch)) - debug.warning("horizontal_space: {0}".format(self.horizontal_space)) + debug.warning("horizontal_space: {0}".format(self.horizontal_space)) debug.warning("layer vertical: {0}".format(self.vertical_layer)) debug.warning("vertiacl_nonpref_pitch: {0}".format(self.vertical_pitch)) debug.warning("vertical_pitch: {0}".format(self.vertical_pitch)) debug.warning("vertical_space: {0}".format(self.vertical_space)) - + self.route() def remove_net_from_graph(self, pin, g): @@ -233,7 +233,7 @@ def route(self): real_channel_offset = vector(self.offset.x, min(self.min_value, self.offset.y)) else: real_channel_offset = vector(min(self.min_value, self.offset.x), self.offset.y) - + if self.dff_area == False: current_offset = real_channel_offset else: # special handle for dff area @@ -241,7 +241,7 @@ def route(self): if self.layer_stack == self.m2_stack: self.vertical_nonpref_pitch = self.horizontal_pitch + 0.1 # 0.1 make sure even if via at same col, fulfill m3-m3 spacing - + if self.layer_stack == self.m1_stack: current_offset = vector(real_channel_offset.x, current_offset.y + 14) # make sure no overlap between col_dffs & data_dffs if real_channel_offset.y > 0: # which means this is channnel router for coldff at the top @@ -326,8 +326,8 @@ def get_layer_pitch(self, layer): except AttributeError: debug.error("Cannot find layer pitch.", -1) return (nonpref_pitch, pitch, pitch - space, space) - - def add_horizontal_trunk_with_jog(self, + + def add_horizontal_trunk_with_jog(self, pins, trunk_offset, pitch): @@ -346,7 +346,7 @@ def add_horizontal_trunk_with_jog(self, non_preferred_route = max_x - min_x <= pitch half_layer_width = 0.5 * drc["minwidth_{0}".format(self.vertical_layer)] if port == 0: # bottom need shift - if non_preferred_route: + if non_preferred_route: # Add the horizontal trunk on the vertical layer! self.add_path(self.vertical_layer, [vector(min_x - half_layer_width, trunk_offset.y), @@ -368,7 +368,7 @@ def add_horizontal_trunk_with_jog(self, self.add_path(self.vertical_layer, [pin_pos, mid]) self.add_via_stack_center(from_layer=pin.layer, to_layer=self.vertical_layer, - offset=pin.bc()) + offset=pin.bc()) else: # Add the horizontal trunk self.add_path(self.horizontal_layer, @@ -399,7 +399,7 @@ def add_horizontal_trunk_with_jog(self, to_layer=self.vertical_layer, offset=pin.bc()) else: # port 1, situation different, top need shift - if non_preferred_route: + if non_preferred_route: # Add the horizontal trunk on the vertical layer! self.add_path(self.vertical_layer, [vector(min_x - half_layer_width, trunk_offset.y), @@ -411,7 +411,7 @@ def add_horizontal_trunk_with_jog(self, pin_pos = pin.uc() # No bend needed here mid = vector(pin_pos.x, trunk_offset.y) - self.add_path(self.vertical_layer, [pin_pos, mid]) + self.add_path(self.vertical_layer, [pin_pos, mid]) self.add_via_stack_center(from_layer=pin.layer, to_layer=self.vertical_layer, offset=pin.uc()) @@ -440,7 +440,7 @@ def add_horizontal_trunk_with_jog(self, directions=self.directions) self.add_via_stack_center(from_layer=pin.layer, to_layer=self.vertical_layer, - offset=pin.uc()) + offset=pin.uc()) else: pin_pos = pin.center() mid = vector(pin_pos.x - 0.1, trunk_offset.y) @@ -451,7 +451,7 @@ def add_horizontal_trunk_with_jog(self, self.add_via_stack_center(from_layer=pin.layer, to_layer=self.vertical_layer, offset=pin_pos) - + def add_horizontal_trunk_route(self, pins, trunk_offset, diff --git a/compiler/modules/sram_1bank.py b/compiler/modules/sram_1bank.py index 1d76e07c9..3fada4d53 100644 --- a/compiler/modules/sram_1bank.py +++ b/compiler/modules/sram_1bank.py @@ -18,6 +18,7 @@ from openram.sram_factory import factory from openram.tech import spice from openram import OPTS, print_time +import re class sram_1bank(design, verilog, lef): @@ -206,8 +207,8 @@ def create_netlist(self): if not OPTS.is_unit_test: print_time("Submodules", datetime.datetime.now(), start_time) - - def create_layout(self, position_add=0, mod=0): + + def create_layout(self, position_add=0, mod=0, route_option="classic"): """ Layout creation """ start_time = datetime.datetime.now() self.place_instances_changeable(position_add=position_add) @@ -215,7 +216,7 @@ def create_layout(self, position_add=0, mod=0): print_time("Placement", datetime.datetime.now(), start_time) start_time = datetime.datetime.now() - self.route_layout(mod=mod) + self.route_layout(mod=mod, route_option=route_option) if not OPTS.is_unit_test: print_time("Routing", datetime.datetime.now(), start_time) @@ -239,7 +240,7 @@ def create_layout(self, position_add=0, mod=0): # 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) @@ -319,7 +320,7 @@ def route_supplies(self, bbox=None): # Grid is left with many top level pins pass - def route_escape_pins(self, bbox=None, mod=0): + def route_escape_pins(self, bbox=None, mod=0, route_option="classic"): """ Add the top-level pins for a single bank SRAM with control. """ @@ -361,17 +362,29 @@ def route_escape_pins(self, bbox=None, mod=0): else: for bit in range(self.num_spare_cols): pins_to_route.append("spare_wen{0}[{1}]".format(port, bit)) - - from openram.router import signal_escape_router as router - # mod Use for control which edge/position the pins(dout) will be placed - # 0 -> default - # 1 -> all top/bottom - # 2 -> all left/right - rtr = router(layers=self.m3_stack, - bbox=bbox, - design=self, - mod=mod) - rtr.route(pins_to_route) + + if route_option == "classic": + from openram.router import signal_escape_router as router + # mod Use for control which edge/position the pins(dout) will be placed + # 0 -> default + # 1 -> all top/bottom + # 2 -> all left/right + rtr = router(layers=self.m3_stack, + bbox=bbox, + design=self, + mod=mod) + rtr.route(pins_to_route) + elif route_option == "fast": + # use io_pin_placer + # put the IO pins at the edge + from openram.router.io_pin_placer import io_pin_placer as placer + pl = placer(layers=self.m3_stack, + bbox=bbox, + design=self) + for name in pins_to_route: + debug.warning("pins_to_route pins -> {0}".format(name)) + pl.add_io_pins_connected(pins_to_route) + #pl.add_io_pins(pins_to_route) def compute_bus_sizes(self): """ Compute the independent bus widths shared between two and four bank SRAMs """ @@ -784,7 +797,7 @@ def create_modules(self): self.spare_wen_dff_insts = self.create_spare_wen_dff() else: self.num_spare_cols = 0 - + def place_instances_changeable(self, position_add=0): """ This places the instances for a single bank SRAM with control @@ -849,11 +862,10 @@ def place_instances_changeable(self, position_add=0): self.add_layout_pins(add_vias=False) self.route_dffs(add_routes=False) self.remove_layout_pins() - + for port in self.all_ports: # Add the extra position - self.data_bus_size[port] += position_add - + self.data_bus_size[port] += position_add # Re-place with the new channel size self.place_dffs() @@ -866,7 +878,7 @@ def place_row_addr_dffs(self): x_offset = self.control_logic_insts[port].rx() - self.row_addr_dff_insts[port].width # It is above the control logic and the predecoder array y_offset = max(self.control_logic_insts[port].uy(), self.bank.predecoder_top) - + y_offset = y_offset + 0.4 # fix, maigc number self.row_addr_pos[port] = vector(x_offset, y_offset) self.row_addr_dff_insts[port].place(self.row_addr_pos[port]) @@ -875,7 +887,7 @@ def place_row_addr_dffs(self): # The row address bits are placed above the control logic aligned on the left. x_offset = self.control_pos[port].x - self.control_logic_insts[port].width + self.row_addr_dff_insts[port].width # If it can be placed above the predecoder and below the control logic, do it - y_offset = self.bank.predecoder_bottom + y_offset = min(self.control_logic_insts[port].by(), self.bank.predecoder_top) self.row_addr_pos[port] = vector(x_offset, y_offset) self.row_addr_dff_insts[port].place(self.row_addr_pos[port], mirror="XY") @@ -1061,7 +1073,7 @@ def add_layout_pins(self, add_vias=True): "spare_wen{0}[{1}]".format(port, bit), start_layer=pin_layer) - def route_layout(self, mod=0): + def route_layout(self, mod=0, route_option="classic"): """ Route a single bank SRAM """ self.route_clk() @@ -1085,12 +1097,11 @@ def route_layout(self, mod=0): if OPTS.perimeter_pins: # We now route the escape routes far enough out so that they will # reach past the power ring or stripes on the sides - self.route_escape_pins(bbox=init_bbox, mod=mod) - + self.route_escape_pins(bbox=init_bbox, mod=mod, route_option=route_option) + if OPTS.route_supplies: self.route_supplies(init_bbox) - def route_dffs(self, add_routes=True): for port in self.all_ports: diff --git a/compiler/modules/sram_for_road.py b/compiler/modules/sram_for_road.py deleted file mode 100644 index 62d088478..000000000 --- a/compiler/modules/sram_for_road.py +++ /dev/null @@ -1,2378 +0,0 @@ -# See LICENSE for licensing information. -# -# Copyright (c) 2016-2024 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. -# - -# This file aims to use openroad to do the routing -# No more rings for each moudle -# Provide methods for generating module verilog files & top verilog file -import datetime -from math import ceil -from importlib import import_module, reload -from openram import debug -from openram.base import vector -from openram.base import channel_route -from openram.base import design -from openram.base import verilog -from openram.base import lef -from openram.sram_factory import factory -from openram.tech import spice -from openram import OPTS, print_time - - -class sram_for_road(design, verilog, lef): - """ - Procedures specific to a one bank SRAM. - """ - def __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 openram.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 - - # delay control logic does not have RBLs - self.has_rbl = OPTS.control_logic != "control_logic_delay" - - def add_pin_bank(self): - # lef file use this pin name, should be same with new pin - """ Adding pins for Bank module""" - 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") - for port in self.all_ports: - if self.has_rbl: - self.add_pin("rbl_bl{0}".format(port), "OUTPUT") - for port in self.write_ports: - for bit in range(self.word_size + self.num_spare_cols): - self.add_pin("bank_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") - - # For more than one bank, we have a bank select and name - # the signals gated_*. - for port in self.read_ports: - self.add_pin("s_en{0}".format(port), "INPUT") - for port in self.all_ports: - self.add_pin("p_en_bar{0}".format(port), "INPUT") - for port in self.write_ports: - self.add_pin("w_en{0}".format(port), "INPUT") - for bit in range(self.num_wmasks): - self.add_pin("bank_wmask{0}[{1}]".format(port, bit), "INPUT") - for bit in range(self.num_spare_cols): - if self.num_spare_cols == 1: - self.add_pin("bank_spare_wen{0}".format(port), "INPUT") - else: - self.add_pin("bank_spare_wen{0}[{1}]".format(port, bit), "INPUT") - for port in self.all_ports: - self.add_pin("wl_en{0}".format(port), "INPUT") - - # 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_pin_row_addr_dff(self): - # lef file use this pin name, should be same with new pin - for port in self.all_ports: - for bit in range(self.row_addr_size): - #input - self.add_pin("addr{}[{}]".format(port, bit + self.col_addr_size), "INPUT") - print("addr{}[{}]".format(port, bit), "INPUT") - #output - self.add_pin("a{}[{}]".format(port, bit + self.col_addr_size), "OUTPUT") - #clk_buf, regard as input - self.add_pin("clk_buf{}".format(port), "INPUT") - # 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_pin_col_addr_dff(self): - # lef file use this pin name, should be same with new pin - for port in self.all_ports: - for bit in range(self.col_addr_size): - #input - self.add_pin("addr{}[{}]".format(port, bit), "INPUT") - #output - self.add_pin("a{}[{}]".format(port, bit), "OUTPUT") - #clk_buf, regard as input - self.add_pin("clk_buf{}".format(port), "INPUT") - # 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_pin_data_dff(self): - # lef file use this pin name, should be same with new pin - for port in self.all_ports: - for bit in range(self.word_size + self.num_spare_cols): - # input - self.add_pin("din{}[{}]".format(port, bit), "INPUT") - # output - self.add_pin("bank_din{}[{}]".format(port, bit), "OUTPUT") - #clk_buf, regard as input - self.add_pin("clk_buf{}".format(port), "INPUT") - # 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_pin_wmask_dff(self): - # lef file use this pin name, should be same with new pin - for port in self.all_ports: - for bit in range(self.num_wmasks): - # input - self.add_pin("wmask{}[{}]".format(port, bit), "INPUT") - # output - self.add_pin("bank_wmask{}[{}]".format(port, bit), "OUTPUT") - #clk_buf, regard as input - self.add_pin("clk_buf{}".format(port), "INPUT") - # 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_pin_spare_wen_dff(self): - # lef file use this pin name, should be same with new pin - for port in self.all_ports: - for bit in range(self.num_spare_cols): - if self.num_spare_cols == 1: - # input - self.add_pin("spare_wen{}".format(port), "INPUT") - # output - self.add_pin("bank_spare_wen{}".format(port), "OUTPUT") - else: - # input - self.add_pin("spare_wen{}[{}]".format(port, bit), "INPUT") - # output - self.add_pin("bank_spare_wen{}[{}]".format(port, bit), "OUTPUT") - #clk_buf, regard as input - self.add_pin("clk_buf{}".format(port), "INPUT") - # 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_pin_control(self): - # lef file use this pin name, should be same with new pin - for port in self.all_ports: - # Inputs - self.add_pin("csb{}".format(port), "INPUT") - if port in self.readwrite_ports: - self.add_pin("web{}".format(port), "INPUT") - self.add_pin("clk{}".format(port), "INPUT") - if self.has_rbl: - self.add_pin("rbl_bl{}".format(port), "INPUT") - # Outputs - if port in self.read_ports: - self.add_pin("s_en{}".format(port), "OUTPUT") - if port in self.write_ports: - self.add_pin("w_en{}".format(port), "OUTPUT") - self.add_pin("p_en_bar{}".format(port), "OUTPUT") - self.add_pin("wl_en{}".format(port), "OUTPUT") - self.add_pin("clk_buf{}".format(port), "OUTPUT") - - # Standard supply and ground names - # not sure if this part should stay inside the for loop - 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_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_netlist_bank(self): - """ Netlist creation """ - - start_time = datetime.datetime.now() - - # Must create the control logic before pins to get the pins - self.add_modules() - self.add_pin_bank() - self.bank_inst = self.create_bank(0) - - # 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 Bank", datetime.datetime.now(), start_time) - - def create_netlist_control(self): - """ Netlist creation """ - - start_time = datetime.datetime.now() - - # Must create the control logic before pins to get the pins - self.add_modules() - self.add_pin_control() - self.control_logic_insts = self.create_control_logic() # be aware, multi-insts possible - - # 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 Control", datetime.datetime.now(), start_time) - - def create_netlist_row_addr_dff(self): - """ Netlist creation """ - - start_time = datetime.datetime.now() - - # Must create the control logic before pins to get the pins - self.add_modules() - self.add_pin_row_addr_dff() - self.row_addr_dff_insts = self.create_row_addr_dff() # be aware, multi-insts possible - - # 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 row_addr_dff", datetime.datetime.now(), start_time) - - def create_netlist_col_addr_dff(self): - """ Netlist creation """ - - start_time = datetime.datetime.now() - - # Must create the control logic before pins to get the pins - self.add_modules() - if self.col_addr_dff: - self.add_pin_col_addr_dff() - self.col_addr_dff_insts = self.create_col_addr_dff() # be aware, multi-insts possible, or none - - # 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 col_addr_dff", datetime.datetime.now(), start_time) - else: - return False # no col_addr_dff will be generated - - def create_netlist_data_dff(self): - """ Netlist creation """ - - start_time = datetime.datetime.now() - - # Must create the control logic before pins to get the pins - self.add_modules() - self.add_pin_data_dff() - self.data_dff_insts = self.create_data_dff() # be aware, multi-insts possible - - # 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 data dff", datetime.datetime.now(), start_time) - - def create_netlist_wmask_dff(self): - """ Netlist creation """ - - start_time = datetime.datetime.now() - - # Must create the control logic before pins to get the pins - self.add_modules() - if self.write_size != self.word_size: - self.add_pin_wmask_dff() - self.wmask_dff_insts = self.create_wmask_dff() # be aware, multi-insts possible, or none - - # 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 wmask dff", datetime.datetime.now(), start_time) - else: - return False # no wmask_dff will be generated - - def create_netlist_spare_wen_dff(self): - """ Netlist creation """ - - start_time = datetime.datetime.now() - - # Must create the control logic before pins to get the pins - self.add_modules() - if self.num_spare_cols: - self.add_pin_spare_wen_dff() - self.spare_wen_dff_insts = self.create_spare_wen_dff() # be aware, multi-insts possible, or none - - # 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 spare wen dff", datetime.datetime.now(), start_time) - else: - self.num_spare_cols = 0 - return False # no spare_col will be generated - - 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_layout_bank_only(self): - start_time = datetime.datetime.now() - #Placement - self.place_bank(self.bank_inst, [0, 0], 1, 1) - if not OPTS.is_unit_test: - print_time("Bank Placement", datetime.datetime.now(), start_time) - start_time = datetime.datetime.now() - self.route_bank_only() - - 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_layout_control_only(self, instance_index=0): - start_time = datetime.datetime.now() - # Placement - # We will generate different layout for different port(if there are multi-port) - port = instance_index - self.control_logic_insts[port].place(vector(0,0)) - - if not OPTS.is_unit_test: - print_time("Control Placement", datetime.datetime.now(), start_time) - start_time = datetime.datetime.now() - self.route_control_only(instance_index=instance_index) - - 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_layout_row_addr_dff_only(self, instance_index=0): - start_time = datetime.datetime.now() - # Placement - # We will generate different layout for different port(if there are multi-port) - port = instance_index - self.row_addr_dff_insts[port].place(vector(0,0)) - - if not OPTS.is_unit_test: - print_time("row_addr_dff Placement", datetime.datetime.now(), start_time) - start_time = datetime.datetime.now() - self.route_row_addr_dff_only(instance_index=instance_index) - - 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_layout_col_addr_dff_only(self, instance_index=0): - start_time = datetime.datetime.now() - # Placement - # We will generate different layout for different port(if there are multi-port) - port = instance_index - self.col_addr_dff_insts[port].place(vector(0,0)) - - if not OPTS.is_unit_test: - print_time("col_addr_dff Placement", datetime.datetime.now(), start_time) - start_time = datetime.datetime.now() - self.route_col_addr_dff_only(instance_index=instance_index) - - 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_layout_data_dff_only(self, instance_index=0): - start_time = datetime.datetime.now() - # Placement - # We will generate different layout for different port(if there are multi-port) - port = instance_index - self.data_dff_insts[port].place(vector(0,0)) - - if not OPTS.is_unit_test: - print_time("data_dff Placement", datetime.datetime.now(), start_time) - start_time = datetime.datetime.now() - self.route_data_dff_only(instance_index=instance_index) - - 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_layout_wmask_dff_only(self, instance_index=0): - start_time = datetime.datetime.now() - # Placement - # We will generate different layout for different port(if there are multi-port) - port = instance_index - self.wmask_dff_insts[port].place(vector(0,0)) - - if not OPTS.is_unit_test: - print_time("wmask_dff Placement", datetime.datetime.now(), start_time) - start_time = datetime.datetime.now() - self.route_wmask_dff_only(instance_index=instance_index) - - 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_layout_spare_wen_dff_only(self, instance_index=0): - start_time = datetime.datetime.now() - # Placement - # We will generate different layout for different port(if there are multi-port) - port = instance_index - self.spare_wen_dff_insts[port].place(vector(0,0)) - - if not OPTS.is_unit_test: - print_time("spare_wen_dff Placement", datetime.datetime.now(), start_time) - start_time = datetime.datetime.now() - self.route_spare_wen_dff_only(instance_index=instance_index) - - 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 route_bank_only(self): - # We add the vias to M3 before routing supplies because - # they might create some blockages - self.add_layout_bank_only_pins() - print("====================================================") - for pin in self.pins: - print(pin) - - # Some technologies have an isolation - self.add_dnwell(inflate=2.5) - - def route_control_only(self, instance_index=0): - # We add the vias to M3 before routing supplies because - # they might create some blockages - self.add_layout_control_only_pins(instance_index=instance_index) - print("====================================================") - for pin in self.pins: - print(pin) - - # Some technologies have an isolation - self.add_dnwell(inflate=2.5) - - def route_row_addr_dff_only(self, instance_index=0): - # We add the vias to M3 before routing supplies because - # they might create some blockages - self.add_layout_row_addr_dff_only_pins(instance_index=instance_index) - print("====================================================") - for pin in self.pins: - print(pin) - - # Some technologies have an isolation - self.add_dnwell(inflate=2.5) - - def route_col_addr_dff_only(self, instance_index=0): - # We add the vias to M3 before routing supplies because - # they might create some blockages - self.add_layout_col_addr_dff_only_pins(instance_index=instance_index) - print("====================================================") - for pin in self.pins: - print(pin) - - # Some technologies have an isolation - self.add_dnwell(inflate=2.5) - - def route_data_dff_only(self, instance_index=0): - # We add the vias to M3 before routing supplies because - # they might create some blockages - self.add_layout_data_dff_only_pins(instance_index=instance_index) - print("====================================================") - for pin in self.pins: - print(pin) - - # Some technologies have an isolation - self.add_dnwell(inflate=2.5) - - def route_wmask_dff_only(self, instance_index=0): - # We add the vias to M3 before routing supplies because - # they might create some blockages - self.add_layout_wmask_dff_only_pins(instance_index=instance_index) - print("====================================================") - for pin in self.pins: - print(pin) - - # Some technologies have an isolation - self.add_dnwell(inflate=2.5) - - def route_spare_wen_dff_only(self, instance_index=0): - # We add the vias to M3 before routing supplies because - # they might create some blockages - self.add_layout_spare_wen_dff_only_pins(instance_index=instance_index) - print("====================================================") - for pin in self.pins: - print(pin) - - # Some technologies have an isolation - self.add_dnwell(inflate=2.5) - - def add_layout_spare_wen_dff_only_pins(self, add_vias=True, instance_index=0): - """ - Add the top-level pins for a single bank SRAM with control. - """ - port = instance_index - # Hack: If we are escape routing, set the pin layer to - # None so that we will start from the pin layer - # Otherwise, set it as the pin layer so that no vias are added. - # Otherwise, when we remove pins to move the dff array dynamically, - # we will leave some remaining vias when the pin locations change. - if add_vias: - pin_layer = None - else: - pin_layer = self.pwr_grid_layers[0] - - for bit in range(self.num_spare_cols): - if self.num_spare_cols == 1: - # input - self.add_io_pin(self.spare_wen_dff_insts[port], - "din_{}".format(bit), # old name - "spare_wen{}".format(port), # new name - start_layer=pin_layer) - # output - self.add_io_pin(self.spare_wen_dff_insts[port], - "dout_{}".format(bit), - "bank_spare_wen{}".format(port), - start_layer=pin_layer) - else: - # input - self.add_io_pin(self.spare_wen_dff_insts[port], - "din_{}".format(bit), # old name - "spare_wen{}[{}]".format(port, bit), # new name - start_layer=pin_layer) - # output - self.add_io_pin(self.spare_wen_dff_insts[port], - "dout_{}".format(bit), - "bank_spare_wen{}[{}]".format(port, bit), - start_layer=pin_layer) - #clk_buf, regard as input - self.add_io_pin(self.spare_wen_dff_insts[port], - "clk", - "clk_buf{}".format(port), - start_layer=pin_layer) - - def add_layout_wmask_dff_only_pins(self, add_vias=True, instance_index=0): - """ - Add the top-level pins for a single bank SRAM with control. - """ - port = instance_index - # Hack: If we are escape routing, set the pin layer to - # None so that we will start from the pin layer - # Otherwise, set it as the pin layer so that no vias are added. - # Otherwise, when we remove pins to move the dff array dynamically, - # we will leave some remaining vias when the pin locations change. - if add_vias: - pin_layer = None - else: - pin_layer = self.pwr_grid_layers[0] - - for bit in range(self.num_wmasks): - # input - self.add_io_pin(self.wmask_dff_insts[port], - "din_{}".format(bit), - "wmask{}[{}]".format(port, bit), - start_layer=pin_layer) - # output - self.add_io_pin(self.wmask_dff_insts[port], - "dout_{}".format(bit), - "bank_wmask{}[{}]".format(port, bit), - start_layer=pin_layer) - #clk_buf, regard as input - self.add_io_pin(self.wmask_dff_insts[port], - "clk", - "clk_buf{}".format(port), - start_layer=pin_layer) - - def add_layout_data_dff_only_pins(self, add_vias=True, instance_index=0): - """ - Add the top-level pins for a single bank SRAM with control. - """ - port = instance_index - # Hack: If we are escape routing, set the pin layer to - # None so that we will start from the pin layer - # Otherwise, set it as the pin layer so that no vias are added. - # Otherwise, when we remove pins to move the dff array dynamically, - # we will leave some remaining vias when the pin locations change. - if add_vias: - pin_layer = None - else: - pin_layer = self.pwr_grid_layers[0] - - for bit in range(self.word_size + self.num_spare_cols): - # input - self.add_io_pin(self.data_dff_insts[port], - "din_{}".format(bit), - "din{}[{}]".format(port, bit), - start_layer=pin_layer) - # output - self.add_io_pin(self.data_dff_insts[port], - "dout_{}".format(bit), - "bank_din{}[{}]".format(port, bit), - start_layer=pin_layer) - #clk_buf, regard as input - self.add_io_pin(self.data_dff_insts[port], - "clk", - "clk_buf{}".format(port), - start_layer=pin_layer) - - def add_layout_col_addr_dff_only_pins(self, add_vias=True, instance_index=0): - """ - Add the top-level pins for a single bank SRAM with control. - """ - port = instance_index - # Hack: If we are escape routing, set the pin layer to - # None so that we will start from the pin layer - # Otherwise, set it as the pin layer so that no vias are added. - # Otherwise, when we remove pins to move the dff array dynamically, - # we will leave some remaining vias when the pin locations change. - if add_vias: - pin_layer = None - else: - pin_layer = self.pwr_grid_layers[0] - - for bit in range(self.col_addr_size): - #input - self.add_io_pin(self.col_addr_dff_insts[port], - "din_{}".format(bit), # old name - "addr{}[{}]".format(port, bit), # new name - start_layer=pin_layer) - #output - self.add_io_pin(self.col_addr_dff_insts[port], - "dout_{}".format(bit), - "a{}[{}]".format(port, bit), - start_layer=pin_layer) - #clk_buf, regard as input - self.add_pin(self.col_addr_dff_insts[port], - "clk", - "clk_buf{}".format(port), - start_layer=pin_layer) - - def add_layout_row_addr_dff_only_pins(self, add_vias=True, instance_index=0): - """ - Add the top-level pins for a single bank SRAM with control. - """ - port = instance_index - # Hack: If we are escape routing, set the pin layer to - # None so that we will start from the pin layer - # Otherwise, set it as the pin layer so that no vias are added. - # Otherwise, when we remove pins to move the dff array dynamically, - # we will leave some remaining vias when the pin locations change. - if add_vias: - pin_layer = None - else: - pin_layer = self.pwr_grid_layers[0] - - for bit in range(self.row_addr_size): - #input - #debug - print("this is port number:{0}".format(port)) - print("insts:{0}".format(self.row_addr_dff_insts[port])) - self.add_io_pin(self.row_addr_dff_insts[port], - "din_{}".format(bit + self.col_addr_size), # old name - "addr{}[{}]".format(port, bit + self.col_addr_size), # new name - start_layer=pin_layer) - #output - self.add_io_pin(self.row_addr_dff_insts[port], - "dout_{}".format(bit + self.col_addr_size), - "a{}[{}]".format(port, bit + self.col_addr_size), - start_layer=pin_layer) - #clk_buf, regard as input - self.add_io_pin(self.row_addr_dff_insts[port], - "clk", - "clk_buf{}".format(port), - start_layer=pin_layer) - - def add_layout_control_only_pins(self, add_vias=True, instance_index=0): - """ - Add the top-level pins for a single bank SRAM with control. - """ - port = instance_index - # Hack: If we are escape routing, set the pin layer to - # None so that we will start from the pin layer - # Otherwise, set it as the pin layer so that no vias are added. - # Otherwise, when we remove pins to move the dff array dynamically, - # we will leave some remaining vias when the pin locations change. - if add_vias: - pin_layer = None - else: - pin_layer = self.pwr_grid_layers[0] - - # Inputs - self.add_io_pin(self.control_logic_insts[port], - "csb", #old name - "csb{}".format(port), #new name - start_layer=pin_layer) - if port in self.readwrite_ports: - self.add_io_pin(self.control_logic_insts[port], - "web", - "web{}".format(port), - start_layer=pin_layer) - self.add_io_pin(self.control_logic_insts[port], - "clk", - "clk{}".format(port), - start_layer=pin_layer) - if self.has_rbl: - self.add_io_pin(self.control_logic_insts[port], - "rbl_bl", - "rbl_bl{}".format(port), - start_layer=pin_layer) - # Outputs - if port in self.read_ports: - self.add_io_pin(self.control_logic_insts[port], - "s_en", - "s_en{}".format(port), - start_layer=pin_layer) - if port in self.write_ports: - self.add_io_pin(self.control_logic_insts[port], - "w_en", - "w_en{}".format(port), - start_layer=pin_layer) - self.add_io_pin(self.control_logic_insts[port], - "p_en_bar", - "p_en_bar{}".format(port), - start_layer=pin_layer) - self.add_io_pin(self.control_logic_insts[port], - "wl_en", - "wl_en{}".format(port), - start_layer=pin_layer) - self.add_io_pin(self.control_logic_insts[port], - "clk_buf", - "clk_buf{}".format(port), - start_layer=pin_layer) - - def add_layout_bank_only_pins(self, add_vias=True): - """ - Add the top-level pins for a single bank SRAM with control. - """ - # Hack: If we are escape routing, set the pin layer to - # None so that we will start from the pin layer - # Otherwise, set it as the pin layer so that no vias are added. - # Otherwise, when we remove pins to move the dff array dynamically, - # we will leave some remaining vias when the pin locations change. - if add_vias: - pin_layer = None - else: - pin_layer = self.pwr_grid_layers[0] - - for port in self.readwrite_ports or port in self.read_ports: - for bit in range(self.word_size + self.num_spare_cols): - self.add_io_pin(self.bank_inst, - "dout{0}_{1}".format(port, bit),#old name - "dout{0}[{1}]".format(port, bit),#new name - start_layer=pin_layer) - """ Adding pins for Bank module""" - for port in self.all_ports: - if self.has_rbl: - self.add_io_pin(self.bank_inst, - "rbl_bl_{0}_{0}".format(port), - "rbl_bl{0}".format(port), - start_layer=pin_layer) - for port in self.write_ports: - for bit in range(self.word_size + self.num_spare_cols): - self.add_io_pin(self.bank_inst, - "din{0}_{1}".format(port, bit), - "bank_din{0}[{1}]".format(port, bit), - start_layer=pin_layer) - # manuel change position, so not at same y - for port in self.all_ports: - for bit in range(self.bank_addr_size): - self.change_layout_pin_position(self.bank_inst, - "addr{0}_{1}".format(port, bit), - "addr{0}[{1}]".format(port, bit), - start_layer=pin_layer, - distance=bit) - for port in self.read_ports: - self.change_layout_pin_position(self.bank_inst, - "s_en{0}".format(port), - "s_en{0}".format(port), - start_layer=pin_layer, - distance=4) - for port in self.all_ports: - self.change_layout_pin_position(self.bank_inst, - "p_en_bar{0}".format(port), - "p_en_bar{0}".format(port), - start_layer=pin_layer, - distance=2) - for port in self.write_ports: - self.change_layout_pin_position(self.bank_inst, - "w_en{0}".format(port), - "w_en{0}".format(port), - start_layer=pin_layer) - - for bit in range(self.num_wmasks): - self.add_io_pin(self.bank_inst, - "bank_wmask{0}_{1}".format(port, bit), - "bank_wmask{0}[{1}]".format(port, bit), - start_layer=pin_layer) - for bit in range(self.num_spare_cols): - if self.num_spare_cols == 1: - self.add_io_pin(self.bank_inst, - "bank_spare_wen{0}_{1}".format(port, bit), - "bank_spare_wen{0}".format(port), - start_layer=pin_layer) - else: - self.add_io_pin(self.bank_inst, - "bank_spare_wen{0}_{1}".format(port, bit), - "bank_spare_wen{0}[{1}]".format(port, bit), - start_layer=pin_layer) - if port in self.all_ports: - self.add_io_pin(self.bank_inst, - "wl_en{0}".format(port), - "wl_en{0}".format(port), - start_layer=pin_layer) - - def change_layout_pin_position(self, instance, pin_name, new_name, start_layer=None, directions=None, distance=1): - """ - Add a signle input or output pin up to metal 3. - This additonal operation make sure pins are not at the same y - """ - pin = instance.get_pin(pin_name) - - if not start_layer: - start_layer = pin.layer - # Just use the power pin function for now to save code - self.add_power_pin(new_name, vector(pin.center()[0], pin.center()[1] + self.m3_pitch * distance * 2), start_layer=start_layer, directions=directions) - - 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]) - - from openram.router import supply_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.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=None): - """ - 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 openram.router import signal_escape_router as router - rtr = router(layers=self.m3_stack, - bbox=bbox, - design=self) - rtr.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_module("." + OPTS.control_logic, "openram.modules")) - 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)) - if self.has_rbl: - 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)) - if self.has_rbl: - 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): - """ - This adds the modules for a single bank SRAM with control - logic. - """ - - self.bank_inst = self.create_bank(0) - - self.control_logic_insts = self.create_control_logic() - - self.row_addr_dff_insts = self.create_row_addr_dff() - - if self.col_addr_dff: - self.col_addr_dff_insts = self.create_col_addr_dff() - - if self.write_size != self.word_size: - self.wmask_dff_insts = self.create_wmask_dff() - self.data_dff_insts = self.create_data_dff() - else: - self.data_dff_insts = self.create_data_dff() - - if self.num_spare_cols: - self.spare_wen_dff_insts = self.create_spare_wen_dff() - else: - self.num_spare_cols = 0 - - def place_instances(self): - """ - This places the instances for a single bank SRAM with control - logic and up to 2 ports. - """ - - # No orientation or offset - self.place_bank(self.bank_inst, [0, 0], 1, 1) - - # The control logic is placed such that the vertical center (between the delay/RBL and - # the actual control logic is aligned with the vertical center of the bank (between - # the sense amps/column mux and cell array) - # The x-coordinate is placed to allow a single clock wire (plus an extra pitch) - # up to the row address DFFs. - self.control_pos = [None] * len(self.all_ports) - self.row_addr_pos = [None] * len(self.all_ports) - - # DFFs are placd on their own - self.col_addr_pos = [None] * len(self.all_ports) - self.wmask_pos = [None] * len(self.all_ports) - self.spare_wen_pos = [None] * len(self.all_ports) - self.data_pos = [None] * len(self.all_ports) - - # These positions utilize the channel route sizes. - # FIXME: Auto-compute these rather than manual computation. - # If a horizontal channel, they rely on the vertical channel non-preferred (contacted) pitch. - # If a vertical channel, they rely on the horizontal channel non-preferred (contacted) pitch. - # So, m3 non-pref pitch means that this is routed on the m2 layer. - self.data_bus_gap = self.m4_nonpref_pitch * 2 - - # Spare wen are on a separate layer so not included - # Start with 1 track minimum - self.data_bus_size = [1] * len(self.all_ports) - self.col_addr_bus_size = [1] * len(self.all_ports) - for port in self.all_ports: - # The column address wires are routed separately from the data bus and will always be smaller. - # All ports need the col addr flops - self.col_addr_bus_size[port] = self.col_addr_size * self.m4_nonpref_pitch - # Write ports need the data input flops and write mask flops - if port in self.write_ports: - self.data_bus_size[port] += self.num_wmasks + self.word_size - # This is for the din pins that get routed in the same channel - # when we have dout and din together - if port in self.readwrite_ports: - self.data_bus_size[port] += self.word_size - # Convert to length - self.data_bus_size[port] *= self.m4_nonpref_pitch - # Add the gap in unit length - self.data_bus_size[port] += self.data_bus_gap - - # The control and row addr flops are independent of any bus widths. - self.place_control() - self.place_row_addr_dffs() - - # Place with an initial wide channel (from above) - self.place_dffs() - - # Route the channel and set to the new data bus size - # We need to temporarily add some pins for the x offsets - # but we'll remove them so that they have the right y - # offsets after the DFF placement. - self.add_layout_pins(add_vias=False) - self.route_dffs(add_routes=False) - self.remove_layout_pins() - - # Re-place with the new channel size - self.place_dffs() - - def place_row_addr_dffs(self): - """ - Must be run after place control logic. - """ - port = 0 - # The row address bits are placed above the control logic aligned on the right. - x_offset = self.control_logic_insts[port].rx() - self.row_addr_dff_insts[port].width - # It is above the control logic and the predecoder array - y_offset = max(self.control_logic_insts[port].uy(), self.bank.predecoder_top) - - self.row_addr_pos[port] = vector(x_offset, y_offset) - self.row_addr_dff_insts[port].place(self.row_addr_pos[port]) - - if len(self.all_ports)>1: - port = 1 - # The row address bits are placed above the control logic aligned on the left. - x_offset = self.control_pos[port].x - self.control_logic_insts[port].width + self.row_addr_dff_insts[port].width - # If it can be placed above the predecoder and below the control logic, do it - y_offset = self.bank.predecoder_bottom - self.row_addr_pos[port] = vector(x_offset, y_offset) - self.row_addr_dff_insts[port].place(self.row_addr_pos[port], mirror="XY") - - def place_control(self): - port = 0 - - # This includes 2 M2 pitches for the row addr clock line. - # The delay line is aligned with the bitcell array while the control logic is aligned with the port_data - # using the control_logic_center value. - self.control_pos[port] = vector(-self.control_logic_insts[port].width - 2 * self.m2_pitch, - self.bank.bank_array_ll.y - self.control_logic_insts[port].mod.control_logic_center.y) - self.control_logic_insts[port].place(self.control_pos[port]) - if len(self.all_ports) > 1: - port = 1 - # This includes 2 M2 pitches for the row addr clock line - # The delay line is aligned with the bitcell array while the control logic is aligned with the port_data - # using the control_logic_center value. - self.control_pos[port] = vector(self.bank_inst.rx() + self.control_logic_insts[port].width + 2 * self.m2_pitch, - self.bank.bank_array_ur.y - + self.control_logic_insts[port].height - - self.control_logic_insts[port].height - + self.control_logic_insts[port].mod.control_logic_center.y) - self.control_logic_insts[port].place(self.control_pos[port], mirror="XY") - - def place_dffs(self): - """ - Place the col addr, data, wmask, and spare data DFFs. - This can be run more than once after we recompute the channel width. - """ - - port = 0 - # Add the col address flops below the bank to the right of the control logic - x_offset = self.control_logic_insts[port].rx() + self.dff.width - # Place it a data bus below the x-axis, but at least as low as the control logic to not block - # the control logic signals - y_offset = min(-self.data_bus_size[port] - self.dff.height, - self.control_logic_insts[port].by()) - if self.col_addr_dff: - self.col_addr_pos[port] = vector(x_offset, - y_offset) - self.col_addr_dff_insts[port].place(self.col_addr_pos[port]) - x_offset = self.col_addr_dff_insts[port].rx() - else: - self.col_addr_pos[port] = vector(x_offset, 0) - - if port in self.write_ports: - 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) - self.wmask_dff_insts[port].place(self.wmask_pos[port]) - x_offset = self.wmask_dff_insts[port].rx() - - # Add the data flops below the write mask flops. - self.data_pos[port] = vector(x_offset, - y_offset) - self.data_dff_insts[port].place(self.data_pos[port]) - x_offset = self.data_dff_insts[port].rx() - - # Add spare write enable flops to the right of data flops since the spare columns - # will be on the right - if self.num_spare_cols: - self.spare_wen_pos[port] = vector(x_offset, - y_offset) - self.spare_wen_dff_insts[port].place(self.spare_wen_pos[port]) - x_offset = self.spare_wen_dff_insts[port].rx() - - else: - self.wmask_pos[port] = vector(x_offset, y_offset) - self.data_pos[port] = vector(x_offset, y_offset) - self.spare_wen_pos[port] = vector(x_offset, y_offset) - - if len(self.all_ports) > 1: - port = 1 - - # Add the col address flops below the bank to the right of the control logic - x_offset = self.control_logic_insts[port].lx() - 2 * self.dff.width - # Place it a data bus below the x-axis, but at least as high as the control logic to not block - # the control logic signals - y_offset = max(self.bank.height + self.data_bus_size[port] + self.dff.height, - self.control_logic_insts[port].uy() - self.dff.height) - if self.col_addr_dff: - self.col_addr_pos[port] = vector(x_offset, - y_offset) - self.col_addr_dff_insts[port].place(self.col_addr_pos[port], mirror="XY") - x_offset = self.col_addr_dff_insts[port].lx() - else: - self.col_addr_pos[port] = vector(x_offset, y_offset) - - if port in self.write_ports: - # Add spare write enable flops to the right of the data flops since the spare - # columns will be on the left - if self.num_spare_cols: - self.spare_wen_pos[port] = vector(x_offset - self.spare_wen_dff_insts[port].width, - y_offset) - 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 != 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) - self.wmask_dff_insts[port].place(self.wmask_pos[port], mirror="MX") - x_offset = self.wmask_dff_insts[port].lx() - - # Add the data flops below the write mask flops. - self.data_pos[port] = vector(x_offset - self.data_dff_insts[port].width, - y_offset) - self.data_dff_insts[port].place(self.data_pos[port], mirror="MX") - else: - self.wmask_pos[port] = vector(x_offset, y_offset) - self.data_pos[port] = vector(x_offset, y_offset) - self.spare_wen_pos[port] = vector(x_offset, y_offset) - - def add_layout_pins(self, add_vias=True): - """ - Add the top-level pins for a single bank SRAM with control. - """ - for port in self.all_ports: - # Hack: If we are escape routing, set the pin layer to - # None so that we will start from the pin layer - # Otherwise, set it as the pin layer so that no vias are added. - # Otherwise, when we remove pins to move the dff array dynamically, - # we will leave some remaining vias when the pin locations change. - if add_vias: - pin_layer = None - else: - pin_layer = self.pwr_grid_layers[0] - - # Connect the control pins as inputs - for signal in self.control_logic_inputs[port]: - if signal.startswith("rbl"): - continue - self.add_io_pin(self.control_logic_insts[port], - signal, - signal + "{}".format(port), - start_layer=pin_layer) - - if port in self.write_ports: - for bit in range(self.word_size + self.num_spare_cols): - self.add_io_pin(self.data_dff_insts[port], - "din_{}".format(bit), - "din{0}[{1}]".format(port, bit), - start_layer=pin_layer) - - if port in self.readwrite_ports or port in self.read_ports: - for bit in range(self.word_size + self.num_spare_cols): - self.add_io_pin(self.bank_inst, - "dout{0}_{1}".format(port, bit), - "dout{0}[{1}]".format(port, bit), - start_layer=pin_layer) - - for bit in range(self.col_addr_size): - self.add_io_pin(self.col_addr_dff_insts[port], - "din_{}".format(bit), - "addr{0}[{1}]".format(port, bit), - start_layer=pin_layer) - - for bit in range(self.row_addr_size): - self.add_io_pin(self.row_addr_dff_insts[port], - "din_{}".format(bit), - "addr{0}[{1}]".format(port, bit + self.col_addr_size), - start_layer=pin_layer) - - if port in self.write_ports: - 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), - "wmask{0}[{1}]".format(port, bit), - start_layer=pin_layer) - - if port in self.write_ports: - if self.num_spare_cols == 1: - self.add_io_pin(self.spare_wen_dff_insts[port], - "din_{}".format(0), - "spare_wen{0}".format(port), - start_layer=pin_layer) - else: - for bit in range(self.num_spare_cols): - self.add_io_pin(self.spare_wen_dff_insts[port], - "din_{}".format(bit), - "spare_wen{0}[{1}]".format(port, bit), - start_layer=pin_layer) - - def route_layout(self): - """ Route a single bank SRAM """ - - self.route_clk() - - self.route_control_logic() - - self.route_row_addr_dff() - - self.route_dffs() - - # We add the vias to M3 before routing supplies because - # they might create some blockages - self.add_layout_pins() - - # Some technologies have an isolation - self.add_dnwell(inflate=2.5) - - init_bbox = self.get_bbox() - # Route the supplies together and/or to the ring/stripes. - # Route the pins to the perimeter - # change the order - - if OPTS.perimeter_pins: - # We now route the escape routes far enough out so that they will - # reach past the power ring or stripes on the sides - self.route_escape_pins(init_bbox) - #if OPTS.route_supplies: - # self.route_supplies(init_bbox) - """ - if OPTS.route_supplies: - self.route_supplies(init_bbox) - if OPTS.perimeter_pins: - # We now route the escape routes far enough out so that they will - # reach past the power ring or stripes on the sides - self.route_escape_pins(init_bbox) - """ - - - def route_dffs(self, add_routes=True): - - for port in self.all_ports: - self.route_dff(port, add_routes) - - def route_dff(self, port, add_routes): - - # This is only done when we add_routes because the data channel will be larger - # so that can be used for area estimation. - if add_routes: - self.route_col_addr_dffs(port) - - self.route_data_dffs(port, add_routes) - - def route_col_addr_dffs(self, port): - - route_map = [] - - # column mux dff is routed on it's own since it is to the far end - # decoder inputs are min pitch M2, so need to use lower layer stack - if self.col_addr_size > 0: - dff_names = ["dout_{}".format(x) for x in range(self.col_addr_size)] - dff_pins = [self.col_addr_dff_insts[port].get_pin(x) for x in dff_names] - bank_names = ["addr{0}_{1}".format(port, x) for x in range(self.col_addr_size)] - bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] - route_map.extend(list(zip(bank_pins, dff_pins))) - - if len(route_map) > 0: - - # This layer stack must be different than the data dff layer stack - layer_stack = self.m1_stack - - if port == 0: - offset = vector(self.control_logic_insts[port].rx() + self.dff.width, - - self.data_bus_size[port] + 2 * self.m3_pitch) - cr = channel_route(netlist=route_map, - offset=offset, - layer_stack=layer_stack, - parent=self) - # This causes problem in magic since it sometimes cannot extract connectivity of instances - # with no active devices. - self.add_inst(cr.name, cr) - self.connect_inst([]) - # self.add_flat_inst(cr.name, cr) - else: - offset = vector(0, - self.bank.height + self.m3_pitch) - cr = channel_route(netlist=route_map, - offset=offset, - layer_stack=layer_stack, - parent=self) - # This causes problem in magic since it sometimes cannot extract connectivity of instances - # with no active devices. - self.add_inst(cr.name, cr) - self.connect_inst([]) - # self.add_flat_inst(cr.name, cr) - - def route_data_dffs(self, port, add_routes): - route_map = [] - - # wmask dff - if self.num_wmasks > 0 and port in self.write_ports: - dff_names = ["dout_{}".format(x) for x in range(self.num_wmasks)] - dff_pins = [self.wmask_dff_insts[port].get_pin(x) for x in dff_names] - bank_names = ["bank_wmask{0}_{1}".format(port, x) for x in range(self.num_wmasks)] - bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] - route_map.extend(list(zip(bank_pins, dff_pins))) - - if port in self.write_ports: - # synchronized inputs from data dff - dff_names = ["dout_{}".format(x) for x in range(self.word_size + self.num_spare_cols)] - dff_pins = [self.data_dff_insts[port].get_pin(x) for x in dff_names] - bank_names = ["din{0}_{1}".format(port, x) for x in range(self.word_size + self.num_spare_cols)] - bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] - route_map.extend(list(zip(bank_pins, dff_pins))) - - # spare wen dff - if self.num_spare_cols > 0 and port in self.write_ports: - dff_names = ["dout_{}".format(x) for x in range(self.num_spare_cols)] - dff_pins = [self.spare_wen_dff_insts[port].get_pin(x) for x in dff_names] - bank_names = ["bank_spare_wen{0}_{1}".format(port, x) for x in range(self.num_spare_cols)] - bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] - route_map.extend(list(zip(bank_pins, dff_pins))) - - if len(route_map) > 0: - - # This layer stack must be different than the column addr dff layer stack - layer_stack = self.m3_stack - if port == 0: - # This is relative to the bank at 0,0 or the s_en which is routed on M3 also - if "s_en" in self.control_logic_insts[port].mod.pin_map: - y_bottom = min(0, self.control_logic_insts[port].get_pin("s_en").by()) - else: - y_bottom = 0 - - y_offset = y_bottom - self.data_bus_size[port] + 2 * self.m3_pitch - offset = vector(self.control_logic_insts[port].rx() + self.dff.width, - y_offset) - cr = channel_route(netlist=route_map, - offset=offset, - layer_stack=layer_stack, - parent=self) - if add_routes: - # This causes problem in magic since it sometimes cannot extract connectivity of instances - # with no active devices. - self.add_inst(cr.name, cr) - self.connect_inst([]) - # self.add_flat_inst(cr.name, cr) - else: - self.data_bus_size[port] = max(cr.height, self.col_addr_bus_size[port]) + self.data_bus_gap - else: - if "s_en" in self.control_logic_insts[port].mod.pin_map: - y_top = max(self.bank.height, self.control_logic_insts[port].get_pin("s_en").uy()) - else: - y_top = self.bank.height - y_offset = y_top + self.m3_pitch - offset = vector(0, - y_offset) - cr = channel_route(netlist=route_map, - offset=offset, - layer_stack=layer_stack, - parent=self) - if add_routes: - # This causes problem in magic since it sometimes cannot extract connectivity of instances - # with no active devices. - self.add_inst(cr.name, cr) - self.connect_inst([]) - # self.add_flat_inst(cr.name, cr) - else: - self.data_bus_size[port] = max(cr.height, self.col_addr_bus_size[port]) + self.data_bus_gap - - def route_clk(self): - """ Route the clock network """ - - # This is the actual input to the SRAM - for port in self.all_ports: - # Connect all of these clock pins to the clock in the central bus - # This is something like a "spine" clock distribution. The two spines - # are clk_buf and clk_buf_bar - control_clk_buf_pin = self.control_logic_insts[port].get_pin("clk_buf") - control_clk_buf_pos = control_clk_buf_pin.center() - - # This uses a metal2 track to the right (for port0) of the control/row addr DFF - # to route vertically. For port1, it is to the left. - row_addr_clk_pin = self.row_addr_dff_insts[port].get_pin("clk") - if port % 2: - control_clk_buf_pos = control_clk_buf_pin.lc() - row_addr_clk_pos = row_addr_clk_pin.lc() - mid1_pos = vector(self.row_addr_dff_insts[port].lx() - self.m2_pitch, - row_addr_clk_pos.y) - else: - control_clk_buf_pos = control_clk_buf_pin.rc() - row_addr_clk_pos = row_addr_clk_pin.rc() - mid1_pos = vector(self.row_addr_dff_insts[port].rx() + self.m2_pitch, - row_addr_clk_pos.y) - - # This is the steiner point where the net branches out - clk_steiner_pos = vector(mid1_pos.x, control_clk_buf_pos.y) - self.add_path(control_clk_buf_pin.layer, [control_clk_buf_pos, clk_steiner_pos]) - self.add_via_stack_center(from_layer=control_clk_buf_pin.layer, - to_layer="m2", - offset=clk_steiner_pos) - - # Note, the via to the control logic is taken care of above - self.add_wire(self.m2_stack[::-1], - [row_addr_clk_pos, mid1_pos, clk_steiner_pos]) - - if self.col_addr_dff: - dff_clk_pin = self.col_addr_dff_insts[port].get_pin("clk") - dff_clk_pos = dff_clk_pin.center() - mid_pos = vector(clk_steiner_pos.x, dff_clk_pos.y) - self.add_wire(self.m2_stack[::-1], - [dff_clk_pos, mid_pos, clk_steiner_pos]) - elif port in self.write_ports: - data_dff_clk_pin = self.data_dff_insts[port].get_pin("clk") - data_dff_clk_pos = data_dff_clk_pin.center() - mid_pos = vector(clk_steiner_pos.x, data_dff_clk_pos.y) - # In some designs, the steiner via will be too close to the mid_pos via - # so make the wire as wide as the contacts - self.add_path("m2", - [mid_pos, clk_steiner_pos], - width=max(self.m2_via.width, self.m2_via.height)) - self.add_wire(self.m2_stack[::-1], - [data_dff_clk_pos, mid_pos, clk_steiner_pos]) - - def route_control_logic(self): - """ - Route the control logic pins that are not inputs - """ - - for port in self.all_ports: - for signal in self.control_logic_outputs[port]: - # The clock gets routed separately and is not a part of the bank - if "clk" in signal: - continue - src_pin = self.control_logic_insts[port].get_pin(signal) - dest_pin = self.bank_inst.get_pin(signal + "{}".format(port)) - self.connect_vbus(src_pin, dest_pin) - - if self.has_rbl: - for port in self.all_ports: - # Only input (besides pins) is the replica bitline - src_pin = self.control_logic_insts[port].get_pin("rbl_bl") - dest_pin = self.bank_inst.get_pin("rbl_bl_{0}_{0}".format(port)) - self.add_wire(self.m3_stack, - [src_pin.center(), vector(src_pin.cx(), dest_pin.cy()), dest_pin.rc()]) - self.add_via_stack_center(from_layer=src_pin.layer, - to_layer="m4", - offset=src_pin.center()) - self.add_via_stack_center(from_layer=dest_pin.layer, - to_layer="m3", - offset=dest_pin.center()) - - def route_row_addr_dff(self): - """ - Connect the output of the row flops to the bank pins - """ - for port in self.all_ports: - for bit in range(self.row_addr_size): - flop_name = "dout_{}".format(bit) - bank_name = "addr{0}_{1}".format(port, bit + self.col_addr_size) - flop_pin = self.row_addr_dff_insts[port].get_pin(flop_name) - bank_pin = self.bank_inst.get_pin(bank_name) - flop_pos = flop_pin.center() - bank_pos = bank_pin.center() - mid_pos = vector(bank_pos.x, flop_pos.y) - self.add_via_stack_center(from_layer=flop_pin.layer, - to_layer="m3", - offset=flop_pos) - self.add_path("m3", [flop_pos, mid_pos]) - self.add_via_stack_center(from_layer=bank_pin.layer, - to_layer="m3", - offset=mid_pos) - self.add_path(bank_pin.layer, [mid_pos, bank_pos]) - - 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. - """ - return - for n in self.control_logic_outputs[0]: - pin = self.control_logic_insts[0].get_pin(n) - self.add_label(text=n, - layer=pin.layer, - offset=pin.center()) - - def graph_exclude_data_dff(self): - """ - Removes data dff and wmask dff (if applicable) from search graph. - """ - # 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 != self.word_size: - for inst in self.wmask_dff_insts: - self.graph_inst_exclude.add(inst) - if self.num_spare_cols: - for inst in self.spare_wen_dff_insts: - self.graph_inst_exclude.add(inst) - - def graph_exclude_addr_dff(self): - """ - Removes data dff from search graph. - """ - # Address is considered not part of the critical path, subjectively removed - for inst in self.row_addr_dff_insts: - self.graph_inst_exclude.add(inst) - - if self.col_addr_dff: - for inst in self.col_addr_dff_insts: - self.graph_inst_exclude.add(inst) - - def graph_exclude_ctrl_dffs(self): - """ - Exclude dffs for CSB, WEB, etc from graph - """ - # Insts located in control logic, exclusion function called here - for inst in self.control_logic_insts: - inst.mod.graph_exclude_dffs() - - def get_cell_name(self, inst_name, row, col): - """ - Gets the spice name of the target bitcell. - """ - # Sanity check in case it was forgotten - if inst_name.find("x") != 0: - inst_name = "x" + inst_name - return self.bank_inst.mod.get_cell_name(inst_name + "{}x".format(OPTS.hier_seperator) + self.bank_inst.name, row, col) - - def get_bank_num(self, inst_name, row, col): - return 0 diff --git a/compiler/modules/sram_openroad.py b/compiler/modules/sram_openroad.py deleted file mode 100644 index 514a042b9..000000000 --- a/compiler/modules/sram_openroad.py +++ /dev/null @@ -1,1485 +0,0 @@ -# See LICENSE for licensing information. -# -# Copyright (c) 2016-2024 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. -# -# This file aiming to create the sram module without routing -# provide methods of seperated lef file generation -import datetime -from math import ceil -from importlib import import_module, reload -from openram import debug -from openram.base import vector -from openram.base import channel_route -from openram.base import design -from openram.base import verilog -from openram.base import lef -from openram.sram_factory import factory -from openram.tech import spice -from openram import OPTS, print_time - - -class sram_1bank(design, verilog, lef): - """ - Procedures specific to a one bank SRAM. - """ - def __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 openram.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 - - # delay control logic does not have RBLs - self.has_rbl = OPTS.control_logic != "control_logic_delay" - - 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 place_io_pins(self, bbox): - """Place IO pins""" - # 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 openram.router.io_pin_placer import io_pin_placer as placer - pl = placer(layers=self.m3_stack, - bbox=bbox, - design=self) - pl.add_io_pins(pins_to_route) - #pl.design.remove_layout_pin("wmask0[0]") # this could use for deleting pins - - def get_closest_edge(self, point, bbox): - """ Return a point's the closest edge and the edge's axis direction. """ - - ll, ur = bbox - - # Snap the pin to the perimeter and break the iteration - ll_diff_x = abs(point.x - ll.x) - ll_diff_y = abs(point.y - ll.y) - ur_diff_x = abs(point.x - ur.x) - ur_diff_y = abs(point.y - ur.y) - min_diff = min(ll_diff_x, ll_diff_y, ur_diff_x, ur_diff_y) - - if min_diff == ll_diff_x: - return "left", True - if min_diff == ll_diff_y: - return "bottom", False - if min_diff == ur_diff_x: - return "right", True - return "top", False - - def create_layout_openroad(self): - # only placement of submoduels & io pins - # not routed inner-signal/escape-signal/pdn - start_time = datetime.datetime.now() - self.place_instances() - if not OPTS.is_unit_test: - print_time("Placement", datetime.datetime.now(), start_time) - - # Some technologies have an isolation - self.add_dnwell(inflate=2.5) - # add IO pins - init_bbox = self.get_bbox() - - self.add_layout_pins() - self.place_io_pins(bbox=init_bbox) - - # add power rings - # use current supply router - if OPTS.route_supplies: - self.route_supplies(init_bbox) - - 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_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]) - - from openram.router import supply_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.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=None): - """ - 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 openram.router import signal_escape_router as router - rtr = router(layers=self.m3_stack, - bbox=bbox, - design=self) - rtr.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_module("." + OPTS.control_logic, "openram.modules")) - 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)) - if self.has_rbl: - 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)) - if self.has_rbl: - 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): - """ - This adds the modules for a single bank SRAM with control - logic. - """ - - self.bank_inst=self.create_bank(0) - - self.control_logic_insts = self.create_control_logic() - - self.row_addr_dff_insts = self.create_row_addr_dff() - - if self.col_addr_dff: - self.col_addr_dff_insts = self.create_col_addr_dff() - - if self.write_size != self.word_size: - self.wmask_dff_insts = self.create_wmask_dff() - self.data_dff_insts = self.create_data_dff() - else: - self.data_dff_insts = self.create_data_dff() - - if self.num_spare_cols: - self.spare_wen_dff_insts = self.create_spare_wen_dff() - else: - self.num_spare_cols = 0 - - def place_instances(self): - """ - This places the instances for a single bank SRAM with control - logic and up to 2 ports. - """ - - # No orientation or offset - self.place_bank(self.bank_inst, [0, 0], 1, 1) - - # The control logic is placed such that the vertical center (between the delay/RBL and - # the actual control logic is aligned with the vertical center of the bank (between - # the sense amps/column mux and cell array) - # The x-coordinate is placed to allow a single clock wire (plus an extra pitch) - # up to the row address DFFs. - self.control_pos = [None] * len(self.all_ports) - self.row_addr_pos = [None] * len(self.all_ports) - - # DFFs are placd on their own - self.col_addr_pos = [None] * len(self.all_ports) - self.wmask_pos = [None] * len(self.all_ports) - self.spare_wen_pos = [None] * len(self.all_ports) - self.data_pos = [None] * len(self.all_ports) - - # These positions utilize the channel route sizes. - # FIXME: Auto-compute these rather than manual computation. - # If a horizontal channel, they rely on the vertical channel non-preferred (contacted) pitch. - # If a vertical channel, they rely on the horizontal channel non-preferred (contacted) pitch. - # So, m3 non-pref pitch means that this is routed on the m2 layer. - self.data_bus_gap = self.m4_nonpref_pitch * 2 - - # Spare wen are on a separate layer so not included - # Start with 1 track minimum - self.data_bus_size = [1] * len(self.all_ports) - self.col_addr_bus_size = [1] * len(self.all_ports) - for port in self.all_ports: - # The column address wires are routed separately from the data bus and will always be smaller. - # All ports need the col addr flops - self.col_addr_bus_size[port] = self.col_addr_size * self.m4_nonpref_pitch - # Write ports need the data input flops and write mask flops - if port in self.write_ports: - self.data_bus_size[port] += self.num_wmasks + self.word_size - # This is for the din pins that get routed in the same channel - # when we have dout and din together - if port in self.readwrite_ports: - self.data_bus_size[port] += self.word_size - # Convert to length - self.data_bus_size[port] *= self.m4_nonpref_pitch - # Add the gap in unit length - self.data_bus_size[port] += self.data_bus_gap - - # The control and row addr flops are independent of any bus widths. - self.place_control() - self.place_row_addr_dffs() - - # Place with an initial wide channel (from above) - self.place_dffs() - - # Route the channel and set to the new data bus size - # We need to temporarily add some pins for the x offsets - # but we'll remove them so that they have the right y - # offsets after the DFF placement. - self.add_layout_pins(add_vias=False) - self.route_dffs(add_routes=False) - self.remove_layout_pins() - - # Re-place with the new channel size - self.place_dffs() - - def place_row_addr_dffs(self): - """ - Must be run after place control logic. - """ - port = 0 - # The row address bits are placed above the control logic aligned on the right. - x_offset = self.control_logic_insts[port].rx() - self.row_addr_dff_insts[port].width - # It is above the control logic and the predecoder array - y_offset = max(self.control_logic_insts[port].uy(), self.bank.predecoder_top) - - self.row_addr_pos[port] = vector(x_offset, y_offset) - self.row_addr_dff_insts[port].place(self.row_addr_pos[port]) - - if len(self.all_ports)>1: - port = 1 - # The row address bits are placed above the control logic aligned on the left. - x_offset = self.control_pos[port].x - self.control_logic_insts[port].width + self.row_addr_dff_insts[port].width - # If it can be placed above the predecoder and below the control logic, do it - y_offset = self.bank.predecoder_bottom - self.row_addr_pos[port] = vector(x_offset, y_offset) - self.row_addr_dff_insts[port].place(self.row_addr_pos[port], mirror="XY") - - def place_control(self): - port = 0 - - # This includes 2 M2 pitches for the row addr clock line. - # The delay line is aligned with the bitcell array while the control logic is aligned with the port_data - # using the control_logic_center value. - self.control_pos[port] = vector(-self.control_logic_insts[port].width - 2 * self.m2_pitch, - self.bank.bank_array_ll.y - self.control_logic_insts[port].mod.control_logic_center.y) - self.control_logic_insts[port].place(self.control_pos[port]) - if len(self.all_ports) > 1: - port = 1 - # This includes 2 M2 pitches for the row addr clock line - # The delay line is aligned with the bitcell array while the control logic is aligned with the port_data - # using the control_logic_center value. - self.control_pos[port] = vector(self.bank_inst.rx() + self.control_logic_insts[port].width + 2 * self.m2_pitch, - self.bank.bank_array_ur.y - + self.control_logic_insts[port].height - - self.control_logic_insts[port].height - + self.control_logic_insts[port].mod.control_logic_center.y) - self.control_logic_insts[port].place(self.control_pos[port], mirror="XY") - - def place_dffs(self): - """ - Place the col addr, data, wmask, and spare data DFFs. - This can be run more than once after we recompute the channel width. - """ - - port = 0 - # Add the col address flops below the bank to the right of the control logic - x_offset = self.control_logic_insts[port].rx() + self.dff.width - # Place it a data bus below the x-axis, but at least as low as the control logic to not block - # the control logic signals - y_offset = min(-self.data_bus_size[port] - self.dff.height, - self.control_logic_insts[port].by()) - if self.col_addr_dff: - self.col_addr_pos[port] = vector(x_offset, - y_offset) - self.col_addr_dff_insts[port].place(self.col_addr_pos[port]) - x_offset = self.col_addr_dff_insts[port].rx() - else: - self.col_addr_pos[port] = vector(x_offset, 0) - - if port in self.write_ports: - 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) - self.wmask_dff_insts[port].place(self.wmask_pos[port]) - x_offset = self.wmask_dff_insts[port].rx() - - # Add the data flops below the write mask flops. - self.data_pos[port] = vector(x_offset, - y_offset) - self.data_dff_insts[port].place(self.data_pos[port]) - x_offset = self.data_dff_insts[port].rx() - - # Add spare write enable flops to the right of data flops since the spare columns - # will be on the right - if self.num_spare_cols: - self.spare_wen_pos[port] = vector(x_offset, - y_offset) - self.spare_wen_dff_insts[port].place(self.spare_wen_pos[port]) - x_offset = self.spare_wen_dff_insts[port].rx() - - else: - self.wmask_pos[port] = vector(x_offset, y_offset) - self.data_pos[port] = vector(x_offset, y_offset) - self.spare_wen_pos[port] = vector(x_offset, y_offset) - - if len(self.all_ports) > 1: - port = 1 - - # Add the col address flops below the bank to the right of the control logic - x_offset = self.control_logic_insts[port].lx() - 2 * self.dff.width - # Place it a data bus below the x-axis, but at least as high as the control logic to not block - # the control logic signals - y_offset = max(self.bank.height + self.data_bus_size[port] + self.dff.height, - self.control_logic_insts[port].uy() - self.dff.height) - if self.col_addr_dff: - self.col_addr_pos[port] = vector(x_offset, - y_offset) - self.col_addr_dff_insts[port].place(self.col_addr_pos[port], mirror="XY") - x_offset = self.col_addr_dff_insts[port].lx() - else: - self.col_addr_pos[port] = vector(x_offset, y_offset) - - if port in self.write_ports: - # Add spare write enable flops to the right of the data flops since the spare - # columns will be on the left - if self.num_spare_cols: - self.spare_wen_pos[port] = vector(x_offset - self.spare_wen_dff_insts[port].width, - y_offset) - 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 != 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) - self.wmask_dff_insts[port].place(self.wmask_pos[port], mirror="MX") - x_offset = self.wmask_dff_insts[port].lx() - - # Add the data flops below the write mask flops. - self.data_pos[port] = vector(x_offset - self.data_dff_insts[port].width, - y_offset) - self.data_dff_insts[port].place(self.data_pos[port], mirror="MX") - else: - self.wmask_pos[port] = vector(x_offset, y_offset) - self.data_pos[port] = vector(x_offset, y_offset) - self.spare_wen_pos[port] = vector(x_offset, y_offset) - - def add_layout_pins(self, add_vias=True): - """ - Add the top-level pins for a single bank SRAM with control. - """ - for port in self.all_ports: - # Hack: If we are escape routing, set the pin layer to - # None so that we will start from the pin layer - # Otherwise, set it as the pin layer so that no vias are added. - # Otherwise, when we remove pins to move the dff array dynamically, - # we will leave some remaining vias when the pin locations change. - if add_vias: - pin_layer = None - else: - pin_layer = self.pwr_grid_layers[0] - - # Connect the control pins as inputs - for signal in self.control_logic_inputs[port]: - if signal.startswith("rbl"): - continue - self.add_io_pin(self.control_logic_insts[port], - signal, - signal + "{}".format(port), - start_layer=pin_layer) - - if port in self.write_ports: - for bit in range(self.word_size + self.num_spare_cols): - self.add_io_pin(self.data_dff_insts[port], - "din_{}".format(bit), - "din{0}[{1}]".format(port, bit), - start_layer=pin_layer) - - if port in self.readwrite_ports or port in self.read_ports: - for bit in range(self.word_size + self.num_spare_cols): - self.add_io_pin(self.bank_inst, - "dout{0}_{1}".format(port, bit), - "dout{0}[{1}]".format(port, bit), - start_layer=pin_layer) - - for bit in range(self.col_addr_size): - self.add_io_pin(self.col_addr_dff_insts[port], - "din_{}".format(bit), - "addr{0}[{1}]".format(port, bit), - start_layer=pin_layer) - - for bit in range(self.row_addr_size): - self.add_io_pin(self.row_addr_dff_insts[port], - "din_{}".format(bit), - "addr{0}[{1}]".format(port, bit + self.col_addr_size), - start_layer=pin_layer) - - if port in self.write_ports: - 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), - "wmask{0}[{1}]".format(port, bit), - start_layer=pin_layer) - - if port in self.write_ports: - if self.num_spare_cols == 1: - self.add_io_pin(self.spare_wen_dff_insts[port], - "din_{}".format(0), - "spare_wen{0}".format(port), - start_layer=pin_layer) - else: - for bit in range(self.num_spare_cols): - self.add_io_pin(self.spare_wen_dff_insts[port], - "din_{}".format(bit), - "spare_wen{0}[{1}]".format(port, bit), - start_layer=pin_layer) - - def route_layout(self): - """ Route a single bank SRAM """ - - self.route_clk() - - self.route_control_logic() - - self.route_row_addr_dff() - - self.route_dffs() - - # We add the vias to M3 before routing supplies because - # they might create some blockages - self.add_layout_pins() - - # Some technologies have an isolation - self.add_dnwell(inflate=2.5) - - init_bbox = self.get_bbox() - # Route the supplies together and/or to the ring/stripes. - # Route the pins to the perimeter - if OPTS.perimeter_pins: - # We now route the escape routes far enough out so that they will - # reach past the power ring or stripes on the sides - self.route_escape_pins(init_bbox) - if OPTS.route_supplies: - self.route_supplies(init_bbox) - - - def route_dffs(self, add_routes=True): - - for port in self.all_ports: - self.route_dff(port, add_routes) - - def route_dff(self, port, add_routes): - - # This is only done when we add_routes because the data channel will be larger - # so that can be used for area estimation. - if add_routes: - self.route_col_addr_dffs(port) - - self.route_data_dffs(port, add_routes) - - def route_col_addr_dffs(self, port): - - route_map = [] - - # column mux dff is routed on it's own since it is to the far end - # decoder inputs are min pitch M2, so need to use lower layer stack - if self.col_addr_size > 0: - dff_names = ["dout_{}".format(x) for x in range(self.col_addr_size)] - dff_pins = [self.col_addr_dff_insts[port].get_pin(x) for x in dff_names] - bank_names = ["addr{0}_{1}".format(port, x) for x in range(self.col_addr_size)] - bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] - route_map.extend(list(zip(bank_pins, dff_pins))) - - if len(route_map) > 0: - - # This layer stack must be different than the data dff layer stack - layer_stack = self.m1_stack - - if port == 0: - offset = vector(self.control_logic_insts[port].rx() + self.dff.width, - - self.data_bus_size[port] + 2 * self.m3_pitch) - cr = channel_route(netlist=route_map, - offset=offset, - layer_stack=layer_stack, - parent=self) - # This causes problem in magic since it sometimes cannot extract connectivity of instances - # with no active devices. - self.add_inst(cr.name, cr) - self.connect_inst([]) - # self.add_flat_inst(cr.name, cr) - else: - offset = vector(0, - self.bank.height + self.m3_pitch) - cr = channel_route(netlist=route_map, - offset=offset, - layer_stack=layer_stack, - parent=self) - # This causes problem in magic since it sometimes cannot extract connectivity of instances - # with no active devices. - self.add_inst(cr.name, cr) - self.connect_inst([]) - # self.add_flat_inst(cr.name, cr) - - def route_data_dffs(self, port, add_routes): - route_map = [] - - # wmask dff - if self.num_wmasks > 0 and port in self.write_ports: - dff_names = ["dout_{}".format(x) for x in range(self.num_wmasks)] - dff_pins = [self.wmask_dff_insts[port].get_pin(x) for x in dff_names] - bank_names = ["bank_wmask{0}_{1}".format(port, x) for x in range(self.num_wmasks)] - bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] - route_map.extend(list(zip(bank_pins, dff_pins))) - - if port in self.write_ports: - # synchronized inputs from data dff - dff_names = ["dout_{}".format(x) for x in range(self.word_size + self.num_spare_cols)] - dff_pins = [self.data_dff_insts[port].get_pin(x) for x in dff_names] - bank_names = ["din{0}_{1}".format(port, x) for x in range(self.word_size + self.num_spare_cols)] - bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] - route_map.extend(list(zip(bank_pins, dff_pins))) - - # spare wen dff - if self.num_spare_cols > 0 and port in self.write_ports: - dff_names = ["dout_{}".format(x) for x in range(self.num_spare_cols)] - dff_pins = [self.spare_wen_dff_insts[port].get_pin(x) for x in dff_names] - bank_names = ["bank_spare_wen{0}_{1}".format(port, x) for x in range(self.num_spare_cols)] - bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] - route_map.extend(list(zip(bank_pins, dff_pins))) - - if len(route_map) > 0: - - # This layer stack must be different than the column addr dff layer stack - layer_stack = self.m3_stack - if port == 0: - # This is relative to the bank at 0,0 or the s_en which is routed on M3 also - if "s_en" in self.control_logic_insts[port].mod.pin_map: - y_bottom = min(0, self.control_logic_insts[port].get_pin("s_en").by()) - else: - y_bottom = 0 - - y_offset = y_bottom - self.data_bus_size[port] + 2 * self.m3_pitch - offset = vector(self.control_logic_insts[port].rx() + self.dff.width, - y_offset) - cr = channel_route(netlist=route_map, - offset=offset, - layer_stack=layer_stack, - parent=self) - if add_routes: - # This causes problem in magic since it sometimes cannot extract connectivity of instances - # with no active devices. - self.add_inst(cr.name, cr) - self.connect_inst([]) - # self.add_flat_inst(cr.name, cr) - else: - self.data_bus_size[port] = max(cr.height, self.col_addr_bus_size[port]) + self.data_bus_gap - else: - if "s_en" in self.control_logic_insts[port].mod.pin_map: - y_top = max(self.bank.height, self.control_logic_insts[port].get_pin("s_en").uy()) - else: - y_top = self.bank.height - y_offset = y_top + self.m3_pitch - offset = vector(0, - y_offset) - cr = channel_route(netlist=route_map, - offset=offset, - layer_stack=layer_stack, - parent=self) - if add_routes: - # This causes problem in magic since it sometimes cannot extract connectivity of instances - # with no active devices. - self.add_inst(cr.name, cr) - self.connect_inst([]) - # self.add_flat_inst(cr.name, cr) - else: - self.data_bus_size[port] = max(cr.height, self.col_addr_bus_size[port]) + self.data_bus_gap - - def route_clk(self): - """ Route the clock network """ - - # This is the actual input to the SRAM - for port in self.all_ports: - # Connect all of these clock pins to the clock in the central bus - # This is something like a "spine" clock distribution. The two spines - # are clk_buf and clk_buf_bar - control_clk_buf_pin = self.control_logic_insts[port].get_pin("clk_buf") - control_clk_buf_pos = control_clk_buf_pin.center() - - # This uses a metal2 track to the right (for port0) of the control/row addr DFF - # to route vertically. For port1, it is to the left. - row_addr_clk_pin = self.row_addr_dff_insts[port].get_pin("clk") - if port % 2: - control_clk_buf_pos = control_clk_buf_pin.lc() - row_addr_clk_pos = row_addr_clk_pin.lc() - mid1_pos = vector(self.row_addr_dff_insts[port].lx() - self.m2_pitch, - row_addr_clk_pos.y) - else: - control_clk_buf_pos = control_clk_buf_pin.rc() - row_addr_clk_pos = row_addr_clk_pin.rc() - mid1_pos = vector(self.row_addr_dff_insts[port].rx() + self.m2_pitch, - row_addr_clk_pos.y) - - # This is the steiner point where the net branches out - clk_steiner_pos = vector(mid1_pos.x, control_clk_buf_pos.y) - self.add_path(control_clk_buf_pin.layer, [control_clk_buf_pos, clk_steiner_pos]) - self.add_via_stack_center(from_layer=control_clk_buf_pin.layer, - to_layer="m2", - offset=clk_steiner_pos) - - # Note, the via to the control logic is taken care of above - self.add_wire(self.m2_stack[::-1], - [row_addr_clk_pos, mid1_pos, clk_steiner_pos]) - - if self.col_addr_dff: - dff_clk_pin = self.col_addr_dff_insts[port].get_pin("clk") - dff_clk_pos = dff_clk_pin.center() - mid_pos = vector(clk_steiner_pos.x, dff_clk_pos.y) - self.add_wire(self.m2_stack[::-1], - [dff_clk_pos, mid_pos, clk_steiner_pos]) - elif port in self.write_ports: - data_dff_clk_pin = self.data_dff_insts[port].get_pin("clk") - data_dff_clk_pos = data_dff_clk_pin.center() - mid_pos = vector(clk_steiner_pos.x, data_dff_clk_pos.y) - # In some designs, the steiner via will be too close to the mid_pos via - # so make the wire as wide as the contacts - self.add_path("m2", - [mid_pos, clk_steiner_pos], - width=max(self.m2_via.width, self.m2_via.height)) - self.add_wire(self.m2_stack[::-1], - [data_dff_clk_pos, mid_pos, clk_steiner_pos]) - - def route_control_logic(self): - """ - Route the control logic pins that are not inputs - """ - - for port in self.all_ports: - for signal in self.control_logic_outputs[port]: - # The clock gets routed separately and is not a part of the bank - if "clk" in signal: - continue - src_pin = self.control_logic_insts[port].get_pin(signal) - dest_pin = self.bank_inst.get_pin(signal + "{}".format(port)) - self.connect_vbus(src_pin, dest_pin) - - if self.has_rbl: - for port in self.all_ports: - # Only input (besides pins) is the replica bitline - src_pin = self.control_logic_insts[port].get_pin("rbl_bl") - dest_pin = self.bank_inst.get_pin("rbl_bl_{0}_{0}".format(port)) - self.add_wire(self.m3_stack, - [src_pin.center(), vector(src_pin.cx(), dest_pin.cy()), dest_pin.rc()]) - self.add_via_stack_center(from_layer=src_pin.layer, - to_layer="m4", - offset=src_pin.center()) - self.add_via_stack_center(from_layer=dest_pin.layer, - to_layer="m3", - offset=dest_pin.center()) - - def route_row_addr_dff(self): - """ - Connect the output of the row flops to the bank pins - """ - for port in self.all_ports: - for bit in range(self.row_addr_size): - flop_name = "dout_{}".format(bit) - bank_name = "addr{0}_{1}".format(port, bit + self.col_addr_size) - flop_pin = self.row_addr_dff_insts[port].get_pin(flop_name) - bank_pin = self.bank_inst.get_pin(bank_name) - flop_pos = flop_pin.center() - bank_pos = bank_pin.center() - mid_pos = vector(bank_pos.x, flop_pos.y) - self.add_via_stack_center(from_layer=flop_pin.layer, - to_layer="m3", - offset=flop_pos) - self.add_path("m3", [flop_pos, mid_pos]) - self.add_via_stack_center(from_layer=bank_pin.layer, - to_layer="m3", - offset=mid_pos) - self.add_path(bank_pin.layer, [mid_pos, bank_pos]) - - 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. - """ - return - for n in self.control_logic_outputs[0]: - pin = self.control_logic_insts[0].get_pin(n) - self.add_label(text=n, - layer=pin.layer, - offset=pin.center()) - - def graph_exclude_data_dff(self): - """ - Removes data dff and wmask dff (if applicable) from search graph. - """ - # 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 != self.word_size: - for inst in self.wmask_dff_insts: - self.graph_inst_exclude.add(inst) - if self.num_spare_cols: - for inst in self.spare_wen_dff_insts: - self.graph_inst_exclude.add(inst) - - def graph_exclude_addr_dff(self): - """ - Removes data dff from search graph. - """ - # Address is considered not part of the critical path, subjectively removed - for inst in self.row_addr_dff_insts: - self.graph_inst_exclude.add(inst) - - if self.col_addr_dff: - for inst in self.col_addr_dff_insts: - self.graph_inst_exclude.add(inst) - - def graph_exclude_ctrl_dffs(self): - """ - Exclude dffs for CSB, WEB, etc from graph - """ - # Insts located in control logic, exclusion function called here - for inst in self.control_logic_insts: - inst.mod.graph_exclude_dffs() - - def get_cell_name(self, inst_name, row, col): - """ - Gets the spice name of the target bitcell. - """ - # Sanity check in case it was forgotten - if inst_name.find("x") != 0: - inst_name = "x" + inst_name - return self.bank_inst.mod.get_cell_name(inst_name + "{}x".format(OPTS.hier_seperator) + self.bank_inst.name, row, col) - - def get_bank_num(self, inst_name, row, col): - return 0 diff --git a/compiler/modules/sram_part.py b/compiler/modules/sram_part.py deleted file mode 100644 index 10b99145b..000000000 --- a/compiler/modules/sram_part.py +++ /dev/null @@ -1,2623 +0,0 @@ -# See LICENSE for licensing information. -# -# Copyright (c) 2016-2024 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. -# - -# This file aims to use openroad to do the routing -import datetime -from math import ceil -from importlib import import_module, reload -from openram import debug -from openram.base import vector -from openram.base import channel_route -from openram.base import design -from openram.base import verilog -from openram.base import lef -from openram.sram_factory import factory -from openram.tech import spice -from openram import OPTS, print_time - - -class sram_1bank(design, verilog, lef): - """ - Procedures specific to a one bank SRAM. - """ - def __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 openram.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 - - # delay control logic does not have RBLs - self.has_rbl = OPTS.control_logic != "control_logic_delay" - - def add_pin_bank(self): - # lef file use this pin name, should be same with new pin - """ Adding pins for Bank module""" - 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") - for port in self.all_ports: - if self.has_rbl: - self.add_pin("rbl_bl{0}".format(port), "OUTPUT") - for port in self.write_ports: - for bit in range(self.word_size + self.num_spare_cols): - self.add_pin("bank_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") - - # For more than one bank, we have a bank select and name - # the signals gated_*. - for port in self.read_ports: - self.add_pin("s_en{0}".format(port), "INPUT") - for port in self.all_ports: - self.add_pin("p_en_bar{0}".format(port), "INPUT") - for port in self.write_ports: - self.add_pin("w_en{0}".format(port), "INPUT") - for bit in range(self.num_wmasks): - self.add_pin("bank_wmask{0}[{1}]".format(port, bit), "INPUT") - for bit in range(self.num_spare_cols): - if self.num_spare_cols ==1: - self.add_pin("bank_spare_wen{0}".format(port), "INPUT") - else: - self.add_pin("bank_spare_wen{0}[{1}]".format(port, bit), "INPUT") - for port in self.all_ports: - self.add_pin("wl_en{0}".format(port), "INPUT") - - # 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_pin_row_addr_dff(self): - # lef file use this pin name, should be same with new pin - for port in self.all_ports: - for bit in range(self.row_addr_size): - #input - self.add_pin("addr{}[{}]".format(port, bit + self.col_addr_size), "INPUT") - print("addr{}[{}]".format(port, bit), "INPUT") - #output - self.add_pin("a{}[{}]".format(port, bit + self.col_addr_size), "OUTPUT") - #clk_buf, regard as input - self.add_pin("clk_buf{}".format(port), "INPUT") - # 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_pin_col_addr_dff(self): - # lef file use this pin name, should be same with new pin - for port in self.all_ports: - for bit in range(self.col_addr_size): - #input - self.add_pin("addr{}[{}]".format(port, bit), "INPUT") - #output - self.add_pin("a{}[{}]".format(port, bit), "OUTPUT") - #clk_buf, regard as input - self.add_pin("clk_buf{}".format(port), "INPUT") - # 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_pin_data_dff(self): - # lef file use this pin name, should be same with new pin - for port in self.all_ports: - for bit in range(self.word_size + self.num_spare_cols): - # input - self.add_pin("din{}[{}]".format(port, bit), "INPUT") - # output - self.add_pin("bank_din{}[{}]".format(port, bit), "OUTPUT") - #clk_buf, regard as input - self.add_pin("clk_buf{}".format(port), "INPUT") - # 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_pin_wmask_dff(self): - # lef file use this pin name, should be same with new pin - for port in self.all_ports: - for bit in range(self.num_wmasks): - # input - self.add_pin("wmask{}[{}]".format(port, bit), "INPUT") - # output - self.add_pin("bank_wmask{}[{}]".format(port, bit), "OUTPUT") - #clk_buf, regard as input - self.add_pin("clk_buf{}".format(port), "INPUT") - # 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_pin_spare_wen_dff(self): - # lef file use this pin name, should be same with new pin - for port in self.all_ports: - for bit in range(self.num_spare_cols): - if self.num_spare_cols == 1: - # input - self.add_pin("spare_wen{}".format(port), "INPUT") - # output - self.add_pin("bank_spare_wen{}".format(port), "OUTPUT") - else: - # input - self.add_pin("spare_wen{}[{}]".format(port, bit), "INPUT") - # output - self.add_pin("bank_spare_wen{}[{}]".format(port, bit), "OUTPUT") - #clk_buf, regard as input - self.add_pin("clk_buf{}".format(port), "INPUT") - # 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_pin_control(self): - # lef file use this pin name, should be same with new pin - for port in self.all_ports: - # Inputs - self.add_pin("csb{}".format(port), "INPUT") - if port in self.readwrite_ports: - self.add_pin("web{}".format(port), "INPUT") - self.add_pin("clk{}".format(port), "INPUT") - if self.has_rbl: - self.add_pin("rbl_bl{}".format(port), "INPUT") - # Outputs - if port in self.read_ports: - self.add_pin("s_en{}".format(port), "OUTPUT") - if port in self.write_ports: - self.add_pin("w_en{}".format(port), "OUTPUT") - self.add_pin("p_en_bar{}".format(port), "OUTPUT") - self.add_pin("wl_en{}".format(port), "OUTPUT") - self.add_pin("clk_buf{}".format(port), "OUTPUT") - - # Standard supply and ground names - # not sure if this part should stay inside the for loop - 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_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_netlist_bank(self): - """ Netlist creation """ - - start_time = datetime.datetime.now() - - # Must create the control logic before pins to get the pins - self.add_modules() - self.add_pin_bank() - self.bank_inst = self.create_bank(0) - - # 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 Bank", datetime.datetime.now(), start_time) - - def create_netlist_control(self): - """ Netlist creation """ - - start_time = datetime.datetime.now() - - # Must create the control logic before pins to get the pins - self.add_modules() - self.add_pin_control() - self.control_logic_insts = self.create_control_logic() # be aware, multi-insts possible - - # 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 Control", datetime.datetime.now(), start_time) - - def create_netlist_row_addr_dff(self): - """ Netlist creation """ - - start_time = datetime.datetime.now() - - # Must create the control logic before pins to get the pins - self.add_modules() - self.add_pin_row_addr_dff() - self.row_addr_dff_insts = self.create_row_addr_dff() # be aware, multi-insts possible - - # 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 row_addr_dff", datetime.datetime.now(), start_time) - - def create_netlist_col_addr_dff(self): - """ Netlist creation """ - - start_time = datetime.datetime.now() - - # Must create the control logic before pins to get the pins - self.add_modules() - if self.col_addr_dff: - self.add_pin_col_addr_dff() - self.col_addr_dff_insts = self.create_col_addr_dff() # be aware, multi-insts possible, or none - - # 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 col_addr_dff", datetime.datetime.now(), start_time) - else: - return False # no col_addr_dff will be generated - - def create_netlist_data_dff(self): - """ Netlist creation """ - - start_time = datetime.datetime.now() - - # Must create the control logic before pins to get the pins - self.add_modules() - self.add_pin_data_dff() - self.data_dff_insts = self.create_data_dff() # be aware, multi-insts possible - - # 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 data dff", datetime.datetime.now(), start_time) - - def create_netlist_wmask_dff(self): - """ Netlist creation """ - - start_time = datetime.datetime.now() - - # Must create the control logic before pins to get the pins - self.add_modules() - if self.write_size != self.word_size: - self.add_pin_wmask_dff() - self.wmask_dff_insts = self.create_wmask_dff() # be aware, multi-insts possible, or none - - # 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 wmask dff", datetime.datetime.now(), start_time) - else: - return False # no wmask_dff will be generated - - def create_netlist_spare_wen_dff(self): - """ Netlist creation """ - - start_time = datetime.datetime.now() - - # Must create the control logic before pins to get the pins - self.add_modules() - if self.num_spare_cols: - self.add_pin_spare_wen_dff() - self.spare_wen_dff_insts = self.create_spare_wen_dff() # be aware, multi-insts possible, or none - - # 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 spare wen dff", datetime.datetime.now(), start_time) - else: - self.num_spare_cols = 0 - return False # no spare_col will be generated - - 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_layout_bank_only(self): - start_time = datetime.datetime.now() - #Placement - self.place_bank(self.bank_inst, [0, 0], 1, 1) - if not OPTS.is_unit_test: - print_time("Bank Placement", datetime.datetime.now(), start_time) - start_time = datetime.datetime.now() - self.route_bank_only() - - 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_layout_control_only(self, instance_index=0): - start_time = datetime.datetime.now() - # Placement - # We will generate different layout for different port(if there are multi-port) - port = instance_index - self.control_logic_insts[port].place(vector(0,0)) - - if not OPTS.is_unit_test: - print_time("Control Placement", datetime.datetime.now(), start_time) - start_time = datetime.datetime.now() - self.route_control_only(instance_index=instance_index) - - 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_layout_row_addr_dff_only(self, instance_index=0): - start_time = datetime.datetime.now() - # Placement - # We will generate different layout for different port(if there are multi-port) - port = instance_index - self.row_addr_dff_insts[port].place(vector(0,0)) - - if not OPTS.is_unit_test: - print_time("row_addr_dff Placement", datetime.datetime.now(), start_time) - start_time = datetime.datetime.now() - self.route_row_addr_dff_only(instance_index=instance_index) - - 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_layout_col_addr_dff_only(self, instance_index=0): - start_time = datetime.datetime.now() - # Placement - # We will generate different layout for different port(if there are multi-port) - port = instance_index - self.col_addr_dff_insts[port].place(vector(0,0)) - - if not OPTS.is_unit_test: - print_time("col_addr_dff Placement", datetime.datetime.now(), start_time) - start_time = datetime.datetime.now() - self.route_col_addr_dff_only(instance_index=instance_index) - - 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_layout_data_dff_only(self, instance_index=0): - start_time = datetime.datetime.now() - # Placement - # We will generate different layout for different port(if there are multi-port) - port = instance_index - self.data_dff_insts[port].place(vector(0,0)) - - if not OPTS.is_unit_test: - print_time("data_dff Placement", datetime.datetime.now(), start_time) - start_time = datetime.datetime.now() - self.route_data_dff_only(instance_index=instance_index) - - 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_layout_wmask_dff_only(self, instance_index=0): - start_time = datetime.datetime.now() - # Placement - # We will generate different layout for different port(if there are multi-port) - port = instance_index - self.wmask_dff_insts[port].place(vector(0,0)) - - if not OPTS.is_unit_test: - print_time("wmask_dff Placement", datetime.datetime.now(), start_time) - start_time = datetime.datetime.now() - self.route_wmask_dff_only(instance_index=instance_index) - - 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_layout_spare_wen_dff_only(self, instance_index=0): - start_time = datetime.datetime.now() - # Placement - # We will generate different layout for different port(if there are multi-port) - port = instance_index - self.spare_wen_dff_insts[port].place(vector(0,0)) - - if not OPTS.is_unit_test: - print_time("spare_wen_dff Placement", datetime.datetime.now(), start_time) - start_time = datetime.datetime.now() - self.route_spare_wen_dff_only(instance_index=instance_index) - - 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 route_bank_only(self): - # We add the vias to M3 before routing supplies because - # they might create some blockages - self.add_layout_bank_only_pins() - pins_to_route_bank = self.add_pins_to_route_bank() - print("====================================================") - for pin in self.pins: - print(pin) - - # Some technologies have an isolation - self.add_dnwell(inflate=2.5) - - init_bbox = self.get_bbox() - # Route the supplies together and/or to the ring/stripes. - # Route the pins to the perimeter - if OPTS.perimeter_pins: - # We now route the escape routes far enough out so that they will - # reach past the power ring or stripes on the sides - from openram.router import signal_escape_router as router - rtr = router(layers=self.m3_stack, - bbox=init_bbox, - design=self) - rtr.route(pins_to_route_bank) - if OPTS.route_supplies: - self.route_supplies(init_bbox) - - def route_control_only(self, instance_index=0): - # We add the vias to M3 before routing supplies because - # they might create some blockages - self.add_layout_control_only_pins(instance_index=instance_index) - pins_to_route_control = self.add_pins_to_route_control(instance_index=instance_index) - print("====================================================") - for pin in self.pins: - print(pin) - - # Some technologies have an isolation - self.add_dnwell(inflate=2.5) - - init_bbox = self.get_bbox() - # Route the supplies together and/or to the ring/stripes. - # Route the pins to the perimeter - if OPTS.perimeter_pins: - # We now route the escape routes far enough out so that they will - # reach past the power ring or stripes on the sides - from openram.router import signal_escape_router as router - rtr = router(layers=self.m3_stack, - bbox=init_bbox, - design=self) - rtr.route(pins_to_route_control) - if OPTS.route_supplies: - self.route_supplies(init_bbox) - - def route_row_addr_dff_only(self, instance_index=0): - # We add the vias to M3 before routing supplies because - # they might create some blockages - self.add_layout_row_addr_dff_only_pins(instance_index=instance_index) - pins_to_route_row_addr_dff = self.add_pins_to_route_row_addr_dff(instance_index=instance_index) - print("====================================================") - for pin in self.pins: - print(pin) - - # Some technologies have an isolation - self.add_dnwell(inflate=2.5) - - init_bbox = self.get_bbox() - # Route the supplies together and/or to the ring/stripes. - # Route the pins to the perimeter - if OPTS.perimeter_pins: - # We now route the escape routes far enough out so that they will - # reach past the power ring or stripes on the sides - from openram.router import signal_escape_router as router - rtr = router(layers=self.m3_stack, - bbox=init_bbox, - design=self) - rtr.route(pins_to_route_row_addr_dff) - if OPTS.route_supplies: - self.route_supplies(init_bbox) - - def route_col_addr_dff_only(self, instance_index=0): - # We add the vias to M3 before routing supplies because - # they might create some blockages - self.add_layout_col_addr_dff_only_pins(instance_index=instance_index) - pins_to_route_col_addr_dff = self.add_pins_to_route_col_addr_dff(instance_index=instance_index) - print("====================================================") - for pin in self.pins: - print(pin) - - # Some technologies have an isolation - self.add_dnwell(inflate=2.5) - - init_bbox = self.get_bbox() - # Route the supplies together and/or to the ring/stripes. - # Route the pins to the perimeter - if OPTS.perimeter_pins: - # We now route the escape routes far enough out so that they will - # reach past the power ring or stripes on the sides - from openram.router import signal_escape_router as router - rtr = router(layers=self.m3_stack, - bbox=init_bbox, - design=self) - rtr.route(pins_to_route_col_addr_dff) - if OPTS.route_supplies: - self.route_supplies(init_bbox) - - def route_data_dff_only(self, instance_index=0): - # We add the vias to M3 before routing supplies because - # they might create some blockages - self.add_layout_data_dff_only_pins(instance_index=instance_index) - pins_to_route_data_dff = self.add_pins_to_route_data_dff(instance_index=instance_index) - print("====================================================") - for pin in self.pins: - print(pin) - - # Some technologies have an isolation - self.add_dnwell(inflate=2.5) - - init_bbox = self.get_bbox() - # Route the supplies together and/or to the ring/stripes. - # Route the pins to the perimeter - if OPTS.perimeter_pins: - # We now route the escape routes far enough out so that they will - # reach past the power ring or stripes on the sides - from openram.router import signal_escape_router as router - rtr = router(layers=self.m3_stack, - bbox=init_bbox, - design=self) - rtr.route(pins_to_route_data_dff) - if OPTS.route_supplies: - self.route_supplies(init_bbox) - - def route_wmask_dff_only(self, instance_index=0): - # We add the vias to M3 before routing supplies because - # they might create some blockages - self.add_layout_wmask_dff_only_pins(instance_index=instance_index) - pins_to_route_wmask_dff = self.add_pins_to_route_wmask_dff(instance_index=instance_index) - print("====================================================") - for pin in self.pins: - print(pin) - - # Some technologies have an isolation - self.add_dnwell(inflate=2.5) - - init_bbox = self.get_bbox() - # Route the supplies together and/or to the ring/stripes. - # Route the pins to the perimeter - if OPTS.perimeter_pins: - # We now route the escape routes far enough out so that they will - # reach past the power ring or stripes on the sides - from openram.router import signal_escape_router as router - rtr = router(layers=self.m3_stack, - bbox=init_bbox, - design=self) - rtr.route(pins_to_route_wmask_dff) - if OPTS.route_supplies: - self.route_supplies(init_bbox) - - def route_spare_wen_dff_only(self, instance_index=0): - # We add the vias to M3 before routing supplies because - # they might create some blockages - self.add_layout_spare_wen_dff_only_pins(instance_index=instance_index) - pins_to_route_spare_wen_dff = self.add_pins_to_route_spare_wen_dff(instance_index=instance_index) - print("====================================================") - for pin in self.pins: - print(pin) - - # Some technologies have an isolation - self.add_dnwell(inflate=2.5) - - init_bbox = self.get_bbox() - # Route the supplies together and/or to the ring/stripes. - # Route the pins to the perimeter - if OPTS.perimeter_pins: - # We now route the escape routes far enough out so that they will - # reach past the power ring or stripes on the sides - from openram.router import signal_escape_router as router - rtr = router(layers=self.m3_stack, - bbox=init_bbox, - design=self) - rtr.route(pins_to_route_spare_wen_dff) - if OPTS.route_supplies: - self.route_supplies(init_bbox) - - def add_pins_to_route_spare_wen_dff(self, instance_index=0): - #only contains signal pins, no power pins - """ Adding to route pins for spare wen dff """ - pins_to_route = [] - port = instance_index - - for bit in range(self.num_spare_cols): - if self.num_spare_cols == 1: - # input - pins_to_route.append("spare_wen{}".format(port)) - # output - pins_to_route.append("bank_spare_wen{}".format(port)) - else: - # input - pins_to_route.append("spare_wen{}[{}]".format(port, bit)) - # output - pins_to_route.append("bank_spare_wen{}[{}]".format(port, bit)) - #clk_buf, regard as input - pins_to_route.append("clk_buf{}".format(port)) - - return pins_to_route - - def add_layout_spare_wen_dff_only_pins(self, add_vias=True, instance_index=0): - """ - Add the top-level pins for a single bank SRAM with control. - """ - port = instance_index - # Hack: If we are escape routing, set the pin layer to - # None so that we will start from the pin layer - # Otherwise, set it as the pin layer so that no vias are added. - # Otherwise, when we remove pins to move the dff array dynamically, - # we will leave some remaining vias when the pin locations change. - if add_vias: - pin_layer = None - else: - pin_layer = self.pwr_grid_layers[0] - - for bit in range(self.num_spare_cols): - if self.num_spare_cols == 1: - # input - self.add_io_pin(self.spare_wen_dff_insts[port], - "din_{}".format(bit), # old name - "spare_wen{}".format(port), # new name - start_layer=pin_layer) - # output - self.add_io_pin(self.spare_wen_dff_insts[port], - "dout_{}".format(bit), - "bank_spare_wen{}".format(port), - start_layer=pin_layer) - else: - # input - self.add_io_pin(self.spare_wen_dff_insts[port], - "din_{}".format(bit), # old name - "spare_wen{}[{}]".format(port, bit), # new name - start_layer=pin_layer) - # output - self.add_io_pin(self.spare_wen_dff_insts[port], - "dout_{}".format(bit), - "bank_spare_wen{}[{}]".format(port, bit), - start_layer=pin_layer) - #clk_buf, regard as input - self.add_io_pin(self.spare_wen_dff_insts[port], - "clk", - "clk_buf{}".format(port), - start_layer=pin_layer) - - def add_pins_to_route_wmask_dff(self, instance_index=0): - #only contains signal pins, no power pins - """ Adding to route pins for wmask addr dff """ - pins_to_route = [] - port = instance_index - for bit in range(self.num_wmasks): - # input - pins_to_route.append("wmask{}[{}]".format(port, bit)) - # output - pins_to_route.append("bank_wmask{}[{}]".format(port, bit)) - #clk_buf, regard as input - pins_to_route.append("clk_buf{}".format(port)) - - return pins_to_route - - def add_layout_wmask_dff_only_pins(self, add_vias=True, instance_index=0): - """ - Add the top-level pins for a single bank SRAM with control. - """ - port = instance_index - # Hack: If we are escape routing, set the pin layer to - # None so that we will start from the pin layer - # Otherwise, set it as the pin layer so that no vias are added. - # Otherwise, when we remove pins to move the dff array dynamically, - # we will leave some remaining vias when the pin locations change. - if add_vias: - pin_layer = None - else: - pin_layer = self.pwr_grid_layers[0] - - for bit in range(self.num_wmasks): - # input - self.add_io_pin(self.wmask_dff_insts[port], - "din_{}".format(bit), - "wmask{}[{}]".format(port, bit), - start_layer=pin_layer) - # output - self.add_io_pin(self.wmask_dff_insts[port], - "dout_{}".format(bit), - "bank_wmask{}[{}]".format(port, bit), - start_layer=pin_layer) - #clk_buf, regard as input - self.add_io_pin(self.wmask_dff_insts[port], - "clk", - "clk_buf{}".format(port), - start_layer=pin_layer) - - def add_pins_to_route_data_dff(self, instance_index=0): - #only contains signal pins, no power pins - """ Adding to route pins for col addr dff """ - pins_to_route = [] - port = instance_index - for bit in range(self.word_size + self.num_spare_cols): - # input - pins_to_route.append("din{}[{}]".format(port, bit)) - # output - pins_to_route.append("bank_din{}[{}]".format(port, bit)) - #clk_buf, regard as input - pins_to_route.append("clk_buf{}".format(port)) - - return pins_to_route - - def add_layout_data_dff_only_pins(self, add_vias=True, instance_index=0): - """ - Add the top-level pins for a single bank SRAM with control. - """ - port = instance_index - # Hack: If we are escape routing, set the pin layer to - # None so that we will start from the pin layer - # Otherwise, set it as the pin layer so that no vias are added. - # Otherwise, when we remove pins to move the dff array dynamically, - # we will leave some remaining vias when the pin locations change. - if add_vias: - pin_layer = None - else: - pin_layer = self.pwr_grid_layers[0] - - for bit in range(self.word_size + self.num_spare_cols): - # input - self.add_io_pin(self.data_dff_insts[port], - "din_{}".format(bit), - "din{}[{}]".format(port, bit), - start_layer=pin_layer) - # output - self.add_io_pin(self.data_dff_insts[port], - "dout_{}".format(bit), - "bank_din{}[{}]".format(port, bit), - start_layer=pin_layer) - #clk_buf, regard as input - self.add_io_pin(self.data_dff_insts[port], - "clk", - "clk_buf{}".format(port), - start_layer=pin_layer) - - def add_pins_to_route_col_addr_dff(self, instance_index=0): - #only contains signal pins, no power pins - """ Adding to route pins for col addr dff """ - pins_to_route = [] - port = instance_index - for bit in range(self.col_addr_size): - #input - pins_to_route.append("addr{}[{}]".format(port, bit)) - #output - pins_to_route.append("a{}[{}]".format(port, bit)) - #clk_buf, regard as input - pins_to_route.append("clk_buf{}".format(port)) - - return pins_to_route - - def add_layout_col_addr_dff_only_pins(self, add_vias=True, instance_index=0): - """ - Add the top-level pins for a single bank SRAM with control. - """ - port = instance_index - # Hack: If we are escape routing, set the pin layer to - # None so that we will start from the pin layer - # Otherwise, set it as the pin layer so that no vias are added. - # Otherwise, when we remove pins to move the dff array dynamically, - # we will leave some remaining vias when the pin locations change. - if add_vias: - pin_layer = None - else: - pin_layer = self.pwr_grid_layers[0] - - for bit in range(self.col_addr_size): - #input - self.add_io_pin(self.col_addr_dff_insts[port], - "din_{}".format(bit), # old name - "addr{}[{}]".format(port, bit), # new name - start_layer=pin_layer) - #output - self.add_io_pin(self.col_addr_dff_insts[port], - "dout_{}".format(bit), - "a{}[{}]".format(port, bit), - start_layer=pin_layer) - #clk_buf, regard as input - self.add_pin(self.col_addr_dff_insts[port], - "clk", - "clk_buf{}".format(port), - start_layer=pin_layer) - - def add_pins_to_route_row_addr_dff(self, instance_index=0): - #only contains signal pins, no power pins - """ Adding to route pins for row addr dff """ - pins_to_route = [] - port = instance_index - for bit in range(self.row_addr_size): - #input - pins_to_route.append("addr{}[{}]".format(port, bit + self.col_addr_size)) - #output - pins_to_route.append("a{}[{}]".format(port, bit + self.col_addr_size)) - #clk_buf, regard as input - pins_to_route.append("clk_buf{}".format(port)) - - return pins_to_route - - def add_layout_row_addr_dff_only_pins(self, add_vias=True, instance_index=0): - """ - Add the top-level pins for a single bank SRAM with control. - """ - port = instance_index - # Hack: If we are escape routing, set the pin layer to - # None so that we will start from the pin layer - # Otherwise, set it as the pin layer so that no vias are added. - # Otherwise, when we remove pins to move the dff array dynamically, - # we will leave some remaining vias when the pin locations change. - if add_vias: - pin_layer = None - else: - pin_layer = self.pwr_grid_layers[0] - - for bit in range(self.row_addr_size): - #input - #debug - print("this is port number:{0}".format(port)) - print("insts:{0}".format(self.row_addr_dff_insts[port])) - self.add_io_pin(self.row_addr_dff_insts[port], - "din_{}".format(bit + self.col_addr_size), # old name - "addr{}[{}]".format(port, bit + self.col_addr_size), # new name - start_layer=pin_layer) - #output - self.add_io_pin(self.row_addr_dff_insts[port], - "dout_{}".format(bit + self.col_addr_size), - "a{}[{}]".format(port, bit + self.col_addr_size), - start_layer=pin_layer) - #clk_buf, regard as input - self.add_io_pin(self.row_addr_dff_insts[port], - "clk", - "clk_buf{}".format(port), - start_layer=pin_layer) - - def add_pins_to_route_control(self, instance_index=0): - #only contains signal pins, no power pins - """ Adding to route pins for control """ - pins_to_route = [] - port = instance_index - # Inputs - pins_to_route.append("csb{}".format(port)) - if port in self.readwrite_ports: - pins_to_route.append("web{}".format(port)), - pins_to_route.append("clk{}".format(port)) - if self.has_rbl: - pins_to_route.append("rbl_bl{}".format(port)) - # Outputs - if port in self.read_ports: - pins_to_route.append("s_en{}".format(port)) - if port in self.write_ports: - pins_to_route.append("w_en{}".format(port)) - pins_to_route.append("p_en_bar{}".format(port)) - pins_to_route.append("wl_en{}".format(port)) - pins_to_route.append("clk_buf{}".format(port)) - - return pins_to_route - - def add_layout_control_only_pins(self, add_vias=True, instance_index=0): - """ - Add the top-level pins for a single bank SRAM with control. - """ - port = instance_index - # Hack: If we are escape routing, set the pin layer to - # None so that we will start from the pin layer - # Otherwise, set it as the pin layer so that no vias are added. - # Otherwise, when we remove pins to move the dff array dynamically, - # we will leave some remaining vias when the pin locations change. - if add_vias: - pin_layer = None - else: - pin_layer = self.pwr_grid_layers[0] - - # Inputs - self.add_io_pin(self.control_logic_insts[port], - "csb", #old name - "csb{}".format(port), #new name - start_layer=pin_layer) - if port in self.readwrite_ports: - self.add_io_pin(self.control_logic_insts[port], - "web", - "web{}".format(port), - start_layer=pin_layer) - self.add_io_pin(self.control_logic_insts[port], - "clk", - "clk{}".format(port), - start_layer=pin_layer) - if self.has_rbl: - self.add_io_pin(self.control_logic_insts[port], - "rbl_bl", - "rbl_bl{}".format(port), - start_layer=pin_layer) - # Outputs - if port in self.read_ports: - self.add_io_pin(self.control_logic_insts[port], - "s_en", - "s_en{}".format(port), - start_layer=pin_layer) - if port in self.write_ports: - self.add_io_pin(self.control_logic_insts[port], - "w_en", - "w_en{}".format(port), - start_layer=pin_layer) - self.add_io_pin(self.control_logic_insts[port], - "p_en_bar", - "p_en_bar{}".format(port), - start_layer=pin_layer) - self.add_io_pin(self.control_logic_insts[port], - "wl_en", - "wl_en{}".format(port), - start_layer=pin_layer) - self.add_io_pin(self.control_logic_insts[port], - "clk_buf", - "clk_buf{}".format(port), - start_layer=pin_layer) - - def add_pins_to_route_bank(self): - #only contains signal pins, no power pins - """ Adding to route pins for Bank """ - pins_to_route = [] - for 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 port in self.all_ports: - if self.has_rbl: - pins_to_route.append("rbl_bl{0}".format(port)) - for port in self.write_ports: - for bit in range(self.word_size + self.num_spare_cols): - pins_to_route.append("bank_din{0}[{1}]".format(port, bit)) - for port in self.all_ports: - for bit in range(self.bank_addr_size): - pins_to_route.append("addr{0}[{1}]".format(port, bit)) - - # For more than one bank, we have a bank select and name - # the signals gated_*. - for port in self.read_ports: - pins_to_route.append("s_en{0}".format(port)) - for port in self.all_ports: - pins_to_route.append("p_en_bar{0}".format(port)) - for port in self.write_ports: - pins_to_route.append("w_en{0}".format(port)) - for bit in range(self.num_wmasks): - pins_to_route.append("bank_wmask{0}[{1}]".format(port, bit)) - for bit in range(self.num_spare_cols): - if self.num_spare_cols == 1: - pins_to_route.append("bank_spare_wen{0}".format(port, bit)) - else: - pins_to_route.append("bank_spare_wen{0}[{1}]".format(port, bit)) - for port in self.all_ports: - pins_to_route.append("wl_en{0}".format(port)) - - return pins_to_route - - def add_layout_bank_only_pins(self, add_vias=True): - """ - Add the top-level pins for a single bank SRAM with control. - """ - # Hack: If we are escape routing, set the pin layer to - # None so that we will start from the pin layer - # Otherwise, set it as the pin layer so that no vias are added. - # Otherwise, when we remove pins to move the dff array dynamically, - # we will leave some remaining vias when the pin locations change. - if add_vias: - pin_layer = None - else: - pin_layer = self.pwr_grid_layers[0] - - for port in self.readwrite_ports or port in self.read_ports: - for bit in range(self.word_size + self.num_spare_cols): - self.add_io_pin(self.bank_inst, - "dout{0}_{1}".format(port, bit),#old name - "dout{0}[{1}]".format(port, bit),#new name - start_layer=pin_layer) - """ Adding pins for Bank module""" - for port in self.all_ports: - if self.has_rbl: - self.add_io_pin(self.bank_inst, - "rbl_bl_{0}_{0}".format(port), - "rbl_bl{0}".format(port), - start_layer=pin_layer) - for port in self.write_ports: - for bit in range(self.word_size + self.num_spare_cols): - self.add_io_pin(self.bank_inst, - "din{0}_{1}".format(port, bit), - "bank_din{0}[{1}]".format(port, bit), - start_layer=pin_layer) - # manuel change position, so not at same y - for port in self.all_ports: - for bit in range(self.bank_addr_size): - self.change_layout_pin_position(self.bank_inst, - "addr{0}_{1}".format(port, bit), - "addr{0}[{1}]".format(port, bit), - start_layer=pin_layer, - distance=bit) - for port in self.read_ports: - self.change_layout_pin_position(self.bank_inst, - "s_en{0}".format(port), - "s_en{0}".format(port), - start_layer=pin_layer, - distance=4) - for port in self.all_ports: - self.change_layout_pin_position(self.bank_inst, - "p_en_bar{0}".format(port), - "p_en_bar{0}".format(port), - start_layer=pin_layer, - distance=2) - for port in self.write_ports: - self.change_layout_pin_position(self.bank_inst, - "w_en{0}".format(port), - "w_en{0}".format(port), - start_layer=pin_layer) - - for bit in range(self.num_wmasks): - self.add_io_pin(self.bank_inst, - "bank_wmask{0}_{1}".format(port, bit), - "bank_wmask{0}[{1}]".format(port, bit), - start_layer=pin_layer) - for bit in range(self.num_spare_cols): - if self.num_spare_cols == 1: - self.add_io_pin(self.bank_inst, - "bank_spare_wen{0}_{1}".format(port, bit), - "bank_spare_wen{0}".format(port), - start_layer=pin_layer) - else: - self.add_io_pin(self.bank_inst, - "bank_spare_wen{0}_{1}".format(port, bit), - "bank_spare_wen{0}[{1}]".format(port, bit), - start_layer=pin_layer) - if port in self.all_ports: - self.add_io_pin(self.bank_inst, - "wl_en{0}".format(port), - "wl_en{0}".format(port), - start_layer=pin_layer) - - def change_layout_pin_position(self, instance, pin_name, new_name, start_layer=None, directions=None, distance=1): - """ - Add a signle input or output pin up to metal 3. - This additonal operation make sure pins are not at the same y - """ - pin = instance.get_pin(pin_name) - - if not start_layer: - start_layer = pin.layer - # Just use the power pin function for now to save code - self.add_power_pin(new_name, vector(pin.center()[0], pin.center()[1] + self.m3_pitch * distance * 2), start_layer=start_layer, directions=directions) - - 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]) - - from openram.router import supply_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.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=None): - """ - 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 openram.router import signal_escape_router as router - rtr = router(layers=self.m3_stack, - bbox=bbox, - design=self) - rtr.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_module("." + OPTS.control_logic, "openram.modules")) - 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)) - if self.has_rbl: - 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)) - if self.has_rbl: - 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): - """ - This adds the modules for a single bank SRAM with control - logic. - """ - - self.bank_inst = self.create_bank(0) - - self.control_logic_insts = self.create_control_logic() - - self.row_addr_dff_insts = self.create_row_addr_dff() - - if self.col_addr_dff: - self.col_addr_dff_insts = self.create_col_addr_dff() - - if self.write_size != self.word_size: - self.wmask_dff_insts = self.create_wmask_dff() - self.data_dff_insts = self.create_data_dff() - else: - self.data_dff_insts = self.create_data_dff() - - if self.num_spare_cols: - self.spare_wen_dff_insts = self.create_spare_wen_dff() - else: - self.num_spare_cols = 0 - - def place_instances(self): - """ - This places the instances for a single bank SRAM with control - logic and up to 2 ports. - """ - - # No orientation or offset - self.place_bank(self.bank_inst, [0, 0], 1, 1) - - # The control logic is placed such that the vertical center (between the delay/RBL and - # the actual control logic is aligned with the vertical center of the bank (between - # the sense amps/column mux and cell array) - # The x-coordinate is placed to allow a single clock wire (plus an extra pitch) - # up to the row address DFFs. - self.control_pos = [None] * len(self.all_ports) - self.row_addr_pos = [None] * len(self.all_ports) - - # DFFs are placd on their own - self.col_addr_pos = [None] * len(self.all_ports) - self.wmask_pos = [None] * len(self.all_ports) - self.spare_wen_pos = [None] * len(self.all_ports) - self.data_pos = [None] * len(self.all_ports) - - # These positions utilize the channel route sizes. - # FIXME: Auto-compute these rather than manual computation. - # If a horizontal channel, they rely on the vertical channel non-preferred (contacted) pitch. - # If a vertical channel, they rely on the horizontal channel non-preferred (contacted) pitch. - # So, m3 non-pref pitch means that this is routed on the m2 layer. - self.data_bus_gap = self.m4_nonpref_pitch * 2 - - # Spare wen are on a separate layer so not included - # Start with 1 track minimum - self.data_bus_size = [1] * len(self.all_ports) - self.col_addr_bus_size = [1] * len(self.all_ports) - for port in self.all_ports: - # The column address wires are routed separately from the data bus and will always be smaller. - # All ports need the col addr flops - self.col_addr_bus_size[port] = self.col_addr_size * self.m4_nonpref_pitch - # Write ports need the data input flops and write mask flops - if port in self.write_ports: - self.data_bus_size[port] += self.num_wmasks + self.word_size - # This is for the din pins that get routed in the same channel - # when we have dout and din together - if port in self.readwrite_ports: - self.data_bus_size[port] += self.word_size - # Convert to length - self.data_bus_size[port] *= self.m4_nonpref_pitch - # Add the gap in unit length - self.data_bus_size[port] += self.data_bus_gap - - # The control and row addr flops are independent of any bus widths. - self.place_control() - self.place_row_addr_dffs() - - # Place with an initial wide channel (from above) - self.place_dffs() - - # Route the channel and set to the new data bus size - # We need to temporarily add some pins for the x offsets - # but we'll remove them so that they have the right y - # offsets after the DFF placement. - self.add_layout_pins(add_vias=False) - self.route_dffs(add_routes=False) - self.remove_layout_pins() - - # Re-place with the new channel size - self.place_dffs() - - def place_row_addr_dffs(self): - """ - Must be run after place control logic. - """ - port = 0 - # The row address bits are placed above the control logic aligned on the right. - x_offset = self.control_logic_insts[port].rx() - self.row_addr_dff_insts[port].width - # It is above the control logic and the predecoder array - y_offset = max(self.control_logic_insts[port].uy(), self.bank.predecoder_top) - - self.row_addr_pos[port] = vector(x_offset, y_offset) - self.row_addr_dff_insts[port].place(self.row_addr_pos[port]) - - if len(self.all_ports)>1: - port = 1 - # The row address bits are placed above the control logic aligned on the left. - x_offset = self.control_pos[port].x - self.control_logic_insts[port].width + self.row_addr_dff_insts[port].width - # If it can be placed above the predecoder and below the control logic, do it - y_offset = self.bank.predecoder_bottom - self.row_addr_pos[port] = vector(x_offset, y_offset) - self.row_addr_dff_insts[port].place(self.row_addr_pos[port], mirror="XY") - - def place_control(self): - port = 0 - - # This includes 2 M2 pitches for the row addr clock line. - # The delay line is aligned with the bitcell array while the control logic is aligned with the port_data - # using the control_logic_center value. - self.control_pos[port] = vector(-self.control_logic_insts[port].width - 2 * self.m2_pitch, - self.bank.bank_array_ll.y - self.control_logic_insts[port].mod.control_logic_center.y) - self.control_logic_insts[port].place(self.control_pos[port]) - if len(self.all_ports) > 1: - port = 1 - # This includes 2 M2 pitches for the row addr clock line - # The delay line is aligned with the bitcell array while the control logic is aligned with the port_data - # using the control_logic_center value. - self.control_pos[port] = vector(self.bank_inst.rx() + self.control_logic_insts[port].width + 2 * self.m2_pitch, - self.bank.bank_array_ur.y - + self.control_logic_insts[port].height - - self.control_logic_insts[port].height - + self.control_logic_insts[port].mod.control_logic_center.y) - self.control_logic_insts[port].place(self.control_pos[port], mirror="XY") - - def place_dffs(self): - """ - Place the col addr, data, wmask, and spare data DFFs. - This can be run more than once after we recompute the channel width. - """ - - port = 0 - # Add the col address flops below the bank to the right of the control logic - x_offset = self.control_logic_insts[port].rx() + self.dff.width - # Place it a data bus below the x-axis, but at least as low as the control logic to not block - # the control logic signals - y_offset = min(-self.data_bus_size[port] - self.dff.height, - self.control_logic_insts[port].by()) - if self.col_addr_dff: - self.col_addr_pos[port] = vector(x_offset, - y_offset) - self.col_addr_dff_insts[port].place(self.col_addr_pos[port]) - x_offset = self.col_addr_dff_insts[port].rx() - else: - self.col_addr_pos[port] = vector(x_offset, 0) - - if port in self.write_ports: - 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) - self.wmask_dff_insts[port].place(self.wmask_pos[port]) - x_offset = self.wmask_dff_insts[port].rx() - - # Add the data flops below the write mask flops. - self.data_pos[port] = vector(x_offset, - y_offset) - self.data_dff_insts[port].place(self.data_pos[port]) - x_offset = self.data_dff_insts[port].rx() - - # Add spare write enable flops to the right of data flops since the spare columns - # will be on the right - if self.num_spare_cols: - self.spare_wen_pos[port] = vector(x_offset, - y_offset) - self.spare_wen_dff_insts[port].place(self.spare_wen_pos[port]) - x_offset = self.spare_wen_dff_insts[port].rx() - - else: - self.wmask_pos[port] = vector(x_offset, y_offset) - self.data_pos[port] = vector(x_offset, y_offset) - self.spare_wen_pos[port] = vector(x_offset, y_offset) - - if len(self.all_ports) > 1: - port = 1 - - # Add the col address flops below the bank to the right of the control logic - x_offset = self.control_logic_insts[port].lx() - 2 * self.dff.width - # Place it a data bus below the x-axis, but at least as high as the control logic to not block - # the control logic signals - y_offset = max(self.bank.height + self.data_bus_size[port] + self.dff.height, - self.control_logic_insts[port].uy() - self.dff.height) - if self.col_addr_dff: - self.col_addr_pos[port] = vector(x_offset, - y_offset) - self.col_addr_dff_insts[port].place(self.col_addr_pos[port], mirror="XY") - x_offset = self.col_addr_dff_insts[port].lx() - else: - self.col_addr_pos[port] = vector(x_offset, y_offset) - - if port in self.write_ports: - # Add spare write enable flops to the right of the data flops since the spare - # columns will be on the left - if self.num_spare_cols: - self.spare_wen_pos[port] = vector(x_offset - self.spare_wen_dff_insts[port].width, - y_offset) - 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 != 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) - self.wmask_dff_insts[port].place(self.wmask_pos[port], mirror="MX") - x_offset = self.wmask_dff_insts[port].lx() - - # Add the data flops below the write mask flops. - self.data_pos[port] = vector(x_offset - self.data_dff_insts[port].width, - y_offset) - self.data_dff_insts[port].place(self.data_pos[port], mirror="MX") - else: - self.wmask_pos[port] = vector(x_offset, y_offset) - self.data_pos[port] = vector(x_offset, y_offset) - self.spare_wen_pos[port] = vector(x_offset, y_offset) - - def add_layout_pins(self, add_vias=True): - """ - Add the top-level pins for a single bank SRAM with control. - """ - for port in self.all_ports: - # Hack: If we are escape routing, set the pin layer to - # None so that we will start from the pin layer - # Otherwise, set it as the pin layer so that no vias are added. - # Otherwise, when we remove pins to move the dff array dynamically, - # we will leave some remaining vias when the pin locations change. - if add_vias: - pin_layer = None - else: - pin_layer = self.pwr_grid_layers[0] - - # Connect the control pins as inputs - for signal in self.control_logic_inputs[port]: - if signal.startswith("rbl"): - continue - self.add_io_pin(self.control_logic_insts[port], - signal, - signal + "{}".format(port), - start_layer=pin_layer) - - if port in self.write_ports: - for bit in range(self.word_size + self.num_spare_cols): - self.add_io_pin(self.data_dff_insts[port], - "din_{}".format(bit), - "din{0}[{1}]".format(port, bit), - start_layer=pin_layer) - - if port in self.readwrite_ports or port in self.read_ports: - for bit in range(self.word_size + self.num_spare_cols): - self.add_io_pin(self.bank_inst, - "dout{0}_{1}".format(port, bit), - "dout{0}[{1}]".format(port, bit), - start_layer=pin_layer) - - for bit in range(self.col_addr_size): - self.add_io_pin(self.col_addr_dff_insts[port], - "din_{}".format(bit), - "addr{0}[{1}]".format(port, bit), - start_layer=pin_layer) - - for bit in range(self.row_addr_size): - self.add_io_pin(self.row_addr_dff_insts[port], - "din_{}".format(bit), - "addr{0}[{1}]".format(port, bit + self.col_addr_size), - start_layer=pin_layer) - - if port in self.write_ports: - 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), - "wmask{0}[{1}]".format(port, bit), - start_layer=pin_layer) - - if port in self.write_ports: - if self.num_spare_cols == 1: - self.add_io_pin(self.spare_wen_dff_insts[port], - "din_{}".format(0), - "spare_wen{0}".format(port), - start_layer=pin_layer) - else: - for bit in range(self.num_spare_cols): - self.add_io_pin(self.spare_wen_dff_insts[port], - "din_{}".format(bit), - "spare_wen{0}[{1}]".format(port, bit), - start_layer=pin_layer) - - def route_layout(self): - """ Route a single bank SRAM """ - - self.route_clk() - - self.route_control_logic() - - self.route_row_addr_dff() - - self.route_dffs() - - # We add the vias to M3 before routing supplies because - # they might create some blockages - self.add_layout_pins() - - # Some technologies have an isolation - self.add_dnwell(inflate=2.5) - - init_bbox = self.get_bbox() - # Route the supplies together and/or to the ring/stripes. - # Route the pins to the perimeter - # change the order - - if OPTS.perimeter_pins: - # We now route the escape routes far enough out so that they will - # reach past the power ring or stripes on the sides - self.route_escape_pins(init_bbox) - #if OPTS.route_supplies: - # self.route_supplies(init_bbox) - """ - if OPTS.route_supplies: - self.route_supplies(init_bbox) - if OPTS.perimeter_pins: - # We now route the escape routes far enough out so that they will - # reach past the power ring or stripes on the sides - self.route_escape_pins(init_bbox) - """ - - - def route_dffs(self, add_routes=True): - - for port in self.all_ports: - self.route_dff(port, add_routes) - - def route_dff(self, port, add_routes): - - # This is only done when we add_routes because the data channel will be larger - # so that can be used for area estimation. - if add_routes: - self.route_col_addr_dffs(port) - - self.route_data_dffs(port, add_routes) - - def route_col_addr_dffs(self, port): - - route_map = [] - - # column mux dff is routed on it's own since it is to the far end - # decoder inputs are min pitch M2, so need to use lower layer stack - if self.col_addr_size > 0: - dff_names = ["dout_{}".format(x) for x in range(self.col_addr_size)] - dff_pins = [self.col_addr_dff_insts[port].get_pin(x) for x in dff_names] - bank_names = ["addr{0}_{1}".format(port, x) for x in range(self.col_addr_size)] - bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] - route_map.extend(list(zip(bank_pins, dff_pins))) - - if len(route_map) > 0: - - # This layer stack must be different than the data dff layer stack - layer_stack = self.m1_stack - - if port == 0: - offset = vector(self.control_logic_insts[port].rx() + self.dff.width, - - self.data_bus_size[port] + 2 * self.m3_pitch) - cr = channel_route(netlist=route_map, - offset=offset, - layer_stack=layer_stack, - parent=self) - # This causes problem in magic since it sometimes cannot extract connectivity of instances - # with no active devices. - self.add_inst(cr.name, cr) - self.connect_inst([]) - # self.add_flat_inst(cr.name, cr) - else: - offset = vector(0, - self.bank.height + self.m3_pitch) - cr = channel_route(netlist=route_map, - offset=offset, - layer_stack=layer_stack, - parent=self) - # This causes problem in magic since it sometimes cannot extract connectivity of instances - # with no active devices. - self.add_inst(cr.name, cr) - self.connect_inst([]) - # self.add_flat_inst(cr.name, cr) - - def route_data_dffs(self, port, add_routes): - route_map = [] - - # wmask dff - if self.num_wmasks > 0 and port in self.write_ports: - dff_names = ["dout_{}".format(x) for x in range(self.num_wmasks)] - dff_pins = [self.wmask_dff_insts[port].get_pin(x) for x in dff_names] - bank_names = ["bank_wmask{0}_{1}".format(port, x) for x in range(self.num_wmasks)] - bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] - route_map.extend(list(zip(bank_pins, dff_pins))) - - if port in self.write_ports: - # synchronized inputs from data dff - dff_names = ["dout_{}".format(x) for x in range(self.word_size + self.num_spare_cols)] - dff_pins = [self.data_dff_insts[port].get_pin(x) for x in dff_names] - bank_names = ["din{0}_{1}".format(port, x) for x in range(self.word_size + self.num_spare_cols)] - bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] - route_map.extend(list(zip(bank_pins, dff_pins))) - - # spare wen dff - if self.num_spare_cols > 0 and port in self.write_ports: - dff_names = ["dout_{}".format(x) for x in range(self.num_spare_cols)] - dff_pins = [self.spare_wen_dff_insts[port].get_pin(x) for x in dff_names] - bank_names = ["bank_spare_wen{0}_{1}".format(port, x) for x in range(self.num_spare_cols)] - bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] - route_map.extend(list(zip(bank_pins, dff_pins))) - - if len(route_map) > 0: - - # This layer stack must be different than the column addr dff layer stack - layer_stack = self.m3_stack - if port == 0: - # This is relative to the bank at 0,0 or the s_en which is routed on M3 also - if "s_en" in self.control_logic_insts[port].mod.pin_map: - y_bottom = min(0, self.control_logic_insts[port].get_pin("s_en").by()) - else: - y_bottom = 0 - - y_offset = y_bottom - self.data_bus_size[port] + 2 * self.m3_pitch - offset = vector(self.control_logic_insts[port].rx() + self.dff.width, - y_offset) - cr = channel_route(netlist=route_map, - offset=offset, - layer_stack=layer_stack, - parent=self) - if add_routes: - # This causes problem in magic since it sometimes cannot extract connectivity of instances - # with no active devices. - self.add_inst(cr.name, cr) - self.connect_inst([]) - # self.add_flat_inst(cr.name, cr) - else: - self.data_bus_size[port] = max(cr.height, self.col_addr_bus_size[port]) + self.data_bus_gap - else: - if "s_en" in self.control_logic_insts[port].mod.pin_map: - y_top = max(self.bank.height, self.control_logic_insts[port].get_pin("s_en").uy()) - else: - y_top = self.bank.height - y_offset = y_top + self.m3_pitch - offset = vector(0, - y_offset) - cr = channel_route(netlist=route_map, - offset=offset, - layer_stack=layer_stack, - parent=self) - if add_routes: - # This causes problem in magic since it sometimes cannot extract connectivity of instances - # with no active devices. - self.add_inst(cr.name, cr) - self.connect_inst([]) - # self.add_flat_inst(cr.name, cr) - else: - self.data_bus_size[port] = max(cr.height, self.col_addr_bus_size[port]) + self.data_bus_gap - - def route_clk(self): - """ Route the clock network """ - - # This is the actual input to the SRAM - for port in self.all_ports: - # Connect all of these clock pins to the clock in the central bus - # This is something like a "spine" clock distribution. The two spines - # are clk_buf and clk_buf_bar - control_clk_buf_pin = self.control_logic_insts[port].get_pin("clk_buf") - control_clk_buf_pos = control_clk_buf_pin.center() - - # This uses a metal2 track to the right (for port0) of the control/row addr DFF - # to route vertically. For port1, it is to the left. - row_addr_clk_pin = self.row_addr_dff_insts[port].get_pin("clk") - if port % 2: - control_clk_buf_pos = control_clk_buf_pin.lc() - row_addr_clk_pos = row_addr_clk_pin.lc() - mid1_pos = vector(self.row_addr_dff_insts[port].lx() - self.m2_pitch, - row_addr_clk_pos.y) - else: - control_clk_buf_pos = control_clk_buf_pin.rc() - row_addr_clk_pos = row_addr_clk_pin.rc() - mid1_pos = vector(self.row_addr_dff_insts[port].rx() + self.m2_pitch, - row_addr_clk_pos.y) - - # This is the steiner point where the net branches out - clk_steiner_pos = vector(mid1_pos.x, control_clk_buf_pos.y) - self.add_path(control_clk_buf_pin.layer, [control_clk_buf_pos, clk_steiner_pos]) - self.add_via_stack_center(from_layer=control_clk_buf_pin.layer, - to_layer="m2", - offset=clk_steiner_pos) - - # Note, the via to the control logic is taken care of above - self.add_wire(self.m2_stack[::-1], - [row_addr_clk_pos, mid1_pos, clk_steiner_pos]) - - if self.col_addr_dff: - dff_clk_pin = self.col_addr_dff_insts[port].get_pin("clk") - dff_clk_pos = dff_clk_pin.center() - mid_pos = vector(clk_steiner_pos.x, dff_clk_pos.y) - self.add_wire(self.m2_stack[::-1], - [dff_clk_pos, mid_pos, clk_steiner_pos]) - elif port in self.write_ports: - data_dff_clk_pin = self.data_dff_insts[port].get_pin("clk") - data_dff_clk_pos = data_dff_clk_pin.center() - mid_pos = vector(clk_steiner_pos.x, data_dff_clk_pos.y) - # In some designs, the steiner via will be too close to the mid_pos via - # so make the wire as wide as the contacts - self.add_path("m2", - [mid_pos, clk_steiner_pos], - width=max(self.m2_via.width, self.m2_via.height)) - self.add_wire(self.m2_stack[::-1], - [data_dff_clk_pos, mid_pos, clk_steiner_pos]) - - def route_control_logic(self): - """ - Route the control logic pins that are not inputs - """ - - for port in self.all_ports: - for signal in self.control_logic_outputs[port]: - # The clock gets routed separately and is not a part of the bank - if "clk" in signal: - continue - src_pin = self.control_logic_insts[port].get_pin(signal) - dest_pin = self.bank_inst.get_pin(signal + "{}".format(port)) - self.connect_vbus(src_pin, dest_pin) - - if self.has_rbl: - for port in self.all_ports: - # Only input (besides pins) is the replica bitline - src_pin = self.control_logic_insts[port].get_pin("rbl_bl") - dest_pin = self.bank_inst.get_pin("rbl_bl_{0}_{0}".format(port)) - self.add_wire(self.m3_stack, - [src_pin.center(), vector(src_pin.cx(), dest_pin.cy()), dest_pin.rc()]) - self.add_via_stack_center(from_layer=src_pin.layer, - to_layer="m4", - offset=src_pin.center()) - self.add_via_stack_center(from_layer=dest_pin.layer, - to_layer="m3", - offset=dest_pin.center()) - - def route_row_addr_dff(self): - """ - Connect the output of the row flops to the bank pins - """ - for port in self.all_ports: - for bit in range(self.row_addr_size): - flop_name = "dout_{}".format(bit) - bank_name = "addr{0}_{1}".format(port, bit + self.col_addr_size) - flop_pin = self.row_addr_dff_insts[port].get_pin(flop_name) - bank_pin = self.bank_inst.get_pin(bank_name) - flop_pos = flop_pin.center() - bank_pos = bank_pin.center() - mid_pos = vector(bank_pos.x, flop_pos.y) - self.add_via_stack_center(from_layer=flop_pin.layer, - to_layer="m3", - offset=flop_pos) - self.add_path("m3", [flop_pos, mid_pos]) - self.add_via_stack_center(from_layer=bank_pin.layer, - to_layer="m3", - offset=mid_pos) - self.add_path(bank_pin.layer, [mid_pos, bank_pos]) - - 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. - """ - return - for n in self.control_logic_outputs[0]: - pin = self.control_logic_insts[0].get_pin(n) - self.add_label(text=n, - layer=pin.layer, - offset=pin.center()) - - def graph_exclude_data_dff(self): - """ - Removes data dff and wmask dff (if applicable) from search graph. - """ - # 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 != self.word_size: - for inst in self.wmask_dff_insts: - self.graph_inst_exclude.add(inst) - if self.num_spare_cols: - for inst in self.spare_wen_dff_insts: - self.graph_inst_exclude.add(inst) - - def graph_exclude_addr_dff(self): - """ - Removes data dff from search graph. - """ - # Address is considered not part of the critical path, subjectively removed - for inst in self.row_addr_dff_insts: - self.graph_inst_exclude.add(inst) - - if self.col_addr_dff: - for inst in self.col_addr_dff_insts: - self.graph_inst_exclude.add(inst) - - def graph_exclude_ctrl_dffs(self): - """ - Exclude dffs for CSB, WEB, etc from graph - """ - # Insts located in control logic, exclusion function called here - for inst in self.control_logic_insts: - inst.mod.graph_exclude_dffs() - - def get_cell_name(self, inst_name, row, col): - """ - Gets the spice name of the target bitcell. - """ - # Sanity check in case it was forgotten - if inst_name.find("x") != 0: - inst_name = "x" + inst_name - return self.bank_inst.mod.get_cell_name(inst_name + "{}x".format(OPTS.hier_seperator) + self.bank_inst.name, row, col) - - def get_bank_num(self, inst_name, row, col): - return 0 diff --git a/compiler/router/io_pin_placer.py b/compiler/router/io_pin_placer.py index ae6677b9f..f3a416ab5 100644 --- a/compiler/router/io_pin_placer.py +++ b/compiler/router/io_pin_placer.py @@ -21,7 +21,7 @@ def __init__(self, layers, design, bbox=None): # New pins are the side supply pins self.new_pins = {} - + # added_io_pins self.io_pins_added_left = [] self.io_pins_added_right = [] @@ -30,7 +30,7 @@ def __init__(self, layers, design, bbox=None): # fake_pins, use for rename self.io_pins_fake =[] - + def get_closest_edge(self, point): """ Return a point's the closest edge and the edge's axis direction. """ @@ -52,9 +52,9 @@ def get_closest_edge(self, point): return "right", True return "top", False - - def initial_position(self, pins): # pins is list [] - """ Set the IO pin center at the perimeter """ + + def initial_position(self, pins): # pins is list [] + """ Set the IO pin center at the perimeter """ pattern_clk = r'^clk' pattern_addr0 = r'^addr0' pattern_addr1 = r'^addr1' @@ -82,14 +82,14 @@ def initial_position(self, pins): # pins is list [] vertical = True elif edge == "bottom": # but for big sram, addr0[] may have pins at bottom, which is allowed vertical = False - self.store_position(pin, edge, vertical) + self.store_position(pin, edge, vertical) if re.match(pattern_addr1, pin.name): # all the addr1[] should be placed at right edge if edge == "bottom" or edge == "right": edge = "right" vertical = True elif edge == "top": # but for big sram, addr1[] may have pins at top, which is allowed vertical = False - self.store_position(pin, edge, vertical) + self.store_position(pin, edge, vertical) if re.match(pattern_din, pin.name): # din self.store_position(pin, edge, vertical) if re.match(pattern_wmask, pin.name): # wmask @@ -100,7 +100,7 @@ def initial_position(self, pins): # pins is list [] self.store_position(pin, edge, vertical) if re.match(pattern_web, pin.name): # web self.store_position(pin, edge, vertical) - # special handle the dout pins, if r/rw ports + # special handle the dout pins, if r/rw ports for pin in pins: if re.match(pattern_dout0, pin.name): edge = "bottom" @@ -115,7 +115,7 @@ def initial_position(self, pins): # pins is list [] def check_overlap(self, pin_position, edge): """ Return the suggested position to aviod overlap """ ll, ur = self.bbox - c = pin_position # original source pin center + c = pin_position # original source pin center offset = 0.95 + 0.19 # FIX: this is the magic number to overcome the ovetflow problem at the boundary, may need a method add_distance = 0 @@ -124,26 +124,26 @@ def check_overlap(self, pin_position, edge): pin_to_close = any(abs(pin_added.center().x - fake_center.x) < (0.4 + self.half_wire * 4)for pin_added in self.io_pins_added_down) via_to_close = False # if cannot direct place below the source pin, need move towards right, and ensure the min. distance between vias - while pin_to_close or via_to_close: - debug.warning("overlap, changing position") - add_distance = add_distance + 0.1 + while pin_to_close or via_to_close: + debug.warning("overlap, changing position") + add_distance = add_distance + 0.1 fake_center = vector(c.x + add_distance, ll.y - self.track_wire * 2 + offset) - pin_to_close = any(abs(pin_added.center().x - fake_center.x) < (0.4 + self.half_wire * 4)for pin_added in self.io_pins_added_down) - via_to_close = abs(fake_center.x - c.x) < 0.6 + pin_to_close = any(abs(pin_added.center().x - fake_center.x) < (0.4 + self.half_wire * 4)for pin_added in self.io_pins_added_down) + via_to_close = abs(fake_center.x - c.x) < 0.6 if edge == "top": fake_center = vector(c.x, ur.y + self.track_wire * 2 - offset) pin_to_close = any(abs(pin_added.center().x - fake_center.x) < (0.4 + self.half_wire * 4)for pin_added in self.io_pins_added_up) - via_to_close = False - # if cannot direct place below the source pin, need move towards right, and ensure the min. distance between vias - while pin_to_close or via_to_close: + via_to_close = False + # if cannot direct place below the source pin, need move towards right, and ensure the min. distance between vias + while pin_to_close or via_to_close: debug.warning("overlap, changing position") add_distance = add_distance + 0.1 fake_center = vector(c.x + add_distance, ur.y + self.track_wire * 2 - offset) - pin_to_close = any(abs(pin_added.center().x - fake_center.x) < (0.4 + self.half_wire * 4)for pin_added in self.io_pins_added_up) - via_to_close = abs(fake_center.x - c.x) < 0.6 + pin_to_close = any(abs(pin_added.center().x - fake_center.x) < (0.4 + self.half_wire * 4)for pin_added in self.io_pins_added_up) + via_to_close = abs(fake_center.x - c.x) < 0.6 + + return fake_center - return fake_center - def store_dout_position(self, pin, edge, vertical): pin_position = pin.center() @@ -151,14 +151,14 @@ def store_dout_position(self, pin, edge, vertical): # store the center position, rect, layer of fake pin, here make sure the pin in the gds will be big enough layer = self.get_layer(int(not vertical)) half_wire_vector = vector([self.half_wire] * 2) - nll = pin_position - half_wire_vector - half_wire_vector + nll = pin_position - half_wire_vector - half_wire_vector nur = pin_position + half_wire_vector + half_wire_vector rect = [nll, nur] #fake_pin = [pin.name() + "_" + "fake", pin_position, rect, layer] fake_pin = graph_shape(name=pin.name + "_" + "fake", rect=rect, layer_name_pp=layer) - + if edge == "left": self.io_pins_added_left.append(fake_pin) elif edge == "bottom": @@ -168,10 +168,10 @@ def store_dout_position(self, pin, edge, vertical): elif edge == "top": self.io_pins_added_up.append(fake_pin) - self.io_pins_fake.append(fake_pin) + self.io_pins_fake.append(fake_pin) debug.warning("pin added: {0}".format(fake_pin)) - - + + def store_position(self, pin, edge, vertical): # also need to store the source pin ll, ur = self.bbox c = pin.center() @@ -197,14 +197,14 @@ def store_position(self, pin, edge, vertical): # also need to store the source p # store the center position, rect, layer of fake pin, here make sure the pin in the gds will be big enough layer = self.get_layer(int(not vertical)) half_wire_vector = vector([self.half_wire] * 2) - nll = fake_center - half_wire_vector - half_wire_vector + nll = fake_center - half_wire_vector - half_wire_vector nur = fake_center + half_wire_vector + half_wire_vector rect = [nll, nur] #fake_pin = [pin.name() + "_" + "fake", fake_center, rect, layer] fake_pin = graph_shape(name=pin.name + "_" + "fake", rect=rect, layer_name_pp=layer) - + if edge == "left": self.io_pins_added_left.append(fake_pin) elif edge == "bottom": @@ -213,111 +213,9 @@ def store_position(self, pin, edge, vertical): # also need to store the source p self.io_pins_added_right.append(fake_pin) elif edge == "top": self.io_pins_added_up.append(fake_pin) - + self.io_pins_fake.append(fake_pin) debug.warning("pin added: {0}".format(fake_pin)) - - - def create_fake_pin(self, pin): - """ Create a fake pin on the perimeter orthogonal to the given pin. """ - - ll, ur = self.bbox - c = pin.center() - print("inside pin name") - print("----------------------------------------------------------") - print(pin.name) - # Find the closest edge - edge, vertical = self.get_closest_edge(c) - # Relocate the pin position of addr/dout - pattern_addr = r'^addr' - pattern_dout = r'^dout' - if re.match(pattern_addr, pin.name):# all the addr[] should be placed at vertical edge - if edge == "top" or edge == "left": - edge = "left" - vertical = True - elif edge == "bottom" or edge == "right": - edge = "right" - vertical = True - - if re.match(pattern_dout, pin.name):# all the dout[] should be placed at horizontal edge - if edge == "bottom" or edge == "right": - edge = "bottom" - vertical = False - elif edge == "top" or edge == "left": - edge = "top" - vertical = False - - offset = 0.95 + 0.19 # FIX: this is the magic number to overcome the ovetflow problem at the boundary, may need a method - add_distance = 0 - # Keep the fake pin out of the SRAM layout are so that they won't be - # blocked by previous signals if they're on the same orthogonal line - if edge == "left": - fake_center = vector(ll.x - self.track_wire * 2 + offset, c.y) - is_too_close = any(abs(pin_added.center().y - fake_center.y) < (0.4 + self.half_wire * 4)for pin_added in self.io_pins_added_left) - while is_too_close: - debug.warning("overlap, changing position") - add_distance = add_distance + 0.1#+ 0.4 + self.half_wire * 4 - fake_center = vector(ll.x - self.track_wire * 2 + offset, c.y + add_distance) - is_too_close = any(abs(pin_added.center().y - fake_center.y) < (0.4 + self.half_wire * 4)for pin_added in self.io_pins_added_left) - - if edge == "bottom": - fake_center = vector(c.x, ll.y - self.track_wire * 2 + offset) - is_too_close = any(abs(pin_added.center().x - fake_center.x) < (0.4 + self.half_wire * 4)for pin_added in self.io_pins_added_down) - while is_too_close: - debug.warning("overlap, changing position") - add_distance = add_distance + 0.1#0.4 + self.half_wire * 4 - fake_center = vector(c.x + add_distance, ll.y - self.track_wire * 2 + offset) - is_too_close = any(abs(pin_added.center().x - fake_center.x) < (0.4 + self.half_wire * 4)for pin_added in self.io_pins_added_down) - - if edge == "right": - fake_center = vector(ur.x + self.track_wire * 2 - offset, c.y) - is_too_close = any(abs(pin_added.center().y - fake_center.y) < (0.4 + self.half_wire * 4)for pin_added in self.io_pins_added_right) - while is_too_close: - debug.warning("overlap, changing position") - add_distance = add_distance + 0.1#0.4 + self.half_wire * 4 - fake_center = vector(ur.x + self.track_wire * 2 - offset, c.y + add_distance) - # debug - for pin_added in self.io_pins_added_right: - dis = abs(pin_added.center().y - fake_center.y) - debug.warning("current position is {0}".format(fake_center)) - debug.warning("distance from {0} is {1}".format(pin_added, dis)) - debug.warning("must disrance is {0}".format(0.4 + self.half_wire * 4)) - is_too_close = any(abs(pin_added.center().y - fake_center.y) < (0.4 + self.half_wire * 4)for pin_added in self.io_pins_added_right) - - if edge == "top": - fake_center = vector(c.x, ur.y + self.track_wire * 2 - offset) - is_too_close = any(abs(pin_added.center().x - fake_center.x) < (0.4 + self.half_wire * 4)for pin_added in self.io_pins_added_up) - while is_too_close: - debug.warning("overlap, changing position") - add_distance = add_distance + 0.1#0.4 + self.half_wire * 4 - fake_center = vector(c.x + add_distance, ur.y + self.track_wire * 2 - offset) - is_too_close = any(abs(pin_added.center().x - fake_center.x) < (0.4 + self.half_wire * 4)for pin_added in self.io_pins_added_up) - - # Create the fake pin shape, here make sure the pin in the gds will be big enough - layer = self.get_layer(int(not vertical)) - half_wire_vector = vector([self.half_wire] * 2) - nll = fake_center - half_wire_vector - half_wire_vector - nur = fake_center + half_wire_vector + half_wire_vector - - rect = [nll, nur] - pin = graph_shape(name=pin.name + "_" + "fake", - rect=rect, - layer_name_pp=layer) - print("this create_fake_pin") - print(pin.name) - print(pin.center) - - if edge == "left": - self.io_pins_added_left.append(pin) - elif edge == "bottom": - self.io_pins_added_down.append(pin) - elif edge == "right": - self.io_pins_added_right.append(pin) - elif edge == "top": - self.io_pins_added_up.append(pin) - debug.warning("pin added: {0}".format(pin)) - - return pin, vertical, add_distance def add_io_pins(self, pin_names): @@ -336,13 +234,13 @@ def add_io_pins(self, pin_names): for name in self.pins: pin = next(iter(self.pins[name])) pin_list.append(pin) - - self.initial_position(pin_list) + + self.initial_position(pin_list) # Change IO pin names, which means "internal name" will be used, and internal io pins will not have label such as "dout0[0]" self.replace_layout_pins(pin_names) - - + + def add_io_pins_connected(self, pin_names): """ Add IO pins on the edges WITH routing them. """ debug.info(1, "Adding IO pins on the perimeter...") @@ -361,9 +259,9 @@ def add_io_pins_connected(self, pin_names): pin = next(iter(self.pins[name])) pin_list.append(pin) debug.warning("the pins in self.pins -> {0}".format(name)) - - self.initial_position(pin_list) - + + self.initial_position(pin_list) + # add fake io pins at the perimeter, which will be used for routing for fake_pin in self.io_pins_fake: self.design.add_layout_pin(text=fake_pin.name, @@ -372,7 +270,7 @@ def add_io_pins_connected(self, pin_names): width=fake_pin.width(), height=fake_pin.height()) - # connect the source_pin and io_pin(target) + # connect the source_pin and io_pin(target) self.connect_pins(pin_names) # remove the fake pin before change the name, in order to avoid possible problem @@ -381,8 +279,8 @@ def add_io_pins_connected(self, pin_names): # Change IO pin names, which means "internal name" will be used, and internal io pins will not have label such as "dout0[0]" self.replace_layout_pins(pin_names) - - + + def connect_pins(self, pin_names): # pin_names should be a list """ Add IO pins on the edges, and connect them to the internal one, not-graph like process """ debug.info(1, "connecting to io pins...") @@ -392,18 +290,18 @@ def connect_pins(self, pin_names): # pin_names should be a list source_pin = next(iter(self.pins[pin_name])) for fake_pin in self.io_pins_fake: if pin_name + "_" + "fake" == fake_pin.name: - target_pin = fake_pin - break + target_pin = fake_pin + break # special hanlde dout pins if re.match(pattern_dout, pin_name): number_str = re.findall(r'\[(\d+)\]', pin_name) if number_str: number = int(number_str[0]) if number % 2 == 0: - is_up = True + is_up = True else: is_up = False - point_list = self.decide_point(source_pin, target_pin, is_up) + point_list = self.decide_point(source_pin, target_pin, is_up) self.add_wire(point_list) # other pins else: @@ -414,7 +312,7 @@ def connect_pins(self, pin_names): # pin_names should be a list self.add_wire(point_list) - def add_wire(self, point_list): + def add_wire(self, point_list): if len(point_list) == 2: # direct connect self.add_line(point_list[0], point_list[1]) @@ -425,32 +323,32 @@ def add_wire(self, point_list): self.add_line(point_list[1], point_list[2]) self.add_via(point_list[2]) self.add_line(point_list[2], point_list[3]) - + def add_line(self, point_1, point_2): if round(point_1.y, 3) == round(point_2.y, 3): - # horizontal m3 + # horizontal m3 self.design.add_path(self.get_layer(False), [point_1, point_2]) else: # vertical m4 self.design.add_path(self.get_layer(True), [point_1, point_2]) - + def add_via(self, point): # currently only m3-m4 vias are supported in this method # usd in order to make "z" shape routing only - self.design.add_via_stack_center(from_layer=self.get_layer(False), - to_layer=self.get_layer(True), + self.design.add_via_stack_center(from_layer=self.get_layer(False), + to_layer=self.get_layer(True), offset=point) - + def add_start_via(self, point): # currently only m3-m4 vias are supported in this method # used in source_pin only - self.design.add_via_stack_center(from_layer=self.get_layer(False), - to_layer=self.get_layer(True), + self.design.add_via_stack_center(from_layer=self.get_layer(False), + to_layer=self.get_layer(True), offset=point) - + def add_big_plate(self, layer, offset, width, height): # add rectagle at internal source pin, which avoid jog/non-preferred routing, but could implement shift-routing @@ -460,19 +358,19 @@ def add_big_plate(self, layer, offset, width, height): offset=offset, width=width, height=height) - + def decide_point(self, source_pin, target_pin, is_up=False): ll, ur = self.bbox offset = 0.95 + 0.19 # FIX: this is the magic number to overcome the ovetflow problem at the boundary, may need a method pattern_clk = r'^clk' pattern_csb = r'^csb' - # internal -> left + # internal -> left if round(target_pin.center().x, 3) == round(ll.x - self.track_wire * 2 + offset, 3): # special handle clk0 if re.match(pattern_clk, source_pin.name): return [vector(source_pin.rc().x, source_pin.rc().y + 0.32), target_pin.lc()]# 0.32 should be same in initial_position - # special handel csb0 + # special handel csb0 elif re.match(pattern_csb, source_pin.name): return [vector(source_pin.rc().x, source_pin.rc().y - 0.32), target_pin.lc()]# 0.32 should be same in initial_position else: @@ -482,11 +380,11 @@ def decide_point(self, source_pin, target_pin, is_up=False): if round(target_pin.center().x, 3) == round(ur.x + self.track_wire * 2 - offset, 3): # special handel clk1 if re.match(pattern_clk, source_pin.name): - return [vector(source_pin.lc().x, source_pin.lc().y - 0.32), target_pin.rc()]# 0.32 should be same in initial_position - # special handle csb0 + return [vector(source_pin.lc().x, source_pin.lc().y - 0.32), target_pin.rc()]# 0.32 should be same in initial_position + # special handle csb0 elif re.match(pattern_csb, source_pin.name): - return [vector(source_pin.lc().x, source_pin.lc().y + 0.32), target_pin.rc()]# 0.32 should be same in initial_position - else: + return [vector(source_pin.lc().x, source_pin.lc().y + 0.32), target_pin.rc()]# 0.32 should be same in initial_position + else: # direct connect possible return [source_pin.lc(), target_pin.rc()] # internal -> top, need to add start_via m3->m4 @@ -499,13 +397,13 @@ def decide_point(self, source_pin, target_pin, is_up=False): # need intermediate point via_basic_y = self.design.bank.height + 3 # 3 is magic number, make sure out of bank area is_up = not is_up# Be attention, for channel at the top, the is_up should be inverted! Otherwise will cause overlap! - if is_up: + if is_up: via_basic_y = via_basic_y + 0.5 else: via_basic_y = via_basic_y - 0.5 point_1 = vector(source_pin.center().x, via_basic_y) point_2 = vector(target_pin.center().x, via_basic_y) - return [source_pin.bc(), point_1, point_2, target_pin.uc()] + return [source_pin.bc(), point_1, point_2, target_pin.uc()] # internal -> bottom, need to add start_via m3->m4 if round(target_pin.center().y, 3) == round(ll.y - self.track_wire * 2 + offset, 3): self.add_start_via(source_pin.center()) @@ -521,14 +419,14 @@ def decide_point(self, source_pin, target_pin, is_up=False): via_basic_y = via_basic_y - 0.5 point_1 = vector(source_pin.center().x, via_basic_y) point_2 = vector(target_pin.center().x, via_basic_y) - return [source_pin.uc(), point_1, point_2, target_pin.bc()] - - + return [source_pin.uc(), point_1, point_2, target_pin.bc()] + + def remove_io_pins(self, pin_name): # remove io pin in gds, so we could reroute self.design.remove_layout_pin(pin_name) - - + + def replace_layout_pins(self, pin_names): """ Change the IO pin names with new ones around the perimeter. """ for pin_name in pin_names: @@ -537,8 +435,7 @@ def replace_layout_pins(self, pin_names): if pin.name == perimeter_pin_name: perimeter_pin = pin self.design.replace_layout_pin(pin_name, perimeter_pin) - break - + break diff --git a/compiler/router/signal_escape_router.py b/compiler/router/signal_escape_router.py index 764001d1d..b965980c9 100644 --- a/compiler/router/signal_escape_router.py +++ b/compiler/router/signal_escape_router.py @@ -24,13 +24,13 @@ def __init__(self, layers, design, bbox=None, mod=0): # New pins are the side supply pins self.new_pins = {} - - # Use for add distance of dout pins at the perimeter + + # Use for add distance of dout pins at the perimeter self.distance_right = 0 self.distance_left = 0 - - # Use for control which edge/position the pins(dout) will be placed + + # Use for control which edge/position the pins(dout) will be placed # 0 -> default # 1 -> all top/bottom # 2 -> all left/right @@ -206,16 +206,16 @@ def create_fake_pin(self, pin): fake_center = vector(ur.x + self.track_wire * 2, c.y) if edge == "top": fake_center = vector(c.x, ur.y + self.track_wire * 2) - + # relocate the pin position pattern = r'^dout' if re.match(pattern, pin.name): - + if self.state_mod == 0: pass# do not change, default - + elif self.state_mod == 1: # all top/bottom - if edge == "right": + if edge == "right": vertical = False fake_center = vector(c.x, ll.y - self.track_wire * 2) self.distance_right += 1 @@ -223,8 +223,8 @@ def create_fake_pin(self, pin): if edge == "left": vertical = False fake_center = vector(c.x, ll.y + self.track_wire * 2) - self.distance_left += 1 - + self.distance_left += 1 + elif self.state_mod == 2: # all left/right if (edge == "bottom") or (edge == "right"):# change to the east vertical = True @@ -234,10 +234,10 @@ def create_fake_pin(self, pin): if (edge == "top") or (edge == "left"):# change to the west vertical = True fake_center = vector(ll.x - self.track_wire * 2, ur.y - 30 - self.distance_left) - self.distance_left += 1 + self.distance_left += 1 else: - debug.error("wrong state mod!", -1) - + debug.error("wrong state mod!", -1) + # Create the fake pin shape layer = self.get_layer(int(not vertical)) half_wire_vector = vector([self.half_wire] * 2) diff --git a/compiler/sram.py b/compiler/sram.py index fcce2acdd..997f57469 100644 --- a/compiler/sram.py +++ b/compiler/sram.py @@ -20,7 +20,7 @@ class sram(): results. We can later add visualizer and other high-level functions as needed. """ - def __init__(self, sram_config=None, name=None): + def __init__(self, sram_config=None, name=None, route_option="classic"): # Create default configs if custom config isn't provided if sram_config is None: @@ -48,67 +48,74 @@ def __init__(self, sram_config=None, name=None): start_time = datetime.datetime.now() self.name = name + self.route_option = route_option # "classic" or "fast" from openram.modules.sram_1bank import sram_1bank as sram num_ports = OPTS.num_rw_ports + OPTS.num_r_ports + OPTS.num_w_ports self.s = sram(name, sram_config) self.s.create_netlist()# not placed & routed jet - cur_state = "IDLE" - if not OPTS.netlist_only: - i = 0 - while i < (OPTS.word_size + 100): - - debug.warning("current state: state = {0}".format(cur_state)) - - if cur_state == "IDLE":# default, fisrt try - try: - self.s.create_layout(position_add=i) - except AssertionError as e: - cur_state = "ALL_TOP_BOTTOM" - del self.s - self.s = sram(name, sram_config) - self.s.create_netlist() - if num_ports > 1: - i = 16 - else: - i = 0 - continue - cur_state = "FINISH" - break - - elif cur_state == "ALL_TOP_BOTTOM": - try: - self.s.create_layout(position_add=i, mod=1) - except AssertionError as e: - cur_state = "ALL_LEFT_RIGHT" - i = OPTS.word_size + 24 - del self.s - self.s = sram(name, sram_config) - self.s.create_netlist() - continue - cur_state = "FINISH" - break - - elif cur_state == "ALL_LEFT_RIGHT": - try: - self.s.create_layout(position_add=i, mod=2) - except AssertionError as e: - cur_state = "ALL_LEFT_RIGHT" - i = i + 1 - if i == (99 + OPTS.word_size):# failed in rounting - debug.error("Failed in rounting", -1) - break - del self.s - self.s = sram(name, sram_config) - self.s.create_netlist() - continue - cur_state = "FINISH" - break - else: - cur_state = "FINISH" - break - + + # choose the routung method, maze router or constructive + if self.route_option == "classic": + cur_state = "IDLE" + if not OPTS.netlist_only: + i = 0 + while i < (OPTS.word_size + 100): + + debug.warning("current state: state = {0}".format(cur_state)) + + if cur_state == "IDLE":# default, fisrt try + try: + self.s.create_layout(position_add=i) + except AssertionError as e: + cur_state = "ALL_TOP_BOTTOM" + del self.s + self.s = sram(name, sram_config) + self.s.create_netlist() + if num_ports > 1: + i = 16 + else: + i = 0 + continue + cur_state = "FINISH" + break + + elif cur_state == "ALL_TOP_BOTTOM": + try: + self.s.create_layout(position_add=i, mod=1) + except AssertionError as e: + cur_state = "ALL_LEFT_RIGHT" + i = OPTS.word_size + 24 + del self.s + self.s = sram(name, sram_config) + self.s.create_netlist() + continue + cur_state = "FINISH" + break + + elif cur_state == "ALL_LEFT_RIGHT": + try: + self.s.create_layout(position_add=i, mod=2) + except AssertionError as e: + cur_state = "ALL_LEFT_RIGHT" + i = i + 1 + if i == (99 + OPTS.word_size):# failed in rounting + debug.error("Failed in rounting", -1) + break + del self.s + self.s = sram(name, sram_config) + self.s.create_netlist() + continue + cur_state = "FINISH" + break + else: + cur_state = "FINISH" + break + elif self.route_option == "fast": + if not OPTS.netlist_only: + self.s.create_layout(route_option=route_option) + if not OPTS.is_unit_test: print_time("SRAM creation", datetime.datetime.now(), start_time) @@ -162,7 +169,7 @@ def save(self): spname = OPTS.output_path + self.s.name + ".sp" debug.print_raw("SP: Writing to {0}".format(spname)) self.sp_write(spname) - ''' #comment the following state when generating big sram, and then disable drc/lvs, because maigc_ext stuck + #''' #comment the following state when generating big sram, and then disable drc/lvs, because maigc_ext stuck # Save a functional simulation file with default period functional(self.s, spname, @@ -184,7 +191,7 @@ def save(self): d.targ_write_ports = [self.s.write_ports[0]] d.write_delay_stimulus() print_time("DELAY", datetime.datetime.now(), start_time) - ''' #comment the above when generating big sram, and then disable drc/lvs, bevause magic_ext stuck + #''' #comment the above when generating big sram, and then disable drc/lvs, bevause magic_ext stuck # Save trimmed spice file temp_trim_sp = "{0}trimmed.sp".format(OPTS.output_path) self.sp_write(temp_trim_sp, lvs=False, trim=True) diff --git a/compiler/sram_openroad_test.py b/compiler/sram_openroad_test.py deleted file mode 100644 index 98ee1583a..000000000 --- a/compiler/sram_openroad_test.py +++ /dev/null @@ -1,247 +0,0 @@ -# See LICENSE for licensing information. -# -# Copyright (c) 2016-2024 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 os -import shutil -import datetime -from openram import debug -from openram import sram_config as config -from openram import OPTS, print_time - - -class sram(): - """ - This is not a design module, but contains an SRAM design instance. - It could later try options of number of banks and organization to compare - results. - We can later add visualizer and other high-level functions as needed. - """ - def __init__(self, sram_config=None, name=None): - - # Create default configs if custom config isn't provided - if sram_config is None: - sram_config = config(word_size=OPTS.word_size, - num_words=OPTS.num_words, - write_size=OPTS.write_size, - num_banks=OPTS.num_banks, - words_per_row=OPTS.words_per_row, - num_spare_rows=OPTS.num_spare_rows, - num_spare_cols=OPTS.num_spare_cols) - - if name is None: - name = OPTS.output_name - - sram_config.set_local_config(self) - - # reset the static duplicate name checker for unit tests - # in case we create more than one SRAM - from openram.base import design - design.name_map=[] - - debug.info(2, "create sram of size {0} with {1} num of words {2} banks".format(self.word_size, - self.num_words, - self.num_banks)) - start_time = datetime.datetime.now() - - self.name = name - - from openram.modules.sram_openroad import sram_1bank as sram - #from openram.modules.sram_new import sram_1bank as sram - self.s = sram(name, sram_config) - - self.s.create_netlist() - if not OPTS.netlist_only: - self.s.create_layout_openroad() - - if not OPTS.is_unit_test: - print_time("SRAM creation", datetime.datetime.now(), start_time) - - def get_sp_name(self): - if OPTS.use_pex: - # Use the extracted spice file - return self.pex_name - else: - # Use generated spice file for characterization - return self.sp_name - - def sp_write(self, name, lvs=False, trim=False): - self.s.sp_write(name, lvs, trim) - - def lef_write(self, name): - self.s.lef_write(name) - - def gds_write(self, name): - self.s.gds_write(name) - - def verilog_write(self, name): - self.s.verilog_write(name) - if self.num_banks != 1: - from openram.modules.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. - Include defaults and anything changed by input config.""" - f = open(name, "w") - var_dict = dict((name, getattr(OPTS, name)) for name in dir(OPTS) if not name.startswith('__') and not callable(getattr(OPTS, name))) - for var_name, var_value in var_dict.items(): - if isinstance(var_value, str): - f.write(str(var_name) + " = " + "\"" + str(var_value) + "\"\n") - else: - f.write(str(var_name) + " = " + str(var_value)+ "\n") - f.close() - - def save_only(self): - if not OPTS.netlist_only: - # Write the layout - start_time = datetime.datetime.now() - gdsname = OPTS.output_path + self.s.name + ".gds" - debug.print_raw("GDS: Writing to {0}".format(gdsname)) - self.gds_write(gdsname) - if OPTS.check_lvsdrc: - verify.write_drc_script(cell_name=self.s.name, - gds_name=os.path.basename(gdsname), - extract=True, - final_verification=True, - output_path=OPTS.output_path) - print_time("GDS", datetime.datetime.now(), start_time) - - # Create a LEF physical model - start_time = datetime.datetime.now() - lefname = OPTS.output_path + self.s.name + ".lef" - debug.print_raw("LEF: Writing to {0}".format(lefname)) - self.lef_write(lefname) - print_time("LEF", datetime.datetime.now(), start_time) - - def save(self): - """ Save all the output files while reporting time to do it as well. """ - - # Import this at the last minute so that the proper tech file - # is loaded and the right tools are selected - from openram import verify - from openram.characterizer import functional - from openram.characterizer import delay - - # Save the spice file - start_time = datetime.datetime.now() - spname = OPTS.output_path + self.s.name + ".sp" - debug.print_raw("SP: Writing to {0}".format(spname)) - self.sp_write(spname) - - # Save a functional simulation file with default period - functional(self.s, - spname, - cycles=200, - output_path=OPTS.output_path) - print_time("Spice writing", datetime.datetime.now(), start_time) - - # Save stimulus and measurement file - start_time = datetime.datetime.now() - debug.print_raw("DELAY: Writing stimulus...") - d = delay(self.s, spname, ("TT", 5, 25), output_path=OPTS.output_path) - if (self.s.num_spare_rows == 0): - probe_address = "1" * self.s.addr_size - else: - probe_address = "0" + "1" * (self.s.addr_size - 1) - probe_data = self.s.word_size - 1 - d.analysis_init(probe_address, probe_data) - d.targ_read_ports.extend(self.s.read_ports) - d.targ_write_ports = [self.s.write_ports[0]] - d.write_delay_stimulus() - print_time("DELAY", datetime.datetime.now(), start_time) - - # Save trimmed spice file - temp_trim_sp = "{0}trimmed.sp".format(OPTS.output_path) - self.sp_write(temp_trim_sp, lvs=False, trim=True) - - if not OPTS.netlist_only: - # Write the layout - start_time = datetime.datetime.now() - gdsname = OPTS.output_path + self.s.name + ".gds" - debug.print_raw("GDS: Writing to {0}".format(gdsname)) - self.gds_write(gdsname) - if OPTS.check_lvsdrc: - verify.write_drc_script(cell_name=self.s.name, - gds_name=os.path.basename(gdsname), - extract=True, - final_verification=True, - output_path=OPTS.output_path) - print_time("GDS", datetime.datetime.now(), start_time) - - # Create a LEF physical model - start_time = datetime.datetime.now() - lefname = OPTS.output_path + self.s.name + ".lef" - debug.print_raw("LEF: Writing to {0}".format(lefname)) - self.lef_write(lefname) - print_time("LEF", datetime.datetime.now(), start_time) - - # Save the LVS file - start_time = datetime.datetime.now() - lvsname = OPTS.output_path + self.s.name + ".lvs.sp" - debug.print_raw("LVS: Writing to {0}".format(lvsname)) - self.sp_write(lvsname, lvs=True) - if not OPTS.netlist_only and OPTS.check_lvsdrc: - verify.write_lvs_script(cell_name=self.s.name, - gds_name=os.path.basename(gdsname), - sp_name=os.path.basename(lvsname), - final_verification=True, - output_path=OPTS.output_path) - print_time("LVS writing", datetime.datetime.now(), start_time) - - # Save the extracted spice file - if OPTS.use_pex: - start_time = datetime.datetime.now() - # Output the extracted design if requested - pexname = OPTS.output_path + self.s.name + ".pex.sp" - spname = OPTS.output_path + self.s.name + ".sp" - verify.run_pex(self.s.name, gdsname, spname, output=pexname) - sp_file = pexname - print_time("Extraction", datetime.datetime.now(), start_time) - else: - # Use generated spice file for characterization - sp_file = spname - - # Characterize the design - start_time = datetime.datetime.now() - from openram.characterizer import lib - debug.print_raw("LIB: Characterizing... ") - lib(out_dir=OPTS.output_path, sram=self.s, sp_file=sp_file) - print_time("Characterization", datetime.datetime.now(), start_time) - - # Write the config file - start_time = datetime.datetime.now() - try: - from shutil import copyfile - copyfile(OPTS.config_file, OPTS.output_path + OPTS.output_name + '.py') - except shutil.SameFileError: - pass - debug.print_raw("Config: Writing to {0}".format(OPTS.output_path + OPTS.output_name + '.py')) - print_time("Config", datetime.datetime.now(), start_time) - - # Write the datasheet - start_time = datetime.datetime.now() - from openram.datasheet import datasheet_gen - dname = OPTS.output_path + self.s.name + ".html" - debug.print_raw("Datasheet: Writing to {0}".format(dname)) - datasheet_gen.datasheet_write(dname) - print_time("Datasheet", datetime.datetime.now(), start_time) - - # Write a verilog model - start_time = datetime.datetime.now() - 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) - - # Write out options if specified - if OPTS.output_extended_config: - start_time = datetime.datetime.now() - oname = OPTS.output_path + OPTS.output_name + "_extended.py" - debug.print_raw("Extended Config: Writing to {0}".format(oname)) - self.extended_config_write(oname) - print_time("Extended Config", datetime.datetime.now(), start_time) diff --git a/compiler/sram_road.py b/compiler/sram_road.py deleted file mode 100644 index 87e133de5..000000000 --- a/compiler/sram_road.py +++ /dev/null @@ -1,268 +0,0 @@ -# See LICENSE for licensing information. -# -# Copyright (c) 2016-2024 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. -# -# This file generate sram part, aiming at using openroad to do the P&R -import os -import shutil -import datetime -from openram import debug -from openram import sram_config as config -from openram import OPTS, print_time - - -class sram(): - """ - This is not a design module, but contains an SRAM design instance. - It could later try options of number of banks and organization to compare - results. - We can later add visualizer and other high-level functions as needed. - """ - def __init__(self, sram_config=None, name=None, mod=0): - - # Create default configs if custom config isn't provided - if sram_config is None: - sram_config = config(word_size=OPTS.word_size, - num_words=OPTS.num_words, - write_size=OPTS.write_size, - num_banks=OPTS.num_banks, - words_per_row=OPTS.words_per_row, - num_spare_rows=OPTS.num_spare_rows, - num_spare_cols=OPTS.num_spare_cols) - # maybe this part should be put after set_local_config, so we could calculate the number of port and define file name - if name is None: - #name = OPTS.output_name - if mod == 0: - name = OPTS.output_name + "_bank_" + "0" - elif mod == 1:# not consider multi-port yet!!!!!! - name = OPTS.output_name + "_control_" + "0" - elif mod == 2: - name = OPTS.output_name + "_row_addr_dff_" + "0" - elif mod == 3: - name = OPTS.output_name + "_col_addr_dff_" + "0" - elif mod == 4: - name = OPTS.output_name + "_data_dff_" + "0" - elif mod == 5: - name = OPTS.output_name + "_wmask_dff_" + "0" - elif mod == 6: - name = OPTS.output_name + "_spare_wen_dff_" + "0" - - - sram_config.set_local_config(self) - - # reset the static duplicate name checker for unit tests - # in case we create more than one SRAM - from openram.base import design - design.name_map=[] - - debug.info(2, "create sram of size {0} with {1} num of words {2} banks".format(self.word_size, - self.num_words, - self.num_banks)) - start_time = datetime.datetime.now() - - self.name = name - - #from openram.modules.sram_1bank import sram_1bank as sram - from openram.modules.sram_for_road import sram_for_road as sram - - self.s = sram(name, sram_config) - - def get_sp_name(self): - if OPTS.use_pex: - # Use the extracted spice file - return self.pex_name - else: - # Use generated spice file for characterization - return self.sp_name - - def sp_write(self, name, lvs=False, trim=False): - self.s.sp_write(name, lvs, trim) - - def lef_write(self, name): - self.s.lef_write(name) - - def gds_write(self, name): - self.s.gds_write(name) - - def verilog_write(self, name): - self.s.verilog_write(name) - if self.num_banks != 1: - from openram.modules.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. - Include defaults and anything changed by input config.""" - f = open(name, "w") - var_dict = dict((name, getattr(OPTS, name)) for name in dir(OPTS) if not name.startswith('__') and not callable(getattr(OPTS, name))) - for var_name, var_value in var_dict.items(): - if isinstance(var_value, str): - f.write(str(var_name) + " = " + "\"" + str(var_value) + "\"\n") - else: - f.write(str(var_name) + " = " + str(var_value)+ "\n") - f.close() - - def generate_files(self): - """use to generate gds, lef files for one certain layout""" - # Import this at the last minute so that the proper tech file - # is loaded and the right tools are selected - from openram import verify - from openram.characterizer import functional - from openram.characterizer import delay - - # Save the spice file - start_time = datetime.datetime.now() - spname = OPTS.output_path + self.s.name + ".sp" - debug.print_raw("SP: Writing to {0}".format(spname)) - self.sp_write(spname) - - print_time("Spice writing", datetime.datetime.now(), start_time) - - # Save trimmed spice file - temp_trim_sp = "{0}trimmed.sp".format(OPTS.output_path) - self.sp_write(temp_trim_sp, lvs=False, trim=True) - - if not OPTS.netlist_only: - # Write the layout - start_time = datetime.datetime.now() - gdsname = OPTS.output_path + self.s.name + ".gds" - debug.print_raw("GDS: Writing to {0}".format(gdsname)) - self.gds_write(gdsname) - if OPTS.check_lvsdrc: - verify.write_drc_script(cell_name=self.s.name, - gds_name=os.path.basename(gdsname), - extract=True, - final_verification=True, - output_path=OPTS.output_path) - print_time("GDS", datetime.datetime.now(), start_time) - - # Create a LEF physical model - start_time = datetime.datetime.now() - lefname = OPTS.output_path + self.s.name + ".lef" - debug.print_raw("LEF: Writing to {0}".format(lefname)) - self.lef_write(lefname) - print_time("LEF", datetime.datetime.now(), start_time) - - # Save the LVS file - start_time = datetime.datetime.now() - lvsname = OPTS.output_path + self.s.name + ".lvs.sp" - debug.print_raw("LVS: Writing to {0}".format(lvsname)) - self.sp_write(lvsname, lvs=True) - if not OPTS.netlist_only and OPTS.check_lvsdrc: - verify.write_lvs_script(cell_name=self.s.name, - gds_name=os.path.basename(gdsname), - sp_name=os.path.basename(lvsname), - final_verification=True, - output_path=OPTS.output_path) - print_time("LVS writing", datetime.datetime.now(), start_time) - - # Save the extracted spice file - if OPTS.use_pex: - start_time = datetime.datetime.now() - # Output the extracted design if requested - pexname = OPTS.output_path + self.s.name + ".pex.sp" - spname = OPTS.output_path + self.s.name + ".sp" - verify.run_pex(self.s.name, gdsname, spname, output=pexname) - sp_file = pexname - print_time("Extraction", datetime.datetime.now(), start_time) - else: - # Use generated spice file for characterization - sp_file = spname - - # Write the config file - start_time = datetime.datetime.now() - try: - from shutil import copyfile - copyfile(OPTS.config_file, OPTS.output_path + OPTS.output_name + '.py') - except shutil.SameFileError: - pass - debug.print_raw("Config: Writing to {0}".format(OPTS.output_path + OPTS.output_name + '.py')) - print_time("Config", datetime.datetime.now(), start_time) - - - # Write a verilog model - start_time = datetime.datetime.now() - 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) - - # Write out options if specified - if OPTS.output_extended_config: - start_time = datetime.datetime.now() - oname = OPTS.output_path + OPTS.output_name + "_extended.py" - debug.print_raw("Extended Config: Writing to {0}".format(oname)) - self.extended_config_write(oname) - print_time("Extended Config", datetime.datetime.now(), start_time) - - - def save(self, mod=0): - """ Save all the output files while reporting time to do it as well. """ - if mod == 0: - self.s.create_netlist_bank() - if not OPTS.netlist_only: - self.s.create_layout_bank_only() - self.generate_files() - elif mod == 1: - self.s.create_netlist_control() - if not OPTS.netlist_only: - for port in self.s.all_ports: - self.s.create_layout_control_only(instance_index=port) - self.generate_files() - else: - for port in self.s.all_ports: - self.generate_files() - elif mod == 2: - self.s.create_netlist_row_addr_dff() - if not OPTS.netlist_only: - for port in self.s.all_ports: - self.s.create_layout_row_addr_dff_only(instance_index=port) - self.generate_files() - else: - for port in self.s.all_ports: - self.generate_files() - elif mod == 3: - if self.s.create_netlist_col_addr_dff() == False: - pass#continue # do not need col addr dff - elif not OPTS.netlist_only: - for port in self.s.all_ports: - self.create_layout_col_addr_dff_only(instance_index=port) - self.generate_files() - else: - self.generate_files() - elif mod == 4: - self.s.create_netlist_data_dff() - if not OPTS.netlist_only: - for port in self.s.all_ports: - self.s.create_layout_data_dff_only(instance_index=port) - self.generate_files() - else: - for port in self.s.all_ports: - self.generate_files() - elif mod == 5: - if self.s.create_netlist_wmask_dff() == False: - pass#continue # do not need wmask dff - elif not OPTS.netlist_only: - for port in self.s.all_ports: - self.s.create_layout_wmask_dff_only(instance_index=port) - self.generate_files() - else: - for port in self.s.all_ports: - self.generate_files() - elif mod == 6: - if self.s.create_netlist_spare_wen_dff() == False: - pass#continue # do not need spare wen dff - elif not OPTS.netlist_only: - for port in self.s.all_ports: - self.s.create_layout_spare_wen_dff_only(instance_index=port) - self.generate_files() - else: - for port in self.s.all_ports: - self.generate_files() - - - diff --git a/sram_compiler.py b/sram_compiler.py index e1404745a..9d5fc4b3b 100755 --- a/sram_compiler.py +++ b/sram_compiler.py @@ -70,40 +70,10 @@ # Create an SRAM (we can also pass sram_config, see documentation/tutorials for details) from openram import sram -s = sram() -#from openram import sram_openroad_test -#s = sram_openroad_test.sram() +s = sram(route_option="classic")# "classic" or "fast" # Output the files for the resulting SRAM s.save() -#s.save_only() -''' -from openram import sram_road -s = sram_road.sram(mod=0) -s.save(mod=0) -del s -s = sram_road.sram(mod=1) -s.save(mod=1) - -del s -s = sram_road.sram(mod=2) -s.save(mod=2) - -del s -s = sram_road.sram(mod=3) -s.save(mod=3) - -del s -s = sram_road.sram(mod=4) -s.save(mod=4) - -del s -s = sram_road.sram(mod=5) -s.save(mod=5) - -del s -s = sram_road.sram(mod=6) -s.save(mod=6) -''' + # Delete temp files etc. openram.end_openram() openram.print_time("End", datetime.datetime.now(), start_time) From 86588619fd7ae20dda436726f92e4d102dc17c08 Mon Sep 17 00:00:00 2001 From: FriedrichWu Date: Sun, 17 Nov 2024 10:35:01 +0100 Subject: [PATCH 17/26] first commt --- compiler/base/hierarchy_layout.py | 101 +++++++-- compiler/modules/sram_1bank.py | 62 ++++-- compiler/router/io_pin_placer.py | 2 +- compiler/router/router.py | 48 +++++ compiler/router/supply_placer.py | 344 ++++++++++++++++++++++++++++++ compiler/router/supply_router.py | 2 +- sram_compiler.py | 2 +- 7 files changed, 522 insertions(+), 39 deletions(-) create mode 100644 compiler/router/supply_placer.py diff --git a/compiler/base/hierarchy_layout.py b/compiler/base/hierarchy_layout.py index 31802ee70..bdb7258bb 100644 --- a/compiler/base/hierarchy_layout.py +++ b/compiler/base/hierarchy_layout.py @@ -1913,6 +1913,42 @@ def add_io_pin(self, instance, pin_name, new_name, start_layer=None, directions= # Just use the power pin function for now to save code self.add_power_pin(new_name, pin.center(), start_layer=start_layer, directions=directions) + def add_power_pin_no_via(self, name, loc, directions=None, start_layer="m1"): + # same function like normal one, but do not add via to the gird layer + # Hack for min area + if OPTS.tech_name == "sky130": + min_area = drc["minarea_{}".format(self.pwr_grid_layers[1])] + width = round_to_grid(sqrt(min_area)) + height = round_to_grid(min_area / width) + else: + width = None + height = None + + pin = None + if start_layer in self.pwr_grid_layers: + pin = self.add_layout_pin_rect_center(text=name, + layer=start_layer, + offset=loc, + width=width, + height=height) + else: + via = self.add_via_stack_center(from_layer=start_layer, + to_layer=start_layer,# so only enclosure shape will be added + offset=loc, + directions=directions) + + if not width: + width = via.width + if not height: + height = via.height + pin = self.add_layout_pin_rect_center(text=name, + layer=start_layer, + offset=loc, + width=width, + height=height) + + return pin + def add_power_pin(self, name, loc, directions=None, start_layer="m1"): # Hack for min area if OPTS.tech_name == "sky130": @@ -2027,7 +2063,7 @@ def add_perimeter_pin(self, name, pin, side, bbox): layer=layer, offset=peri_pin_loc) - def add_dnwell(self, bbox=None, inflate=1): + def add_dnwell(self, bbox=None, inflate=1, add_vias=True): """ Create a dnwell, along with nwell moat at border. """ if "dnwell" not in tech_layer: @@ -2049,11 +2085,20 @@ def add_dnwell(self, bbox=None, inflate=1): ul = vector(ll.x, ur.y) lr = vector(ur.x, ll.y) - # Add the dnwell - self.add_rect("dnwell", - offset=ll, - height=ur.y - ll.y, - width=ur.x - ll.x) + # Hack for sky130 klayout drc rule nwell.6 + if OPTS.tech_name == "sky130": + # Apply the drc rule + # Add the dnwell + self.add_rect("dnwell", + offset=ll - vector(0.5 * self.nwell_width, 0.5 * self.nwell_width) - vector(drc["minclosure_nwell_by_dnwell"], drc["minclosure_nwell_by_dnwell"]), + height=ur.y - ll.y + self.nwell_width + 2 * drc["minclosure_nwell_by_dnwell"], + width=ur.x - ll.x + self.nwell_width + 2 * drc["minclosure_nwell_by_dnwell"]) + else: # other tech + # Add the dnwell + self.add_rect("dnwell", + offset=ll, + height=ur.y - ll.y, + width=ur.x - ll.x) # Add the moat self.add_path("nwell", [ll, lr, ur, ul, ll - vector(0, 0.5 * self.nwell_width)]) @@ -2080,9 +2125,14 @@ def add_dnwell(self, bbox=None, inflate=1): to_layer="m1", offset=loc) else: - self.add_power_pin(name="vdd", - loc=loc, - start_layer="li") + if add_vias: + self.add_power_pin(name="vdd", + loc=loc, + start_layer="li") + else: + self.add_power_pin_no_via(name="vdd", + loc=loc, + start_layer="li") count += 1 loc += nwell_offset.scale(tap_spacing, 0) @@ -2100,9 +2150,14 @@ def add_dnwell(self, bbox=None, inflate=1): to_layer="m1", offset=loc) else: - self.add_power_pin(name="vdd", - loc=loc, - start_layer="li") + if add_vias: + self.add_power_pin(name="vdd", + loc=loc, + start_layer="li") + else: + self.add_power_pin_no_via(name="vdd", + loc=loc, + start_layer="li") count += 1 loc += nwell_offset.scale(tap_spacing, 0) @@ -2120,9 +2175,14 @@ def add_dnwell(self, bbox=None, inflate=1): to_layer="m2", offset=loc) else: - self.add_power_pin(name="vdd", - loc=loc, - start_layer="li") + if add_vias: + self.add_power_pin(name="vdd", + loc=loc, + start_layer="li") + else: + self.add_power_pin_no_via(name="vdd", + loc=loc, + start_layer="li") count += 1 loc += nwell_offset.scale(0, tap_spacing) @@ -2140,9 +2200,14 @@ def add_dnwell(self, bbox=None, inflate=1): to_layer="m2", offset=loc) else: - self.add_power_pin(name="vdd", - loc=loc, - start_layer="li") + if add_vias: + self.add_power_pin(name="vdd", + loc=loc, + start_layer="li") + else: + self.add_power_pin_no_via(name="vdd", + loc=loc, + start_layer="li") count += 1 loc += nwell_offset.scale(0, tap_spacing) diff --git a/compiler/modules/sram_1bank.py b/compiler/modules/sram_1bank.py index 3fada4d53..88349dcf9 100644 --- a/compiler/modules/sram_1bank.py +++ b/compiler/modules/sram_1bank.py @@ -53,6 +53,9 @@ def __init__(self, name, sram_config): # delay control logic does not have RBLs self.has_rbl = OPTS.control_logic != "control_logic_delay" + # IO pins, except power, list of pin names + self.pins_to_route = [] + def add_pins(self): """ Add pins for entire SRAM. """ @@ -244,6 +247,31 @@ def create_layout(self, position_add=0, mod=0, route_option="classic"): def create_modules(self): debug.error("Must override pure virtual function.", -1) + def route_supplies_constructive(self, bbox=None): + # prepare the "router" + from openram.router.supply_placer import supply_placer as router + rtr = router(layers=self.supply_stack, + design=self, + bbox=bbox, + pin_type=OPTS.supply_pin_type, + ext_vdd_name=self.vdd_name, + ext_gnd_name=self.gnd_name) + # add power rings / side pins + if OPTS.supply_pin_type in ["top", "bottom", "right", "left"]: + rtr.add_side_pin(self.vdd_name) + rtr.add_side_pin(self.gnd_name) + elif OPTS.supply_pin_type == "ring": + rtr.add_ring_pin(self.vdd_name)# ring vdd name + rtr.add_ring_pin(self.gnd_name) + else: + debug.warning("Side supply pins aren't created.") + + # maze router the bank power pins + for pin_name in ["vdd", "gnd"]: + for inst in self.bank_insts: + self.copy_power_pins(inst, pin_name) + rtr.route_bank() + def route_supplies(self, bbox=None): """ Route the supply grid and connect the pins to them. """ @@ -324,44 +352,41 @@ def route_escape_pins(self, bbox=None, mod=0, route_option="classic"): """ 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)) + self.pins_to_route.append("{0}{1}".format(signal, port)) else: - pins_to_route.append("{0}{1}".format(signal, port)) + self.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)) + self.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)) + self.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)) + self.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)) + self.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)) + self.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)) + self.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)) + self.pins_to_route.append("spare_wen{0}[{1}]".format(port, bit)) if route_option == "classic": from openram.router import signal_escape_router as router @@ -373,7 +398,7 @@ def route_escape_pins(self, bbox=None, mod=0, route_option="classic"): bbox=bbox, design=self, mod=mod) - rtr.route(pins_to_route) + rtr.route(self.pins_to_route) elif route_option == "fast": # use io_pin_placer # put the IO pins at the edge @@ -381,10 +406,10 @@ def route_escape_pins(self, bbox=None, mod=0, route_option="classic"): pl = placer(layers=self.m3_stack, bbox=bbox, design=self) - for name in pins_to_route: + for name in self.pins_to_route: debug.warning("pins_to_route pins -> {0}".format(name)) - pl.add_io_pins_connected(pins_to_route) - #pl.add_io_pins(pins_to_route) + pl.add_io_pins_connected(self.pins_to_route) + #pl.add_io_pins(self.pins_to_route) def compute_bus_sizes(self): """ Compute the independent bus widths shared between two and four bank SRAMs """ @@ -1089,7 +1114,7 @@ def route_layout(self, mod=0, route_option="classic"): self.add_layout_pins() # Some technologies have an isolation - self.add_dnwell(inflate=2.5) + self.add_dnwell(inflate=2.5, add_vias=False) init_bbox = self.get_bbox() # Route the supplies together and/or to the ring/stripes. @@ -1100,7 +1125,8 @@ def route_layout(self, mod=0, route_option="classic"): self.route_escape_pins(bbox=init_bbox, mod=mod, route_option=route_option) if OPTS.route_supplies: - self.route_supplies(init_bbox) + #self.route_supplies(init_bbox) + self.route_supplies_constructive(init_bbox) def route_dffs(self, add_routes=True): diff --git a/compiler/router/io_pin_placer.py b/compiler/router/io_pin_placer.py index f3a416ab5..27125dc3e 100644 --- a/compiler/router/io_pin_placer.py +++ b/compiler/router/io_pin_placer.py @@ -412,7 +412,7 @@ def decide_point(self, source_pin, target_pin, is_up=False): return [source_pin.uc(), target_pin.bc()] else: # need intermediate point - via_basic_y = ll.y + 21 # 21 is magic number, make sure out of dff area + via_basic_y = ll.y + 22 # 22 is magic number, make sure out of dff area if is_up: via_basic_y = via_basic_y + 0.5 else: diff --git a/compiler/router/router.py b/compiler/router/router.py index 22169cc8c..4b58a51ef 100644 --- a/compiler/router/router.py +++ b/compiler/router/router.py @@ -111,6 +111,54 @@ def find_pins(self, pin_name): self.all_pins.update(pin_set) + def find_pins_inside(self, pin_name): + # find pins except moat, power ring, the moat pins will be store as list and return + """ Find the pins with the given name. """ + debug.info(4, "Finding all pins for {}".format(pin_name)) + moat_pins = [] + shape_list = self.layout.getAllPinShapes(str(pin_name)) + pin_set = set() + for shape in shape_list: + layer, boundary = shape + # gdsMill boundaries are in (left, bottom, right, top) order + ll = vector(boundary[0], boundary[1]) + ur = vector(boundary[2], boundary[3]) + rect = [ll, ur] + new_pin = graph_shape(pin_name, rect, layer) + # Skip this pin if it's contained by another pin of the same type + if new_pin.core_contained_by_any(pin_set): + continue + # skip the moat pin + if self.check_pin_on_moat(new_pin): + moat_pins.append(new_pin) + continue + # Merge previous pins into this one if possible + self.merge_shapes(new_pin, pin_set) + pin_set.add(new_pin) + # Add these pins to the 'pins' dict + self.pins[pin_name] = pin_set + self.all_pins.update(pin_set) + return moat_pins + + + def check_pin_on_moat(self, pin_shape): + """ Check if a given pin is on the moat. """ + ll, ur = self.bbox + left_x = ll.x + right_x = ur.x + bottom_y = ll.y + top_y = ur.y + + threshold = 10 # inside this distance, could be considered as on the moat + + is_on_left = abs(pin_shape.center().x - left_x) < threshold + is_on_right = abs(pin_shape.center().x - right_x) < threshold + is_on_bottom = abs(pin_shape.center().y - bottom_y) < threshold + is_on_top = abs(pin_shape.center().y - top_y) < threshold + + return is_on_left or is_on_right or is_on_bottom or is_on_top + + def find_blockages(self, name="blockage", shape_list=None): """ Find all blockages in the routing layers. """ debug.info(4, "Finding blockages...") diff --git a/compiler/router/supply_placer.py b/compiler/router/supply_placer.py new file mode 100644 index 000000000..bec2b07ca --- /dev/null +++ b/compiler/router/supply_placer.py @@ -0,0 +1,344 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2024 Regents of the University of California, Santa Cruz +# All rights reserved. +# +from openram import debug +from openram.base.vector import vector +from openram import OPTS +from .graph import graph +from .graph_shape import graph_shape +from .router import router + +class supply_placer(router): + + def __init__(self, layers, design, bbox=None, pin_type=None, ext_vdd_name="vccd1", ext_gnd_name="vssd1"): + + # `router` is the base router class + router.__init__(self, layers, design, bbox) + + # Side supply pin type + # (can be "top", "bottom", "right", "left", and "ring") + self.pin_type = pin_type + # New pins are the side supply pins + self.new_pins = {} + # external power name of the whole macro + self.ext_vdd_name = ext_vdd_name + self.ext_gnd_name = ext_gnd_name + # instances + self.insts = self.design.insts + # moat pins + self.moat_pins = [] + # io pins + self.io_pins_left = [] + self.io_pins_right = [] + self.io_pins_top = [] + self.io_pins_bottom = [] + + def route_bank(self, vdd_name="vdd", gnd_name="gnd"): + debug.info(1, "Running router for {} and {}...".format(vdd_name, gnd_name)) + + # Save pin names + self.vdd_name = vdd_name + self.gnd_name = gnd_name + + # Prepare gdsMill to find pins and blockages + self.prepare_gds_reader() + + # Find vdd/gnd pins of bank, to be routed + self.moat_pins = self.find_pins_inside(vdd_name) + self.find_pins_inside(gnd_name) + + # Find blockages and vias + self.find_blockages() + self.find_vias() + + # Convert blockages and vias if they overlap a pin + self.convert_vias() + self.convert_blockages() + + # Add vdd and gnd pins as blockages as well + # NOTE: This is done to make vdd and gnd pins DRC-safe + for pin in self.all_pins: + self.blockages.append(self.inflate_shape(pin)) + + # Route vdd and gnd + routed_count = 0 + routed_max = len(self.pins[vdd_name]) + len(self.pins[gnd_name]) + for pin_name in [vdd_name, gnd_name]: + pins = self.pins[pin_name] + # Route closest pins according to the minimum spanning tree + for source, target in self.get_mst_pairs(list(pins)): + # Create the graph + g = graph(self) + g.create_graph(source, target) + # Find the shortest path from source to target + path = g.find_shortest_path() + # If no path is found, throw an error + if path is None: + self.write_debug_gds(gds_name="{}error.gds".format(OPTS.openram_temp), g=g, source=source, target=target) + debug.error("Couldn't route from {} to {}.".format(source, target), -1) + # Create the path shapes on layout + new_wires, new_vias = self.add_path(path) + # Find the recently added shapes + self.find_blockages(pin_name, new_wires) + self.find_vias(new_vias) + # Report routed count + routed_count += 1 + debug.info(2, "Routed {} of {} supply pins".format(routed_count, routed_max)) + + + #def route_moat(self): + + #def route_other(self): + + def check_overlap(self, moat_pin, io_pin_names): + # use all the IO pins(at correspoding edge) to check overlap, check 1 moat vdd pin, give the corresponding target/source position as list, and pull the source up to m3 + add_distance = 0 + direction = 1 + self.prepare_io_pins(io_pin_names) + # judge the edge of moat vdd + edge = self.get_closest_edge(moat_pin) + source_center = moat_pin.center() + if edge == "bottom": + pin_too_close = any(abs(io_pin.center().x - source_center.x) < self.track_width for io_pin in self.io_pins_bottom) + tmp_center = source_center + while pin_too_close: + tmp_center = source_center + add_distance = add_distance + 0.1 + if direction == 1: # right shift + tmp_center = tmp_center + add_distance + else: # left shift + tmp_center = tmp_center - add_distance + pin_too_close = any(abs(io_pin.center().x - tmp_center.x) < self.track_width for io_pin in self.io_pins_bottom) + if tmp_center == source_center: # no overlap + # no jog, direct pull to m3 + self.design.copy_power_pin(moat_pin, loc=None, directions=None, new_name="") + else: # need jog + # shift the center + # add rectangle at same layer (original) + self.design.add + elif edge == "top": + pass + elif edge == "left": + pass + else: #right + pass + + def prepare_io_pins(self, io_pin_names): + # io_pin_names is a list + # find all the io pins + for pin_name in io_pin_names: + self.find_pins(pin_name)# pin now in self.pins + io_pin = self.pins[pin_name] + self.find_closest_edge(io_pin) + + + def get_closest_edge(self, pin): + """ Return a point's the closest edge and the edge's axis direction. Here we use to find the edge of moat vdd """ + + ll, ur = self.bbox + point = pin.center() + # Snap the pin to the perimeter and break the iteration + ll_diff_x = abs(point.x - ll.x) + ll_diff_y = abs(point.y - ll.y) + ur_diff_x = abs(point.x - ur.x) + ur_diff_y = abs(point.y - ur.y) + min_diff = min(ll_diff_x, ll_diff_y, ur_diff_x, ur_diff_y) + + if min_diff == ll_diff_x: + return "left" + if min_diff == ll_diff_y: + return "bottom" + if min_diff == ur_diff_x: + return "right" + return "top" + + + def find_closest_edge(self, pin): + """ Use to find the edge, where the io pin locats """ + + ll, ur = self.bbox + point = pin.center() + # Snap the pin to the perimeter and break the iteration + ll_diff_x = abs(point.x - ll.x) + ll_diff_y = abs(point.y - ll.y) + ur_diff_x = abs(point.x - ur.x) + ur_diff_y = abs(point.y - ur.y) + min_diff = min(ll_diff_x, ll_diff_y, ur_diff_x, ur_diff_y) + + if min_diff == ll_diff_x: + self.io_pins_left.append(pin) + elif min_diff == ll_diff_y: + self.io_pins_bottom.append(pin) + elif min_diff == ur_diff_x: + self.io_pins_right.append(pin) + else: + self.io_pins_top.append(pin) + + + def add_side_pin(self, pin_name, side, num_vias=3, num_fake_pins=4): + """ Add supply pin to one side of the layout. """ + + ll, ur = self.bbox + vertical = side in ["left", "right"] + inner = pin_name == self.ext_vdd_name + + # Calculate wires' wideness + wideness = self.track_wire * num_vias + self.track_space * (num_vias - 1) + + # Calculate the offset for the inner ring + if inner: + margin = wideness * 2 + else: + margin = 0 + + # Calculate the lower left coordinate + if side == "top": + offset = vector(ll.x + margin, ur.y - wideness - margin) + elif side == "bottom": + offset = vector(ll.x + margin, ll.y + margin) + elif side == "left": + offset = vector(ll.x + margin, ll.y + margin) + elif side == "right": + offset = vector(ur.x - wideness - margin, ll.y + margin) + + # Calculate width and height + shape = ur - ll + if vertical: + shape_width = wideness + shape_height = shape.y + else: + shape_width = shape.x + shape_height = wideness + if inner: + if vertical: + shape_height -= margin * 2 + else: + shape_width -= margin * 2 + + # Add this new pin + layer = self.get_layer(int(vertical)) + pin = self.design.add_layout_pin(text=pin_name, + layer=layer, + offset=offset, + width=shape_width, + height=shape_height) + + # Add fake pins on this new pin evenly + fake_pins = [] + if vertical: + space = (shape_height - (2 * wideness) - num_fake_pins * self.track_wire) / (num_fake_pins + 1) + start_offset = vector(offset.x, offset.y + wideness) + else: + space = (shape_width - (2 * wideness) - num_fake_pins * self.track_wire) / (num_fake_pins + 1) + start_offset = vector(offset.x + wideness, offset.y) + for i in range(1, num_fake_pins + 1): + if vertical: + offset = vector(start_offset.x, start_offset.y + i * (space + self.track_wire)) + ll = vector(offset.x, offset.y - self.track_wire) + ur = vector(offset.x + wideness, offset.y) + else: + offset = vector(start_offset.x + i * (space + self.track_wire), start_offset.y) + ll = vector(offset.x - self.track_wire, offset.y) + ur = vector(offset.x, offset.y + wideness) + rect = [ll, ur] + fake_pin = graph_shape(name=pin_name, + rect=rect, + layer_name_pp=layer) + fake_pins.append(fake_pin) + return pin, fake_pins + + + def add_ring_pin(self, pin_name, num_vias=3, num_fake_pins=4): + """ Add the supply ring to the layout. """ + + # Add side pins + new_pins = [] + for side in ["top", "bottom", "right", "left"]: + new_shape, fake_pins = self.add_side_pin(pin_name, side, num_vias, num_fake_pins) + ll, ur = new_shape.rect + rect = [ll, ur] + layer = self.get_layer(side in ["left", "right"]) + new_pin = graph_shape(name=pin_name, + rect=rect, + layer_name_pp=layer) + new_pins.append(new_pin) + #self.pins[pin_name].update(fake_pins) + self.fake_pins.extend(fake_pins) + + # Add vias to the corners + shift = self.track_wire + self.track_space + half_wide = self.track_wire / 2 + for i in range(4): + ll, ur = new_pins[i].rect + if i % 2: + top_left = vector(ur.x - (num_vias - 1) * shift - half_wide, ll.y + (num_vias - 1) * shift + half_wide) + else: + top_left = vector(ll.x + half_wide, ur.y - half_wide) + for j in range(num_vias): + for k in range(num_vias): + offset = vector(top_left.x + j * shift, top_left.y - k * shift) + self.design.add_via_center(layers=self.layers, + offset=offset) + + # Save side pins for routing + self.new_pins[pin_name] = new_pins + for pin in new_pins: + self.blockages.append(self.inflate_shape(pin)) + + + def get_mst_pairs(self, pins): + """ + Return the pin pairs from the minimum spanning tree in a graph that + connects all pins together. + """ + + pin_count = len(pins) + + # Create an adjacency matrix that connects all pins + edges = [[0] * pin_count for i in range(pin_count)] + for i in range(pin_count): + for j in range(pin_count): + # Skip if they're the same pin + if i == j: + continue + # Skip if both pins are fake + if pins[i] in self.fake_pins and pins[j] in self.fake_pins: + continue + edges[i][j] = pins[i].distance(pins[j]) + + pin_connected = [False] * pin_count + pin_connected[0] = True + + # Add the minimum cost edge in each iteration (Prim's) + mst_pairs = [] + for i in range(pin_count - 1): + min_cost = float("inf") + s = 0 + t = 0 + # Iterate over already connected pins + for m in range(pin_count): + # Skip if not connected + if not pin_connected[m]: + continue + # Iterate over this pin's neighbors + for n in range(pin_count): + # Skip if already connected or isn't a neighbor + if pin_connected[n] or edges[m][n] == 0: + continue + # Choose this edge if it's better the the current one + if edges[m][n] < min_cost: + min_cost = edges[m][n] + s = m + t = n + pin_connected[t] = True + mst_pairs.append((pins[s], pins[t])) + + return mst_pairs + + + def get_new_pins(self, name): + """ Return the new supply pins added by this router. """ + + return self.new_pins[name] \ No newline at end of file diff --git a/compiler/router/supply_router.py b/compiler/router/supply_router.py index fc61f4e64..3e1f431ee 100644 --- a/compiler/router/supply_router.py +++ b/compiler/router/supply_router.py @@ -97,7 +97,7 @@ def add_side_pin(self, pin_name, side, num_vias=3, num_fake_pins=4): ll, ur = self.bbox vertical = side in ["left", "right"] - inner = pin_name == self.gnd_name + inner = pin_name == self.vdd_name # Calculate wires' wideness wideness = self.track_wire * num_vias + self.track_space * (num_vias - 1) diff --git a/sram_compiler.py b/sram_compiler.py index 9d5fc4b3b..e0727ea32 100755 --- a/sram_compiler.py +++ b/sram_compiler.py @@ -70,7 +70,7 @@ # Create an SRAM (we can also pass sram_config, see documentation/tutorials for details) from openram import sram -s = sram(route_option="classic")# "classic" or "fast" +s = sram(route_option="fast")# "classic" or "fast" # Output the files for the resulting SRAM s.save() From 4fe635a05fa5c4aa2ca688dff7942f28a66bc17f Mon Sep 17 00:00:00 2001 From: FriedrichWu Date: Wed, 20 Nov 2024 16:24:26 +0100 Subject: [PATCH 18/26] add route_outside, remove unused methods --- compiler/base/hierarchy_layout.py | 77 ++-- compiler/modules/sram_1bank.py | 33 +- compiler/router/router.py | 6 +- compiler/router/router_tech.py | 2 + compiler/router/supply_placer.py | 591 +++++++++++++++++++++++++++--- compiler/sram.py | 4 +- 6 files changed, 611 insertions(+), 102 deletions(-) diff --git a/compiler/base/hierarchy_layout.py b/compiler/base/hierarchy_layout.py index bdb7258bb..7562bb69d 100644 --- a/compiler/base/hierarchy_layout.py +++ b/compiler/base/hierarchy_layout.py @@ -1913,8 +1913,8 @@ def add_io_pin(self, instance, pin_name, new_name, start_layer=None, directions= # Just use the power pin function for now to save code self.add_power_pin(new_name, pin.center(), start_layer=start_layer, directions=directions) - def add_power_pin_no_via(self, name, loc, directions=None, start_layer="m1"): - # same function like normal one, but do not add via to the gird layer + def add_power_pin_m2(self, name, loc, directions=None, start_layer="m1"): + # same function like normal one, but add power pin at m2 # Hack for min area if OPTS.tech_name == "sky130": min_area = drc["minarea_{}".format(self.pwr_grid_layers[1])] @@ -1925,7 +1925,7 @@ def add_power_pin_no_via(self, name, loc, directions=None, start_layer="m1"): height = None pin = None - if start_layer in self.pwr_grid_layers: + if start_layer == "m2": pin = self.add_layout_pin_rect_center(text=name, layer=start_layer, offset=loc, @@ -1933,7 +1933,7 @@ def add_power_pin_no_via(self, name, loc, directions=None, start_layer="m1"): height=height) else: via = self.add_via_stack_center(from_layer=start_layer, - to_layer=start_layer,# so only enclosure shape will be added + to_layer="m2", offset=loc, directions=directions) @@ -1942,7 +1942,7 @@ def add_power_pin_no_via(self, name, loc, directions=None, start_layer="m1"): if not height: height = via.height pin = self.add_layout_pin_rect_center(text=name, - layer=start_layer, + layer="m2", offset=loc, width=width, height=height) @@ -2063,7 +2063,7 @@ def add_perimeter_pin(self, name, pin, side, bbox): layer=layer, offset=peri_pin_loc) - def add_dnwell(self, bbox=None, inflate=1, add_vias=True): + def add_dnwell(self, bbox=None, inflate=1, route_option="classic"): """ Create a dnwell, along with nwell moat at border. """ if "dnwell" not in tech_layer: @@ -2108,9 +2108,9 @@ def add_dnwell(self, bbox=None, inflate=1, add_vias=True): tap_spacing = 2 nwell_offset = vector(self.nwell_width, self.nwell_width) - # Every nth tap is connected to gnd + # Every nth tap is connected to vdd period = 5 - + moat_pins = [] # BOTTOM count = 0 loc = ll + nwell_offset.scale(tap_spacing, 0) @@ -2125,14 +2125,10 @@ def add_dnwell(self, bbox=None, inflate=1, add_vias=True): to_layer="m1", offset=loc) else: - if add_vias: - self.add_power_pin(name="vdd", - loc=loc, - start_layer="li") - else: - self.add_power_pin_no_via(name="vdd", - loc=loc, - start_layer="li") + pin = self.add_power_pin(name="vdd", + loc=loc, + start_layer="li") + moat_pins.append(pin) count += 1 loc += nwell_offset.scale(tap_spacing, 0) @@ -2150,14 +2146,10 @@ def add_dnwell(self, bbox=None, inflate=1, add_vias=True): to_layer="m1", offset=loc) else: - if add_vias: - self.add_power_pin(name="vdd", - loc=loc, - start_layer="li") - else: - self.add_power_pin_no_via(name="vdd", - loc=loc, - start_layer="li") + pin = self.add_power_pin(name="vdd", + loc=loc, + start_layer="li") + moat_pins.append(pin) count += 1 loc += nwell_offset.scale(tap_spacing, 0) @@ -2175,14 +2167,15 @@ def add_dnwell(self, bbox=None, inflate=1, add_vias=True): to_layer="m2", offset=loc) else: - if add_vias: - self.add_power_pin(name="vdd", - loc=loc, - start_layer="li") - else: - self.add_power_pin_no_via(name="vdd", - loc=loc, - start_layer="li") + if route_option == "classic": + pin = self.add_power_pin(name="vdd", + loc=loc, + start_layer="li") + elif route_option == "fast": + pin = self.add_power_pin_m2(name="vdd", + loc=loc, + start_layer="li") + moat_pins.append(pin) count += 1 loc += nwell_offset.scale(0, tap_spacing) @@ -2200,19 +2193,21 @@ def add_dnwell(self, bbox=None, inflate=1, add_vias=True): to_layer="m2", offset=loc) else: - if add_vias: - self.add_power_pin(name="vdd", - loc=loc, - start_layer="li") - else: - self.add_power_pin_no_via(name="vdd", - loc=loc, - start_layer="li") + if route_option == "classic": + pin = self.add_power_pin(name="vdd", + loc=loc, + start_layer="li") + elif route_option == "fast": + pin = self.add_power_pin_m2(name="vdd", + loc=loc, + start_layer="li") + moat_pins.append(pin) count += 1 loc += nwell_offset.scale(0, tap_spacing) - # Add the gnd ring + # Add the vdd ring self.add_ring([ll, ur]) + return moat_pins def add_ring(self, bbox=None, width_mult=8, offset=0): """ diff --git a/compiler/modules/sram_1bank.py b/compiler/modules/sram_1bank.py index 88349dcf9..0c5056d07 100644 --- a/compiler/modules/sram_1bank.py +++ b/compiler/modules/sram_1bank.py @@ -53,8 +53,10 @@ def __init__(self, name, sram_config): # delay control logic does not have RBLs self.has_rbl = OPTS.control_logic != "control_logic_delay" - # IO pins, except power, list of pin names + # IO pins, except power, list of pin names self.pins_to_route = [] + # vdd pins on moat, list of pins + self.moat_pins = [] def add_pins(self): """ Add pins for entire SRAM. """ @@ -248,6 +250,7 @@ def create_modules(self): debug.error("Must override pure virtual function.", -1) def route_supplies_constructive(self, bbox=None): + """ Corresponding supply router for io_pin_placer """ # prepare the "router" from openram.router.supply_placer import supply_placer as router rtr = router(layers=self.supply_stack, @@ -255,22 +258,32 @@ def route_supplies_constructive(self, bbox=None): bbox=bbox, pin_type=OPTS.supply_pin_type, ext_vdd_name=self.vdd_name, - ext_gnd_name=self.gnd_name) + ext_gnd_name=self.gnd_name, + moat_pins=self.moat_pins) + # add power rings / side pins if OPTS.supply_pin_type in ["top", "bottom", "right", "left"]: - rtr.add_side_pin(self.vdd_name) - rtr.add_side_pin(self.gnd_name) + rtr.add_side_pin("vdd") + rtr.add_side_pin("gnd")# noraml gnd name elif OPTS.supply_pin_type == "ring": - rtr.add_ring_pin(self.vdd_name)# ring vdd name - rtr.add_ring_pin(self.gnd_name) + rtr.add_ring_pin("vdd") + rtr.add_ring_pin("gnd")# normal gnd name else: debug.warning("Side supply pins aren't created.") - # maze router the bank power pins + # Prepare the inside power pins (all power pins of submodules), at m3 for pin_name in ["vdd", "gnd"]: - for inst in self.bank_insts: + for inst in self.insts: self.copy_power_pins(inst, pin_name) - rtr.route_bank() + # Route all the power pins + #rtr.route_inside() # only connecting the power pins of inside submodules with each other, moat pins will connect to the ring + rtr.route_outside(io_pin_names=self.pins_to_route) + # route moat vdds + #rtr.route_moat(self.pins_to_route) + + # route to the outside + #rtr.prepare_escape_pins() + def route_supplies(self, bbox=None): """ Route the supply grid and connect the pins to them. """ @@ -1114,7 +1127,7 @@ def route_layout(self, mod=0, route_option="classic"): self.add_layout_pins() # Some technologies have an isolation - self.add_dnwell(inflate=2.5, add_vias=False) + self.moat_pins = self.add_dnwell(inflate=2.5, route_option=route_option) init_bbox = self.get_bbox() # Route the supplies together and/or to the ring/stripes. diff --git a/compiler/router/router.py b/compiler/router/router.py index 4b58a51ef..cf3c17ffa 100644 --- a/compiler/router/router.py +++ b/compiler/router/router.py @@ -112,10 +112,10 @@ def find_pins(self, pin_name): def find_pins_inside(self, pin_name): - # find pins except moat, power ring, the moat pins will be store as list and return + # find pins except moat, power ring, the moat pins will be store as set and return """ Find the pins with the given name. """ debug.info(4, "Finding all pins for {}".format(pin_name)) - moat_pins = [] + shape_list = self.layout.getAllPinShapes(str(pin_name)) pin_set = set() for shape in shape_list: @@ -130,7 +130,6 @@ def find_pins_inside(self, pin_name): continue # skip the moat pin if self.check_pin_on_moat(new_pin): - moat_pins.append(new_pin) continue # Merge previous pins into this one if possible self.merge_shapes(new_pin, pin_set) @@ -138,7 +137,6 @@ def find_pins_inside(self, pin_name): # Add these pins to the 'pins' dict self.pins[pin_name] = pin_set self.all_pins.update(pin_set) - return moat_pins def check_pin_on_moat(self, pin_shape): diff --git a/compiler/router/router_tech.py b/compiler/router/router_tech.py index 7a9fffebb..97cefe092 100644 --- a/compiler/router/router_tech.py +++ b/compiler/router/router_tech.py @@ -89,6 +89,8 @@ def __init__(self, layers, route_track_width): # When we actually create the routes, make them the width of the track (minus 1/2 spacing on each side) self.layer_widths = [self.track_wire, 1, self.track_wire] + # via2 to via3 distance requirements + self.via2_via3_pitch = 0.5 * drc("minwidth_{}".format("via2")) + 0.5 * drc("minwidth_{}".format("via3")) + drc["via3_to_via2"] def same_lpp(self, lpp1, lpp2): """ diff --git a/compiler/router/supply_placer.py b/compiler/router/supply_placer.py index bec2b07ca..e782b7882 100644 --- a/compiler/router/supply_placer.py +++ b/compiler/router/supply_placer.py @@ -12,7 +12,7 @@ class supply_placer(router): - def __init__(self, layers, design, bbox=None, pin_type=None, ext_vdd_name="vccd1", ext_gnd_name="vssd1"): + def __init__(self, layers, design, bbox=None, pin_type=None, ext_vdd_name="vccd1", ext_gnd_name="vssd1", moat_pins=None): # `router` is the base router class router.__init__(self, layers, design, bbox) @@ -28,14 +28,91 @@ def __init__(self, layers, design, bbox=None, pin_type=None, ext_vdd_name="vccd1 # instances self.insts = self.design.insts # moat pins - self.moat_pins = [] + self.moat_pins = moat_pins + # store a graphshape of intermediate points(if shift)/source points(if no shift) when connecting moat_pins to outside + # trick: since in the creation of dnwell, these pins are "ordered" added, so they'are also ordered here + # order inside list: left -> right or bottom -> up + self.moat_pins_left = [] + self.moat_pins_right = [] + self.moat_pins_top = [] + self.moat_pins_bottom = [] # io pins self.io_pins_left = [] self.io_pins_right = [] self.io_pins_top = [] self.io_pins_bottom = [] - def route_bank(self, vdd_name="vdd", gnd_name="gnd"): + + def route_outside(self, vdd_name="vdd", gnd_name="gnd", io_pin_names=None): + # only connect supply with inside submodules, not connecting to the power ring + debug.info(1, "Running router for {} and {}...".format(vdd_name, gnd_name)) + + # Save pin names + self.vdd_name = vdd_name + self.gnd_name = gnd_name + + # Prepare gdsMill to find pins and blockages + self.prepare_gds_reader() + + # Find vdd/gnd pins of bank, to be routed + self.find_pins_inside(vdd_name) + self.find_pins_inside(gnd_name) + self.route_moat(io_pin_names) + # Find blockages and vias + self.find_blockages() + self.find_vias() + + # Convert blockages and vias if they overlap a pin + self.convert_vias() + self.convert_blockages() + + # Add vdd and gnd pins as blockages as well + # NOTE: This is done to make vdd and gnd pins DRC-safe + for pin in self.all_pins: + self.blockages.append(self.inflate_shape(pin)) + + # Prepare the selected moat pins + selected_moat_pins = self.prepare_selected_moat_pins() + # Route vdd and gnd + routed_count = 0 + routed_max = len(self.pins[vdd_name]) + len(self.pins[gnd_name]) + for pin_name in [vdd_name, gnd_name]: + if pin_name == gnd_name: # otherwise will not recognaize the moat blocakge + self.prepare_gds_reader() + # Find blockages and vias + self.find_blockages() + self.find_vias() + + # Convert blockages and vias if they overlap a pin + self.convert_vias() + self.convert_blockages() + + pins = self.pins[pin_name] + # Route closest pins according to the minimum spanning tree + for source, target in self.get_mst_with_ring(list(pins), selected_moat_pins, pin_name): + # Create the graph + g = graph(self) + g.create_graph(source, target) + # Find the shortest path from source to target + path = g.find_shortest_path() + # If no path is found, throw an error + if path is None: + self.write_debug_gds(gds_name="{}error.gds".format(OPTS.openram_temp), g=g, source=source, target=target) + debug.error("Couldn't route from {} to {}.".format(source, target), -1) + # Create the path shapes on layout + new_wires, new_vias = self.add_path(path) + # Find the recently added shapes + self.find_blockages(pin_name, new_wires) + self.find_vias(new_vias) + # Report routed count + routed_count += 1 + debug.info(2, "Routed {} of {} supply pins".format(routed_count, routed_max)) + # finsih + self.replace_layout_pins() + + + def route_inside(self, vdd_name="vdd", gnd_name="gnd"): + # only connect supply with inside submodules, not connecting to the power ring debug.info(1, "Running router for {} and {}...".format(vdd_name, gnd_name)) # Save pin names @@ -46,7 +123,7 @@ def route_bank(self, vdd_name="vdd", gnd_name="gnd"): self.prepare_gds_reader() # Find vdd/gnd pins of bank, to be routed - self.moat_pins = self.find_pins_inside(vdd_name) + self.find_pins_inside(vdd_name) self.find_pins_inside(gnd_name) # Find blockages and vias @@ -86,14 +163,152 @@ def route_bank(self, vdd_name="vdd", gnd_name="gnd"): # Report routed count routed_count += 1 debug.info(2, "Routed {} of {} supply pins".format(routed_count, routed_max)) + # finsih + self.replace_layout_pins() - #def route_moat(self): + def route_moat(self, io_pin_names): + # route the vdd pins at the moat + # io_pin_names is a list + # the moat vdd shape will also be created and stored in the list + for moat_pin in self.moat_pins: + self.check_overlap(moat_pin, io_pin_names) + + + def replace_layout_pins(self): + # clear all the inside "vdd " "gnd" at sram module level + # Copy the pin shape(s) to rectangles + for pin_name in ["vdd", "gnd"]: + # Copy the pin shape(s) to rectangles + for pin in self.design.get_pins(pin_name): + self.design.add_rect(pin.layer, + pin.ll(), + pin.width(), + pin.height()) + + # Remove the pin shape(s) + self.design.remove_layout_pin(pin_name) + + # Get new pins, change the name of ring to extern supply name + # vccd1 ring + pins = self.get_new_pins("vdd") + for pin in pins: + self.design.add_layout_pin(self.ext_vdd_name, + pin.layer, + pin.ll(), + pin.width(), + pin.height()) + # vssd1 ring + pins = self.get_new_pins("gnd") + for pin in pins: + self.design.add_layout_pin(self.ext_gnd_name, + pin.layer, + pin.ll(), + pin.width(), + pin.height()) + + + def prepare_selected_moat_pins(self): + """ Selcet the possibe moat pins, feed into the MST, where will decide which of these pin should be connected to which pin """ + if len(self.design.all_ports) > 1: + # in order to save runtime + # top -> moat pins all + # bottom -> moat pins all + # left -> moat pins near control logic + # right -> moat pins near control logic + # expected connection for control logic + + # for port 0 -> left + start_y = self.design.control_logic_insts[0].by()# bottom edge y value + end_y = self.design.control_logic_insts[0].uy()# up edge y value + # filter the pin in the range + filtered_moat_pins_left = [pin for pin in self.moat_pins_left if start_y <= pin.center().y <= end_y] + + # for port 1 -> right + start_y = self.design.control_logic_insts[1].by()# bottom edge y value + end_y = self.design.control_logic_insts[1].uy()# up edge y value + # filter the pin in the range + filtered_moat_pins_right = [pin for pin in self.moat_pins_right if start_y <= pin.center().y <= end_y] + # return the selected moat pins + selected_moat_pins = [] + selected_moat_pins.extend(filtered_moat_pins_left) + selected_moat_pins.extend(self.moat_pins_bottom) + selected_moat_pins.extend(filtered_moat_pins_right) + selected_moat_pins.extend(self.moat_pins_top) + return selected_moat_pins + + else: # only 1 port + # in order to save runtime + # top -> moat pins all + # bottom -> moat pins all + # left -> moat pins near control logic + # right -> moat pins all + start_y = self.design.control_logic_insts[0].by()# bottom edge y value + end_y = self.design.control_logic_insts[0].uy()# up edge y value + # filter the pin in the range + filtered_moat_pins_left = [pin for pin in self.moat_pins_left if start_y <= pin.center().y <= end_y] + # return the selected moat pins + selected_moat_pins = [] + selected_moat_pins.extend(filtered_moat_pins_left) + selected_moat_pins.extend(self.moat_pins_bottom) + selected_moat_pins.extend(self.moat_pins_right) + selected_moat_pins.extend(self.moat_pins_top) + return selected_moat_pins + + + def prepare_escape_pins(self): + # clear all the inside "vdd " "gnd" at sram module level + # Copy the pin shape(s) to rectangles + for pin_name in ["vdd", "gnd"]: + # Copy the pin shape(s) to rectangles + for pin in self.design.get_pins(pin_name): + self.design.add_rect(pin.layer, + pin.ll(), + pin.width(), + pin.height()) + + # Remove the pin shape(s) + self.design.remove_layout_pin(pin_name) - #def route_other(self): + # prepare pins for every instances + for pin_name in ["vdd"]: + count =0 + for pin in self.design.control_logic_insts[0].get_pins(pin_name): + debug.warning("vdd pin shape -> ll:{0} ur:{1}".format(pin.ll(), pin.ur())) + """ + count = count + 1 + new_pin = graph_shape("gnd", pin.rect, pin.layer) + source = new_pin + source_center_x = pin.center().x + target_ur_y = self.new_pins[self.ext_gnd_name][1].center().y + 0.5 * self.new_pins[self.ext_gnd_name][1].height() + ll = vector(source_center_x - 2.6 - 0.5 * self.track_wire, pin.center().y - 0.5 * self.track_wire)#target_ur_y - self.track_wire) + ur = vector(source_center_x - 2.6 + 0.5 * self.track_wire, pin.center().y + 0.5 * self.track_wire)#target_ur_y) + target = graph_shape("fake", [ll,ur], pin.layer) + # Create the graph + g = graph(self) + g.create_graph(source, target) + # Find the shortest path from source to target + path = g.find_shortest_path() + # If no path is found, throw an error + if path is None: + self.write_debug_gds(gds_name="{}error.gds".format(OPTS.openram_temp), g=g, source=source, target=target) + debug.error("Couldn't route from {} to {}.".format(source, target), -1) + # Create the path shapes on layout + new_wires, new_vias = self.add_path(path) + # Find the recently added shapes + self.find_blockages(pin_name, new_wires) + self.find_vias(new_vias) + """ + debug.warning("vdd of wmask number -> {0}".format(count)) + debug.warning("instance postion -> {0} {1}".format(self.design.control_logic_insts[0].lx(), self.design.control_logic_insts[0].by())) + + # print pins_all + for pin in self.all_pins: + debug.warning("all_pins -> {0}".format(pin)) + def check_overlap(self, moat_pin, io_pin_names): - # use all the IO pins(at correspoding edge) to check overlap, check 1 moat vdd pin, give the corresponding target/source position as list, and pull the source up to m3 + # use all the IO pins(at correspoding edge) to check overlap, check 1 moat vdd pin, give the corresponding target/source position as list, and connect them add_distance = 0 direction = 1 self.prepare_io_pins(io_pin_names) @@ -101,36 +316,294 @@ def check_overlap(self, moat_pin, io_pin_names): edge = self.get_closest_edge(moat_pin) source_center = moat_pin.center() if edge == "bottom": + add_distance = self.via2_via3_pitch # if shift, need to fulfill via2-via3 spacing, top/bottom only pin_too_close = any(abs(io_pin.center().x - source_center.x) < self.track_width for io_pin in self.io_pins_bottom) - tmp_center = source_center + tmp_center = vector(source_center.x, source_center.y) while pin_too_close: - tmp_center = source_center + tmp_center = vector(source_center.x, source_center.y) add_distance = add_distance + 0.1 if direction == 1: # right shift - tmp_center = tmp_center + add_distance + tmp_center = vector((tmp_center.x + add_distance), tmp_center.y) else: # left shift - tmp_center = tmp_center - add_distance + tmp_center = vector((tmp_center.x - add_distance), tmp_center.y) pin_too_close = any(abs(io_pin.center().x - tmp_center.x) < self.track_width for io_pin in self.io_pins_bottom) + direction = - direction + # the nearst vdd ring + vdd_ring = self.new_pins["vdd"][1] # order in list -> "top", "bottom", "right", "left"] + # bottom ring's y position at it's top + target_egde_y = vdd_ring.center().y + 0.5 * vdd_ring.height() if tmp_center == source_center: # no overlap - # no jog, direct pull to m3 - self.design.copy_power_pin(moat_pin, loc=None, directions=None, new_name="") + # no jog, direct return the source/target center position + # the target center position, should consider enought space for via + target_point = vector(tmp_center.x, (target_egde_y - 0.5 * self.track_wire)) + source_point = vector(tmp_center.x, tmp_center.y) + point_list = [source_point, target_point] + self.add_wire(point_list, vertical=True) + # store the shape of moat pins, need for route later + ll = vector(source_point.x - 0.5 * self.track_wire, source_point.y - 0.5 * self.track_wire) + ur = vector(source_point.x + 0.5 * self.track_wire, source_point.y + 0.5 * self.track_wire) + rect = [ll, ur] + moat_pin_route = graph_shape("vdd", rect, "m4") + self.moat_pins_bottom.append(moat_pin_route) else: # need jog # shift the center # add rectangle at same layer (original) - self.design.add + intermediate_point = vector(tmp_center.x, tmp_center.y) + source_point = vector(source_center.x, source_center.y) + target_point = vector(tmp_center.x, (target_egde_y - 0.5 * self.track_wire)) + point_list = [source_point, intermediate_point, target_point] + self.add_wire(point_list, vertical=True) + # store the shape of moat pins, need for route later + ll = vector(intermediate_point.x - 0.5 * self.track_wire, intermediate_point.y - 0.5 * self.track_wire) + ur = vector(intermediate_point.x + 0.5 * self.track_wire, intermediate_point.y + 0.5 * self.track_wire) + rect = [ll, ur] + moat_pin_route = graph_shape("vdd", rect, "m4") + self.moat_pins_bottom.append(moat_pin_route) elif edge == "top": - pass + add_distance = self.via2_via3_pitch # if shift, need to fulfill via2-via3 spacing, top/bottom only + pin_too_close = any(abs(io_pin.center().x - source_center.x) < self.track_width for io_pin in self.io_pins_top) + tmp_center = vector(source_center.x, source_center.y) + while pin_too_close: + tmp_center = vector(source_center.x, source_center.y) + add_distance = add_distance + 0.1 + if direction == 1: # right shift + tmp_center = vector((tmp_center.x + add_distance), tmp_center.y) + else: # left shift + tmp_center = vector((tmp_center.x - add_distance), tmp_center.y) + pin_too_close = any(abs(io_pin.center().x - tmp_center.x) < self.track_width for io_pin in self.io_pins_top) + direction = - direction + # the nearst vdd ring + vdd_ring = self.new_pins["vdd"][0] # order in list -> "top", "bottom", "right", "left"] + # top ring's y position at it's bottom + target_egde_y = vdd_ring.center().y - 0.5 * vdd_ring.height() + if tmp_center == source_center: # no overlap + # no jog, direct return the source/target center position + # the target center position, should consider enought space for via + target_point = vector(tmp_center.x, (target_egde_y + 0.5 * self.track_wire)) + source_point = vector(tmp_center.x, tmp_center.y) + point_list = [source_point, target_point] + self.add_wire(point_list, vertical=True) + # store the shape of moat pins, need for route later + ll = vector(source_point.x - 0.5 * self.track_wire, source_point.y - 0.5 * self.track_wire) + ur = vector(source_point.x + 0.5 * self.track_wire, source_point.y + 0.5 * self.track_wire) + rect = [ll, ur] + moat_pin_route = graph_shape("vdd", rect, "m4") + self.moat_pins_top.append(moat_pin_route) + else: # need jog + # shift the center + # add rectangle at same layer (original) + intermediate_point = vector(tmp_center.x, tmp_center.y) + source_point = vector(source_center.x, source_center.y) + target_point = vector(tmp_center.x, (target_egde_y + 0.5 * self.track_wire)) + point_list = [source_point, intermediate_point, target_point] + self.add_wire(point_list, vertical=True) + # store the shape of moat pins, need for route later + ll = vector(intermediate_point.x - 0.5 * self.track_wire, intermediate_point.y - 0.5 * self.track_wire) + ur = vector(intermediate_point.x + 0.5 * self.track_wire, intermediate_point.y + 0.5 * self.track_wire) + rect = [ll, ur] + moat_pin_route = graph_shape("vdd", rect, "m4") + self.moat_pins_top.append(moat_pin_route) elif edge == "left": - pass + pin_too_close = any(abs(io_pin.center().y - source_center.y) < self.track_width for io_pin in self.io_pins_left) + tmp_center = vector(source_center.x, source_center.y) + while pin_too_close: + tmp_center = vector(source_center.x, source_center.y) + add_distance = add_distance + 0.1 + if direction == 1: # up shift + tmp_center = vector(tmp_center.x, (tmp_center.y + add_distance)) + else: # down shift + tmp_center = vector(tmp_center.x, (tmp_center.y - add_distance)) + pin_too_close = any(abs(io_pin.center().y - tmp_center.y) < self.track_width for io_pin in self.io_pins_left) + direction = - direction + # the nearst vdd ring + vdd_ring = self.new_pins["vdd"][3] # order in list -> "top", "bottom", "right", "left"] + # left ring's x position at it's right + target_egde_x = vdd_ring.center().x + 0.5 * vdd_ring.width() + if tmp_center == source_center: # no overlap + # no jog, direct return the source/target center position + # the target center position, should consider enought space for via + target_point = vector((target_egde_x - 0.5 * self.track_wire), tmp_center.y) + source_point = vector(tmp_center.x, tmp_center.y) + point_list = [source_point, target_point] + self.add_wire(point_list, vertical=False) + # store the shape of moat pins, need for route later + ll = vector(source_point.x - 0.5 * self.track_wire, source_point.y - 0.5 * self.track_wire) + ur = vector(source_point.x + 0.5 * self.track_wire, source_point.y + 0.5 * self.track_wire) + rect = [ll, ur] + moat_pin_route = graph_shape("vdd", rect, "m3") + self.moat_pins_left.append(moat_pin_route) + else: # need jog + # shift the center + # add rectangle at same layer (original) + intermediate_point = vector(tmp_center.x, tmp_center.y) + source_point = vector(source_center.x, source_center.y) + target_point = vector((target_egde_x - 0.5 * self.track_wire), tmp_center.y) + point_list = [source_point, intermediate_point, target_point] + self.add_wire(point_list, vertical=False) + # store the shape of moat pins, need for route later + ll = vector(intermediate_point.x - 0.5 * self.track_wire, intermediate_point.y - 0.5 * self.track_wire) + ur = vector(intermediate_point.x + 0.5 * self.track_wire, intermediate_point.y + 0.5 * self.track_wire) + rect = [ll, ur] + moat_pin_route = graph_shape("vdd", rect, "m3") + self.moat_pins_left.append(moat_pin_route) else: #right - pass + pin_too_close = any(abs(io_pin.center().y - source_center.y) < self.track_width for io_pin in self.io_pins_right) + tmp_center = vector(source_center.x, source_center.y) + while pin_too_close: + tmp_center = vector(source_center.x, source_center.y) + add_distance = add_distance + 0.1 + if direction == 1: # up shift + tmp_center = vector(tmp_center.x, (tmp_center.y + add_distance)) + else: # down shift + tmp_center = vector(tmp_center.x, (tmp_center.y - add_distance)) + pin_too_close = any(abs(io_pin.center().y - tmp_center.y) < self.track_width for io_pin in self.io_pins_right) + direction = - direction + # the nearst vdd ring + vdd_ring = self.new_pins["vdd"][2] # order in list -> "top", "bottom", "right", "left"] + # right ring's y position at it's left + target_egde_x = vdd_ring.center().x - 0.5 * vdd_ring.width() + if tmp_center == source_center: # no overlap + # no jog, direct return the source/target center position + # the target center position, should consider enought space for via + target_point = vector((target_egde_x + 0.5 * self.track_wire), tmp_center.y) + source_point = vector(tmp_center.x, tmp_center.y) + point_list = [source_point, target_point] + self.add_wire(point_list, vertical=False) + # store the shape of moat pins, need for route later + ll = vector(source_point.x - 0.5 * self.track_wire, source_point.y - 0.5 * self.track_wire) + ur = vector(source_point.x + 0.5 * self.track_wire, source_point.y + 0.5 * self.track_wire) + rect = [ll, ur] + moat_pin_route = graph_shape("vdd", rect, "m3") + self.moat_pins_right.append(moat_pin_route) + else: # need jog + # shift the center + # add rectangle at same layer (original) + intermediate_point = vector(tmp_center.x, tmp_center.y) + source_point = vector(source_center.x, source_center.y) + target_point = vector((target_egde_x + 0.5 * self.track_wire) ,tmp_center.y) + point_list = [source_point, intermediate_point, target_point] + self.add_wire(point_list, vertical=False) + # store the shape of moat pins, need for route later + ll = vector(intermediate_point.x - 0.5 * self.track_wire, intermediate_point.y - 0.5 * self.track_wire) + ur = vector(intermediate_point.x + 0.5 * self.track_wire, intermediate_point.y + 0.5 * self.track_wire) + rect = [ll, ur] + moat_pin_route = graph_shape("vdd", rect, "m3") + self.moat_pins_right.append(moat_pin_route) + + + def add_wire(self, point_list, vertical=False): + if vertical == True: # m4 line, need start via3(m3 -> m4), end via3(m3 -> m4) + if len(point_list) == 2: # direct connect + # start via + self.add_via(point=point_list[0], + from_layer="m3", + to_layer="m4") + self.add_via(point=point_list[0], + from_layer="m4", + to_layer="m4") # shape + # connection + self.add_line(point_1=point_list[0], + point_2=point_list[1], + layer="m4") + # end via + self.add_via(point=point_list[1], + from_layer="m3", + to_layer="m4") + self.add_via(point=point_list[1], + from_layer="m4", + to_layer="m4") # shape + elif len(point_list) == 3: # need intermediate point + # jog + self.add_line(point_1=point_list[0], + point_2=point_list[1], + layer="m3") + # start_via + self.add_via(point=point_list[1], + from_layer="m3", + to_layer="m4") + self.add_via(point=point_list[1], + from_layer="m3", + to_layer="m3") # shape + # connection + self.add_line(point_1=point_list[1], + point_2=point_list[2], + layer="m4") + # end via + self.add_via(point=point_list[2], + from_layer="m3", + to_layer="m4") + self.add_via(point=point_list[2], + from_layer="m4", + to_layer="m4") # shape + else: # m3 line, need start via2(m2 -> m3), end via3(m3 -> m4) + if len(point_list) == 2: # direct connect + # start via + self.add_via(point=point_list[0], + from_layer="m2", + to_layer="m3") + self.add_via(point=point_list[0], + from_layer="m3", + to_layer="m3") # shape + # connection + self.add_line(point_1=point_list[0], + point_2=point_list[1], + layer="m3") + # end via + self.add_via(point=point_list[1], + from_layer="m3", + to_layer="m4") + self.add_via(point=point_list[1], + from_layer="m3", + to_layer="m3") # shape + elif len(point_list) == 3: # need intermediate point + # jog + self.add_line(point_1=point_list[0], + point_2=point_list[1], + layer="m2") + # start_via + self.add_via(point=point_list[1], + from_layer="m2", + to_layer="m3") + self.add_via(point=point_list[1], + from_layer="m3", + to_layer="m3") # shape + # connection + self.add_line(point_1=point_list[1], + point_2=point_list[2], + layer="m3") + # end via + self.add_via(point=point_list[2], + from_layer="m3", + to_layer="m4") + self.add_via(point=point_list[2], + from_layer="m3", + to_layer="m3") # shape + + + def add_line(self, point_1, point_2, layer="m3"): # "m2", "m3", "m4" + self.design.add_path(layer, [point_1, point_2], self.track_wire) + + + def add_via(self, point, from_layer="m3", to_layer="m4"): + # via could be via2(m2 -> m3), via3(m3 -> m4) + # or a shape at same layer + if from_layer == to_layer: + self.design.add_rect_center(layer=from_layer, + offset=point, + width=self.track_wire, + height=self.track_wire) + else: + self.design.add_via_stack_center(from_layer=from_layer, + to_layer=to_layer, + offset=point) + def prepare_io_pins(self, io_pin_names): # io_pin_names is a list # find all the io pins for pin_name in io_pin_names: self.find_pins(pin_name)# pin now in self.pins - io_pin = self.pins[pin_name] + io_pin = next(iter(self.pins[pin_name])) self.find_closest_edge(io_pin) @@ -139,6 +612,7 @@ def get_closest_edge(self, pin): ll, ur = self.bbox point = pin.center() + debug.warning("moat pin center -> {0}".format(point)) # Snap the pin to the perimeter and break the iteration ll_diff_x = abs(point.x - ll.x) ll_diff_y = abs(point.y - ll.y) @@ -159,6 +633,7 @@ def find_closest_edge(self, pin): """ Use to find the edge, where the io pin locats """ ll, ur = self.bbox + #debug.warning("pin -> {0}".format(pin)) point = pin.center() # Snap the pin to the perimeter and break the iteration ll_diff_x = abs(point.x - ll.x) @@ -182,7 +657,7 @@ def add_side_pin(self, pin_name, side, num_vias=3, num_fake_pins=4): ll, ur = self.bbox vertical = side in ["left", "right"] - inner = pin_name == self.ext_vdd_name + inner = pin_name == "vdd" # Calculate wires' wideness wideness = self.track_wire * num_vias + self.track_space * (num_vias - 1) @@ -225,29 +700,7 @@ def add_side_pin(self, pin_name, side, num_vias=3, num_fake_pins=4): width=shape_width, height=shape_height) - # Add fake pins on this new pin evenly - fake_pins = [] - if vertical: - space = (shape_height - (2 * wideness) - num_fake_pins * self.track_wire) / (num_fake_pins + 1) - start_offset = vector(offset.x, offset.y + wideness) - else: - space = (shape_width - (2 * wideness) - num_fake_pins * self.track_wire) / (num_fake_pins + 1) - start_offset = vector(offset.x + wideness, offset.y) - for i in range(1, num_fake_pins + 1): - if vertical: - offset = vector(start_offset.x, start_offset.y + i * (space + self.track_wire)) - ll = vector(offset.x, offset.y - self.track_wire) - ur = vector(offset.x + wideness, offset.y) - else: - offset = vector(start_offset.x + i * (space + self.track_wire), start_offset.y) - ll = vector(offset.x - self.track_wire, offset.y) - ur = vector(offset.x, offset.y + wideness) - rect = [ll, ur] - fake_pin = graph_shape(name=pin_name, - rect=rect, - layer_name_pp=layer) - fake_pins.append(fake_pin) - return pin, fake_pins + return pin def add_ring_pin(self, pin_name, num_vias=3, num_fake_pins=4): @@ -256,7 +709,7 @@ def add_ring_pin(self, pin_name, num_vias=3, num_fake_pins=4): # Add side pins new_pins = [] for side in ["top", "bottom", "right", "left"]: - new_shape, fake_pins = self.add_side_pin(pin_name, side, num_vias, num_fake_pins) + new_shape = self.add_side_pin(pin_name, side, num_vias, num_fake_pins) ll, ur = new_shape.rect rect = [ll, ur] layer = self.get_layer(side in ["left", "right"]) @@ -264,8 +717,6 @@ def add_ring_pin(self, pin_name, num_vias=3, num_fake_pins=4): rect=rect, layer_name_pp=layer) new_pins.append(new_pin) - #self.pins[pin_name].update(fake_pins) - self.fake_pins.extend(fake_pins) # Add vias to the corners shift = self.track_wire + self.track_space @@ -338,6 +789,56 @@ def get_mst_pairs(self, pins): return mst_pairs + def get_mst_with_ring(self, pins, ring_pins, pin_name="vdd"): + """ + Extend the MST logic to connect internal pins to the nearest external ring pins. + """ + # Prepare the pins that are allowed to connect to the moat pins. + # Specical handle gnd ring + candidate_pins = [] + max_distance = 13 + if pin_name == "gnd": + ring_pins = [] + ring_pins = self.new_pins[pin_name] + for pin in pins: + for ring_pin in ring_pins: + dist = pin.distance(ring_pin) + if max_distance is None or dist <= max_distance: + candidate_pins.append(pin) + break + + # Compute the MST for internal pins + mst_pairs = self.get_mst_pairs(pins) + + # Connect each internal pin to the nearest external ring pin + used_ring_pins = set() + internal_to_ring_pairs = [] + for pin in candidate_pins: + min_distance = float("inf") + nearest_ring_pin = None + + for ring_pin in ring_pins: + if pin_name == "vdd" and ring_pin in used_ring_pins: + continue + + dist = pin.distance(ring_pin) + if dist < min_distance: + min_distance = dist + nearest_ring_pin = ring_pin + + # Add the connection to the nearest ring pin + if nearest_ring_pin: + internal_to_ring_pairs.append((pin, nearest_ring_pin)) + # Mark the ring pin as used if the pin is VDD + if pin_name == "vdd": + used_ring_pins.add(nearest_ring_pin) + + # Combine internal MST pairs and external connections + full_connections = mst_pairs + internal_to_ring_pairs + + return full_connections + + def get_new_pins(self, name): """ Return the new supply pins added by this router. """ diff --git a/compiler/sram.py b/compiler/sram.py index 997f57469..f7e9b46d4 100644 --- a/compiler/sram.py +++ b/compiler/sram.py @@ -169,7 +169,7 @@ def save(self): spname = OPTS.output_path + self.s.name + ".sp" debug.print_raw("SP: Writing to {0}".format(spname)) self.sp_write(spname) - #''' #comment the following state when generating big sram, and then disable drc/lvs, because maigc_ext stuck + ''' #comment the following state when generating big sram, and then disable drc/lvs, because maigc_ext stuck # Save a functional simulation file with default period functional(self.s, spname, @@ -191,7 +191,7 @@ def save(self): d.targ_write_ports = [self.s.write_ports[0]] d.write_delay_stimulus() print_time("DELAY", datetime.datetime.now(), start_time) - #''' #comment the above when generating big sram, and then disable drc/lvs, bevause magic_ext stuck + ''' #comment the above when generating big sram, and then disable drc/lvs, bevause magic_ext stuck # Save trimmed spice file temp_trim_sp = "{0}trimmed.sp".format(OPTS.output_path) self.sp_write(temp_trim_sp, lvs=False, trim=True) From bda3adf9f9228675d6ac033edaae8fb21ba8d867 Mon Sep 17 00:00:00 2001 From: FriedrichWu Date: Mon, 16 Dec 2024 00:13:40 +0100 Subject: [PATCH 19/26] more stable --- compiler/base/channel_route.py | 13 +--- compiler/modules/sram_1bank.py | 102 ++++++++++++++++++++++++------- compiler/router/io_pin_placer.py | 18 ++++-- compiler/router/supply_placer.py | 63 ++----------------- compiler/sram.py | 16 ++++- sram_compiler.py | 2 +- 6 files changed, 116 insertions(+), 98 deletions(-) diff --git a/compiler/base/channel_route.py b/compiler/base/channel_route.py index e1f9bedc1..3afaf611a 100644 --- a/compiler/base/channel_route.py +++ b/compiler/base/channel_route.py @@ -233,19 +233,10 @@ def route(self): real_channel_offset = vector(self.offset.x, min(self.min_value, self.offset.y)) else: real_channel_offset = vector(min(self.min_value, self.offset.x), self.offset.y) - - if self.dff_area == False: - current_offset = real_channel_offset - else: # special handle for dff area - current_offset = vector(real_channel_offset.x, real_channel_offset.y + 5) # make route out of dffs area - + current_offset = real_channel_offset + if self.dff_area == True: if self.layer_stack == self.m2_stack: self.vertical_nonpref_pitch = self.horizontal_pitch + 0.1 # 0.1 make sure even if via at same col, fulfill m3-m3 spacing - - if self.layer_stack == self.m1_stack: - current_offset = vector(real_channel_offset.x, current_offset.y + 14) # make sure no overlap between col_dffs & data_dffs - if real_channel_offset.y > 0: # which means this is channnel router for coldff at the top - current_offset = real_channel_offset # no offset to avoid overlap problem at the top # Sort nets by left edge value nets.sort() while len(nets) > 0: diff --git a/compiler/modules/sram_1bank.py b/compiler/modules/sram_1bank.py index 0c5056d07..6cc206053 100644 --- a/compiler/modules/sram_1bank.py +++ b/compiler/modules/sram_1bank.py @@ -280,10 +280,6 @@ def route_supplies_constructive(self, bbox=None): rtr.route_outside(io_pin_names=self.pins_to_route) # route moat vdds #rtr.route_moat(self.pins_to_route) - - # route to the outside - #rtr.prepare_escape_pins() - def route_supplies(self, bbox=None): """ Route the supply grid and connect the pins to them. """ @@ -916,7 +912,6 @@ def place_row_addr_dffs(self): x_offset = self.control_logic_insts[port].rx() - self.row_addr_dff_insts[port].width # It is above the control logic and the predecoder array y_offset = max(self.control_logic_insts[port].uy(), self.bank.predecoder_top) - y_offset = y_offset + 0.4 # fix, maigc number self.row_addr_pos[port] = vector(x_offset, y_offset) self.row_addr_dff_insts[port].place(self.row_addr_pos[port]) @@ -925,7 +920,7 @@ def place_row_addr_dffs(self): # The row address bits are placed above the control logic aligned on the left. x_offset = self.control_pos[port].x - self.control_logic_insts[port].width + self.row_addr_dff_insts[port].width # If it can be placed above the predecoder and below the control logic, do it - y_offset = min(self.control_logic_insts[port].by(), self.bank.predecoder_top) + y_offset = min(self.control_logic_insts[port].by(), self.bank.predecoder_bottom) self.row_addr_pos[port] = vector(x_offset, y_offset) self.row_addr_dff_insts[port].place(self.row_addr_pos[port], mirror="XY") @@ -1138,8 +1133,10 @@ def route_layout(self, mod=0, route_option="classic"): self.route_escape_pins(bbox=init_bbox, mod=mod, route_option=route_option) if OPTS.route_supplies: - #self.route_supplies(init_bbox) - self.route_supplies_constructive(init_bbox) + if route_option == "classic": + self.route_supplies(init_bbox) + else: # fast + self.route_supplies_constructive(init_bbox) def route_dffs(self, add_routes=True): @@ -1175,7 +1172,7 @@ def route_col_addr_dffs(self, port): if port == 0: offset = vector(self.control_logic_insts[port].rx() + self.dff.width, - - self.data_bus_size[port] + 2 * self.m3_pitch) + self.bank_inst.by() - self.col_addr_size * self.m3_pitch)# higher offset to avoid possible overlap with data dffs channel routing cr = channel_route(netlist=route_map, offset=offset, layer_stack=layer_stack, @@ -1228,17 +1225,11 @@ def route_data_dffs(self, port, add_routes): route_map.extend(list(zip(bank_pins, dff_pins))) if len(route_map) > 0: - # This layer stack must be different than the column addr dff layer stack layer_stack = self.m2_stack if port == 0: - # This is relative to the bank at 0,0 or the s_en which is routed on M3 also - if "s_en" in self.control_logic_insts[port].mod.pin_map: - y_bottom = min(0, self.control_logic_insts[port].get_pin("s_en").by()) - else: - y_bottom = 0 - - y_offset = y_bottom - self.data_bus_size[port] + 2 * self.m3_pitch + # for port 0, the offset of first track in channel router is fixed + y_offset = self.data_dff_insts[port].uy() + 6 * self.m3_pitch offset = vector(self.control_logic_insts[port].rx() + self.dff.width, y_offset) cr = channel_route(netlist=route_map, @@ -1253,13 +1244,77 @@ def route_data_dffs(self, port, add_routes): self.connect_inst([]) # self.add_flat_inst(cr.name, cr) else: - self.data_bus_size[port] = max(cr.height, self.col_addr_bus_size[port]) + self.data_bus_gap + # return the real channel width that min. should be + # the bottom of w_en or s_en + if ("s_en" in self.control_logic_insts[port].mod.pin_map) and ("w_en" in self.control_logic_insts[port].mod.pin_map):# rw + y_bottom = min(0, self.control_logic_insts[port].get_pin("s_en").by(), self.control_logic_insts[port].get_pin("w_en").by()) + elif "w_en" in self.control_logic_insts[port].mod.pin_map:# w + y_bottom = min(0, self.control_logic_insts[port].get_pin("w_en").by()) + else: + y_bottom = 0 + + if len(self.all_ports) == 1: # only 1 port at bottom + extra_offset = 6 * self.m3_pitch + (self.bank_inst.by() - (y_bottom - self.m4_nonpref_pitch)) + else: # 2 ports, row address decoder needs to be considered + # for port 0 + # determine the most right dffs + if self.num_spare_cols: # if we have spare regs + dff_right_x = self.spare_wen_dff_insts[0].rx() + else: # data dffs + dff_right_x = self.data_dff_insts[0].rx() + # check if row address dffs are overlaped with dff area, bank position as reference + if self.bank_inst.rx() < (dff_right_x + 2 * self.m4_pitch): + debug.warning("m4 pitch ----> {0}".format(self.m4_pitch)) + debug.warning("m3 pitch ----> {0}".format(self.m3_pitch)) + debug.warning("m4_non_pref pitch ----> {0}".format(self.m4_nonpref_pitch)) + debug.warning("lower row addr dff: {0}".format(self.row_addr_dff_insts[1].by())) + debug.warning("higher row addr dff: {0}".format(self.row_addr_dff_insts[1].uy())) + # check the most lower one betwenn control signal and address dff + if y_bottom < self.row_addr_dff_insts[1].by(): + y_bottom_most = y_bottom + else: + y_bottom_most = self.row_addr_dff_insts[1].by() + # the upper track should below the lower one + extra_offset = 6 * self.m3_pitch + (self.bank_inst.by() - (y_bottom_most - self.m4_nonpref_pitch)) + else: # do not need take care of address dff 1, since it's far away + extra_offset = 6 * self.m3_pitch + (self.bank_inst.by() - (y_bottom - self.m4_nonpref_pitch)) + debug.warning("extra_offset->{0}".format(extra_offset)) + debug.warning("channel_height->{0}".format(cr.height)) + debug.warning("self.col_addr_bus_size->{0}".format(self.col_addr_bus_size[port])) + debug.warning("self.databusgap->{0}".format(self.data_bus_gap)) + self.data_bus_size[port] = max((cr.height + extra_offset), self.col_addr_bus_size[port]) + self.data_bus_gap else: - if "s_en" in self.control_logic_insts[port].mod.pin_map: - y_top = max(self.bank.height, self.control_logic_insts[port].get_pin("s_en").uy()) + # for port1, the offset of first track in channel router needs to check first, make sure no overlap with control signal & address dff + if ("s_en" in self.control_logic_insts[port].mod.pin_map) and ("w_en" in self.control_logic_insts[port].mod.pin_map): + y_top = max(self.bank.height, self.control_logic_insts[port].get_pin("s_en").uy(), self.control_logic_insts[port].get_pin("w_en").uy()) + y_offset = y_top + 6 * self.m3_pitch + elif "w_en" in self.control_logic_insts[port].mod.pin_map: + y_top = max(self.bank.height, self.control_logic_insts[port].get_pin("w_en").uy()) + y_offset = y_top + self.m3_pitch# it's fine, since w port doesn't have dout signals else: y_top = self.bank.height - y_offset = y_top + self.m3_pitch + y_offset = y_top + 6 * self.m3_pitch + # check the offset overlap with address dff 0 or not + if self.num_spare_cols: # if we have spare regs + dff_left_x = self.spare_wen_dff_insts[1].lx() + else: # data dffs + dff_left_x = self.data_dff_insts[1].lx() + # check if row address dffs are overlaped with dff area + if self.bank_inst.lx() > (dff_left_x - 2 * self.m4_pitch): + debug.warning("m4 pitch ----> {0}".format(self.m4_pitch)) + debug.warning("m3 pitch ----> {0}".format(self.m3_pitch)) + debug.warning("m4_non_pref pitch ----> {0}".format(self.m4_nonpref_pitch)) + # the bottom track should also above row address decoder + if y_offset > self.row_addr_dff_insts[0].uy() + self.m4_nonpref_pitch: + # do not need change since first track is high enough + extra_offset = y_offset - self.bank.height # height could be use since bank at 0,0 + else: # make it higher tham row address decoder + extra_offset = self.row_addr_dff_insts[0].uy() + self.m4_nonpref_pitch - self.bank_inst.height + # update the new y_offset + y_offset = self.row_addr_dff_insts[0].uy() + self.m4_nonpref_pitch + else: # do not need to take care address dff0, since it's far away + extra_offset = y_offset - self.bank.height # height could be use since bank at 0,0 + offset = vector(0, y_offset) cr = channel_route(netlist=route_map, @@ -1274,7 +1329,10 @@ def route_data_dffs(self, port, add_routes): self.connect_inst([]) # self.add_flat_inst(cr.name, cr) else: - self.data_bus_size[port] = max(cr.height, self.col_addr_bus_size[port]) + self.data_bus_gap + # return the real channel width that min. should be + # 2 ports, row address decoder needs to be considered + # for port 1 + self.data_bus_size[port] = max((cr.height + extra_offset), self.col_addr_bus_size[port]) + self.data_bus_gap def route_clk(self): """ Route the clock network """ diff --git a/compiler/router/io_pin_placer.py b/compiler/router/io_pin_placer.py index 27125dc3e..f1f8dbf7d 100644 --- a/compiler/router/io_pin_placer.py +++ b/compiler/router/io_pin_placer.py @@ -395,12 +395,15 @@ def decide_point(self, source_pin, target_pin, is_up=False): return [source_pin.bc(), target_pin.uc()] else: # need intermediate point - via_basic_y = self.design.bank.height + 3 # 3 is magic number, make sure out of bank area + #via_basic_y = self.design.bank.height + 3 # 3 is magic number, make sure out of bank area + via_basic_y = self.design.bank_inst.uy() + 3 * self.design.m3_pitch is_up = not is_up# Be attention, for channel at the top, the is_up should be inverted! Otherwise will cause overlap! if is_up: - via_basic_y = via_basic_y + 0.5 + #via_basic_y = via_basic_y + 0.5 + via_basic_y = via_basic_y + self.design.m3_pitch else: - via_basic_y = via_basic_y - 0.5 + #via_basic_y = via_basic_y - 0.5 + via_basic_y = via_basic_y - self.design.m3_pitch point_1 = vector(source_pin.center().x, via_basic_y) point_2 = vector(target_pin.center().x, via_basic_y) return [source_pin.bc(), point_1, point_2, target_pin.uc()] @@ -412,11 +415,14 @@ def decide_point(self, source_pin, target_pin, is_up=False): return [source_pin.uc(), target_pin.bc()] else: # need intermediate point - via_basic_y = ll.y + 22 # 22 is magic number, make sure out of dff area + #via_basic_y = ll.y + 22 # 22 is magic number, make sure out of dff area + via_basic_y = self.design.data_dff_insts[0].uy() + 3 * self.design.m3_pitch if is_up: - via_basic_y = via_basic_y + 0.5 + #via_basic_y = via_basic_y + 0.5 + via_basic_y = via_basic_y + self.design.m3_pitch else: - via_basic_y = via_basic_y - 0.5 + #via_basic_y = via_basic_y - 0.5 + via_basic_y = via_basic_y - self.design.m3_pitch point_1 = vector(source_pin.center().x, via_basic_y) point_2 = vector(target_pin.center().x, via_basic_y) return [source_pin.uc(), point_1, point_2, target_pin.bc()] diff --git a/compiler/router/supply_placer.py b/compiler/router/supply_placer.py index e782b7882..aa47ec5dc 100644 --- a/compiler/router/supply_placer.py +++ b/compiler/router/supply_placer.py @@ -75,7 +75,7 @@ def route_outside(self, vdd_name="vdd", gnd_name="gnd", io_pin_names=None): selected_moat_pins = self.prepare_selected_moat_pins() # Route vdd and gnd routed_count = 0 - routed_max = len(self.pins[vdd_name]) + len(self.pins[gnd_name]) + routed_max = len(self.pins[vdd_name]) + len(self.pins[gnd_name]) + len(self.moat_pins) + len(self.new_pins["gnd"]) for pin_name in [vdd_name, gnd_name]: if pin_name == gnd_name: # otherwise will not recognaize the moat blocakge self.prepare_gds_reader() @@ -206,7 +206,7 @@ def replace_layout_pins(self): pin.ll(), pin.width(), pin.height()) - + def prepare_selected_moat_pins(self): """ Selcet the possibe moat pins, feed into the MST, where will decide which of these pin should be connected to which pin """ @@ -256,57 +256,6 @@ def prepare_selected_moat_pins(self): return selected_moat_pins - def prepare_escape_pins(self): - # clear all the inside "vdd " "gnd" at sram module level - # Copy the pin shape(s) to rectangles - for pin_name in ["vdd", "gnd"]: - # Copy the pin shape(s) to rectangles - for pin in self.design.get_pins(pin_name): - self.design.add_rect(pin.layer, - pin.ll(), - pin.width(), - pin.height()) - - # Remove the pin shape(s) - self.design.remove_layout_pin(pin_name) - - # prepare pins for every instances - for pin_name in ["vdd"]: - count =0 - for pin in self.design.control_logic_insts[0].get_pins(pin_name): - debug.warning("vdd pin shape -> ll:{0} ur:{1}".format(pin.ll(), pin.ur())) - """ - count = count + 1 - new_pin = graph_shape("gnd", pin.rect, pin.layer) - source = new_pin - source_center_x = pin.center().x - target_ur_y = self.new_pins[self.ext_gnd_name][1].center().y + 0.5 * self.new_pins[self.ext_gnd_name][1].height() - ll = vector(source_center_x - 2.6 - 0.5 * self.track_wire, pin.center().y - 0.5 * self.track_wire)#target_ur_y - self.track_wire) - ur = vector(source_center_x - 2.6 + 0.5 * self.track_wire, pin.center().y + 0.5 * self.track_wire)#target_ur_y) - target = graph_shape("fake", [ll,ur], pin.layer) - # Create the graph - g = graph(self) - g.create_graph(source, target) - # Find the shortest path from source to target - path = g.find_shortest_path() - # If no path is found, throw an error - if path is None: - self.write_debug_gds(gds_name="{}error.gds".format(OPTS.openram_temp), g=g, source=source, target=target) - debug.error("Couldn't route from {} to {}.".format(source, target), -1) - # Create the path shapes on layout - new_wires, new_vias = self.add_path(path) - # Find the recently added shapes - self.find_blockages(pin_name, new_wires) - self.find_vias(new_vias) - """ - debug.warning("vdd of wmask number -> {0}".format(count)) - debug.warning("instance postion -> {0} {1}".format(self.design.control_logic_insts[0].lx(), self.design.control_logic_insts[0].by())) - - # print pins_all - for pin in self.all_pins: - debug.warning("all_pins -> {0}".format(pin)) - - def check_overlap(self, moat_pin, io_pin_names): # use all the IO pins(at correspoding edge) to check overlap, check 1 moat vdd pin, give the corresponding target/source position as list, and connect them add_distance = 0 @@ -361,7 +310,7 @@ def check_overlap(self, moat_pin, io_pin_names): self.moat_pins_bottom.append(moat_pin_route) elif edge == "top": add_distance = self.via2_via3_pitch # if shift, need to fulfill via2-via3 spacing, top/bottom only - pin_too_close = any(abs(io_pin.center().x - source_center.x) < self.track_width for io_pin in self.io_pins_top) + pin_too_close = any(abs(io_pin.center().x - source_center.x) < (self.track_width + 0.1) for io_pin in self.io_pins_top) tmp_center = vector(source_center.x, source_center.y) while pin_too_close: tmp_center = vector(source_center.x, source_center.y) @@ -370,7 +319,7 @@ def check_overlap(self, moat_pin, io_pin_names): tmp_center = vector((tmp_center.x + add_distance), tmp_center.y) else: # left shift tmp_center = vector((tmp_center.x - add_distance), tmp_center.y) - pin_too_close = any(abs(io_pin.center().x - tmp_center.x) < self.track_width for io_pin in self.io_pins_top) + pin_too_close = any(abs(io_pin.center().x - tmp_center.x) < (self.track_width + 0.1) for io_pin in self.io_pins_top) direction = - direction # the nearst vdd ring vdd_ring = self.new_pins["vdd"][0] # order in list -> "top", "bottom", "right", "left"] @@ -628,7 +577,7 @@ def get_closest_edge(self, pin): return "right" return "top" - + def find_closest_edge(self, pin): """ Use to find the edge, where the io pin locats """ @@ -796,7 +745,7 @@ def get_mst_with_ring(self, pins, ring_pins, pin_name="vdd"): # Prepare the pins that are allowed to connect to the moat pins. # Specical handle gnd ring candidate_pins = [] - max_distance = 13 + max_distance = 20#13 if pin_name == "gnd": ring_pins = [] ring_pins = self.new_pins[pin_name] diff --git a/compiler/sram.py b/compiler/sram.py index f7e9b46d4..98127aa67 100644 --- a/compiler/sram.py +++ b/compiler/sram.py @@ -114,7 +114,21 @@ def __init__(self, sram_config=None, name=None, route_option="classic"): break elif self.route_option == "fast": if not OPTS.netlist_only: - self.s.create_layout(route_option=route_option) + i = 0 + while i < 10: + debug.warning("current i: i = {0}".format(i)) + try: + self.s.create_layout(position_add=i, route_option=route_option) + except AssertionError as e: + i = i + 1 + if i == 9: #failed in routing + debug.error("Failed in routing", -1) + break + del self.s + self.s = sram(name, sram_config) + self.s.create_netlist() + continue + break if not OPTS.is_unit_test: print_time("SRAM creation", datetime.datetime.now(), start_time) diff --git a/sram_compiler.py b/sram_compiler.py index e0727ea32..9d5fc4b3b 100755 --- a/sram_compiler.py +++ b/sram_compiler.py @@ -70,7 +70,7 @@ # Create an SRAM (we can also pass sram_config, see documentation/tutorials for details) from openram import sram -s = sram(route_option="fast")# "classic" or "fast" +s = sram(route_option="classic")# "classic" or "fast" # Output the files for the resulting SRAM s.save() From 474a240f38062a05fa1b6907c7d01613db3fc07b Mon Sep 17 00:00:00 2001 From: FriedrichWu Date: Sat, 21 Dec 2024 18:22:06 +0100 Subject: [PATCH 20/26] move apporach select to options.py --- compiler/modules/sram_1bank.py | 4 ++-- compiler/options.py | 2 ++ compiler/sram.py | 9 ++++----- sram_compiler.py | 2 +- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/compiler/modules/sram_1bank.py b/compiler/modules/sram_1bank.py index 6cc206053..370f5e52c 100644 --- a/compiler/modules/sram_1bank.py +++ b/compiler/modules/sram_1bank.py @@ -408,7 +408,7 @@ def route_escape_pins(self, bbox=None, mod=0, route_option="classic"): design=self, mod=mod) rtr.route(self.pins_to_route) - elif route_option == "fast": + elif route_option == "quality": # use io_pin_placer # put the IO pins at the edge from openram.router.io_pin_placer import io_pin_placer as placer @@ -1135,7 +1135,7 @@ def route_layout(self, mod=0, route_option="classic"): if OPTS.route_supplies: if route_option == "classic": self.route_supplies(init_bbox) - else: # fast + else: # quality self.route_supplies_constructive(init_bbox) def route_dffs(self, add_routes=True): diff --git a/compiler/options.py b/compiler/options.py index d97ee70ed..4d1fb861f 100644 --- a/compiler/options.py +++ b/compiler/options.py @@ -52,6 +52,8 @@ class options(optparse.Values): words_per_row = None num_spare_rows = 0 num_spare_cols = 0 + # Route approach + route_approach = "classic"# "classic" or "quality" ################### # ROM configuration options diff --git a/compiler/sram.py b/compiler/sram.py index 98127aa67..9260551b7 100644 --- a/compiler/sram.py +++ b/compiler/sram.py @@ -20,7 +20,7 @@ class sram(): results. We can later add visualizer and other high-level functions as needed. """ - def __init__(self, sram_config=None, name=None, route_option="classic"): + def __init__(self, sram_config=None, name=None): # Create default configs if custom config isn't provided if sram_config is None: @@ -48,7 +48,6 @@ def __init__(self, sram_config=None, name=None, route_option="classic"): start_time = datetime.datetime.now() self.name = name - self.route_option = route_option # "classic" or "fast" from openram.modules.sram_1bank import sram_1bank as sram @@ -57,7 +56,7 @@ def __init__(self, sram_config=None, name=None, route_option="classic"): self.s.create_netlist()# not placed & routed jet # choose the routung method, maze router or constructive - if self.route_option == "classic": + if OPTS.route_approach == "classic": cur_state = "IDLE" if not OPTS.netlist_only: i = 0 @@ -112,13 +111,13 @@ def __init__(self, sram_config=None, name=None, route_option="classic"): else: cur_state = "FINISH" break - elif self.route_option == "fast": + elif OPTS.route_approach == "quality": if not OPTS.netlist_only: i = 0 while i < 10: debug.warning("current i: i = {0}".format(i)) try: - self.s.create_layout(position_add=i, route_option=route_option) + self.s.create_layout(position_add=i, route_option="quality") except AssertionError as e: i = i + 1 if i == 9: #failed in routing diff --git a/sram_compiler.py b/sram_compiler.py index 9d5fc4b3b..989a524ed 100755 --- a/sram_compiler.py +++ b/sram_compiler.py @@ -70,7 +70,7 @@ # Create an SRAM (we can also pass sram_config, see documentation/tutorials for details) from openram import sram -s = sram(route_option="classic")# "classic" or "fast" +s = sram() # Output the files for the resulting SRAM s.save() From 74cab877820369a0762ed5003409ecd737e354a8 Mon Sep 17 00:00:00 2001 From: FriedrichWu Date: Sat, 21 Dec 2024 18:57:06 +0100 Subject: [PATCH 21/26] add tech fix --- technology/sky130/tech/tech.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/technology/sky130/tech/tech.py b/technology/sky130/tech/tech.py index 0df22cb7d..b52a35b3b 100755 --- a/technology/sky130/tech/tech.py +++ b/technology/sky130/tech/tech.py @@ -502,7 +502,8 @@ drc.add_layer("nwell", width=0.840, spacing=1.270) - +# nwell.6 Minimum enclosure of nwell hole by deep nwell outside UHVI +drc["minclosure_nwell_by_dnwell"] = 1.030 # poly.1a Minimum width of poly # poly.2 Minimum spacing of poly AND active drc.add_layer("poly", @@ -662,7 +663,8 @@ drc.add_layer("via3", width=0.200, spacing=0.200) - +# via3.12 Minimum spacing of via3 to via2 (cu) +drc["via3_to_via2"] = 0.180 # m4.1 Minimum width of metal4 # m4.2 Minimum spacing of metal4 # m4.7 Minimum area of metal4 @@ -748,6 +750,7 @@ spice["inv_leakage"] = 1 # Leakage power of inverter in nW spice["nand2_leakage"] = 1 # Leakage power of 2-input nand in nW spice["nand3_leakage"] = 1 # Leakage power of 3-input nand in nW +spice["nand4_leakage"] = 1 # Leakage power of 4-input nand in nW spice["nor2_leakage"] = 1 # Leakage power of 2-input nor in nW spice["dff_leakage"] = 1 # Leakage power of flop in nW From 70ed2a506e96068be58f7b527ce9937f00740a71 Mon Sep 17 00:00:00 2001 From: FriedrichWu Date: Sun, 22 Dec 2024 12:14:57 +0100 Subject: [PATCH 22/26] deleting spacing, add ci test, fixing merge error --- .gitlab-ci.yml | 20 +++++++ compiler/base/hierarchy_layout.py | 14 ++--- compiler/modules/sram_1bank.py | 10 ++-- compiler/router/router.py | 2 +- compiler/router/supply_placer.py | 90 +++++++++++++++---------------- 5 files changed, 78 insertions(+), 58 deletions(-) create mode 100644 .gitlab-ci.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 000000000..871201e02 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,20 @@ +default: + image: ubuntu:latest + before_script: + - apt-get update && apt-get install -y git wget make gcc curl + - git checkout ci_test + - git pull origin ci_test + - chmod +x ./install_conda.sh + - ./install_conda.sh + - echo "export OPENRAM_HOME="/builds/asic_non_nda/openramenhanced/compiler"" >> ~/.bashrc + - echo "export OPENRAM_TECH="/builds/asic_non_nda/openramenhance/technology"" >> ~/.bashrc + - echo "export PYTHONPATH=$OPENRAM_HOME" >> ~/.bashrc + - source ~/.bashrc + - source miniconda/bin/activate + - make sky130-pdk + - make sky130-install + script: + - pwd + - cd ./macros + - make sky130_sram_1rw_tiny + \ No newline at end of file diff --git a/compiler/base/hierarchy_layout.py b/compiler/base/hierarchy_layout.py index 7562bb69d..f8d371a90 100644 --- a/compiler/base/hierarchy_layout.py +++ b/compiler/base/hierarchy_layout.py @@ -1947,7 +1947,7 @@ def add_power_pin_m2(self, name, loc, directions=None, start_layer="m1"): width=width, height=height) - return pin + return pin def add_power_pin(self, name, loc, directions=None, start_layer="m1"): # Hack for min area @@ -2087,12 +2087,12 @@ def add_dnwell(self, bbox=None, inflate=1, route_option="classic"): # Hack for sky130 klayout drc rule nwell.6 if OPTS.tech_name == "sky130": - # Apply the drc rule + # Apply the drc rule # Add the dnwell self.add_rect("dnwell", offset=ll - vector(0.5 * self.nwell_width, 0.5 * self.nwell_width) - vector(drc["minclosure_nwell_by_dnwell"], drc["minclosure_nwell_by_dnwell"]), height=ur.y - ll.y + self.nwell_width + 2 * drc["minclosure_nwell_by_dnwell"], - width=ur.x - ll.x + self.nwell_width + 2 * drc["minclosure_nwell_by_dnwell"]) + width=ur.x - ll.x + self.nwell_width + 2 * drc["minclosure_nwell_by_dnwell"]) else: # other tech # Add the dnwell self.add_rect("dnwell", @@ -2171,11 +2171,11 @@ def add_dnwell(self, bbox=None, inflate=1, route_option="classic"): pin = self.add_power_pin(name="vdd", loc=loc, start_layer="li") - elif route_option == "fast": + elif route_option == "quality": pin = self.add_power_pin_m2(name="vdd", loc=loc, - start_layer="li") - moat_pins.append(pin) + start_layer="li") + moat_pins.append(pin) count += 1 loc += nwell_offset.scale(0, tap_spacing) @@ -2197,7 +2197,7 @@ def add_dnwell(self, bbox=None, inflate=1, route_option="classic"): pin = self.add_power_pin(name="vdd", loc=loc, start_layer="li") - elif route_option == "fast": + elif route_option == "quality": pin = self.add_power_pin_m2(name="vdd", loc=loc, start_layer="li") diff --git a/compiler/modules/sram_1bank.py b/compiler/modules/sram_1bank.py index 370f5e52c..da9c6a8dd 100644 --- a/compiler/modules/sram_1bank.py +++ b/compiler/modules/sram_1bank.py @@ -1135,7 +1135,7 @@ def route_layout(self, mod=0, route_option="classic"): if OPTS.route_supplies: if route_option == "classic": self.route_supplies(init_bbox) - else: # quality + elif route_option == "quality": # quality self.route_supplies_constructive(init_bbox) def route_dffs(self, add_routes=True): @@ -1264,7 +1264,7 @@ def route_data_dffs(self, port, add_routes): dff_right_x = self.data_dff_insts[0].rx() # check if row address dffs are overlaped with dff area, bank position as reference if self.bank_inst.rx() < (dff_right_x + 2 * self.m4_pitch): - debug.warning("m4 pitch ----> {0}".format(self.m4_pitch)) + debug.warning("m4 pitch ----> {0}".format(self.m4_pitch)) debug.warning("m3 pitch ----> {0}".format(self.m3_pitch)) debug.warning("m4_non_pref pitch ----> {0}".format(self.m4_nonpref_pitch)) debug.warning("lower row addr dff: {0}".format(self.row_addr_dff_insts[1].by())) @@ -1273,7 +1273,7 @@ def route_data_dffs(self, port, add_routes): if y_bottom < self.row_addr_dff_insts[1].by(): y_bottom_most = y_bottom else: - y_bottom_most = self.row_addr_dff_insts[1].by() + y_bottom_most = self.row_addr_dff_insts[1].by() # the upper track should below the lower one extra_offset = 6 * self.m3_pitch + (self.bank_inst.by() - (y_bottom_most - self.m4_nonpref_pitch)) else: # do not need take care of address dff 1, since it's far away @@ -1301,7 +1301,7 @@ def route_data_dffs(self, port, add_routes): dff_left_x = self.data_dff_insts[1].lx() # check if row address dffs are overlaped with dff area if self.bank_inst.lx() > (dff_left_x - 2 * self.m4_pitch): - debug.warning("m4 pitch ----> {0}".format(self.m4_pitch)) + debug.warning("m4 pitch ----> {0}".format(self.m4_pitch)) debug.warning("m3 pitch ----> {0}".format(self.m3_pitch)) debug.warning("m4_non_pref pitch ----> {0}".format(self.m4_nonpref_pitch)) # the bottom track should also above row address decoder @@ -1309,7 +1309,7 @@ def route_data_dffs(self, port, add_routes): # do not need change since first track is high enough extra_offset = y_offset - self.bank.height # height could be use since bank at 0,0 else: # make it higher tham row address decoder - extra_offset = self.row_addr_dff_insts[0].uy() + self.m4_nonpref_pitch - self.bank_inst.height + extra_offset = self.row_addr_dff_insts[0].uy() + self.m4_nonpref_pitch - self.bank_inst.height # update the new y_offset y_offset = self.row_addr_dff_insts[0].uy() + self.m4_nonpref_pitch else: # do not need to take care address dff0, since it's far away diff --git a/compiler/router/router.py b/compiler/router/router.py index cf3c17ffa..e9e424523 100644 --- a/compiler/router/router.py +++ b/compiler/router/router.py @@ -111,7 +111,7 @@ def find_pins(self, pin_name): self.all_pins.update(pin_set) - def find_pins_inside(self, pin_name): + def find_pins_inside(self, pin_name): # find pins except moat, power ring, the moat pins will be store as set and return """ Find the pins with the given name. """ debug.info(4, "Finding all pins for {}".format(pin_name)) diff --git a/compiler/router/supply_placer.py b/compiler/router/supply_placer.py index aa47ec5dc..bc759549d 100644 --- a/compiler/router/supply_placer.py +++ b/compiler/router/supply_placer.py @@ -107,7 +107,7 @@ def route_outside(self, vdd_name="vdd", gnd_name="gnd", io_pin_names=None): # Report routed count routed_count += 1 debug.info(2, "Routed {} of {} supply pins".format(routed_count, routed_max)) - # finsih + # finsih self.replace_layout_pins() @@ -163,14 +163,14 @@ def route_inside(self, vdd_name="vdd", gnd_name="gnd"): # Report routed count routed_count += 1 debug.info(2, "Routed {} of {} supply pins".format(routed_count, routed_max)) - # finsih + # finsih self.replace_layout_pins() def route_moat(self, io_pin_names): # route the vdd pins at the moat # io_pin_names is a list - # the moat vdd shape will also be created and stored in the list + # the moat vdd shape will also be created and stored in the list for moat_pin in self.moat_pins: self.check_overlap(moat_pin, io_pin_names) @@ -188,7 +188,7 @@ def replace_layout_pins(self): # Remove the pin shape(s) self.design.remove_layout_pin(pin_name) - + # Get new pins, change the name of ring to extern supply name # vccd1 ring pins = self.get_new_pins("vdd") @@ -205,7 +205,7 @@ def replace_layout_pins(self): pin.layer, pin.ll(), pin.width(), - pin.height()) + pin.height()) def prepare_selected_moat_pins(self): @@ -236,7 +236,7 @@ def prepare_selected_moat_pins(self): selected_moat_pins.extend(filtered_moat_pins_right) selected_moat_pins.extend(self.moat_pins_top) return selected_moat_pins - + else: # only 1 port # in order to save runtime # top -> moat pins all @@ -256,7 +256,7 @@ def prepare_selected_moat_pins(self): return selected_moat_pins - def check_overlap(self, moat_pin, io_pin_names): + def check_overlap(self, moat_pin, io_pin_names): # use all the IO pins(at correspoding edge) to check overlap, check 1 moat vdd pin, give the corresponding target/source position as list, and connect them add_distance = 0 direction = 1 @@ -281,7 +281,7 @@ def check_overlap(self, moat_pin, io_pin_names): vdd_ring = self.new_pins["vdd"][1] # order in list -> "top", "bottom", "right", "left"] # bottom ring's y position at it's top target_egde_y = vdd_ring.center().y + 0.5 * vdd_ring.height() - if tmp_center == source_center: # no overlap + if tmp_center == source_center: # no overlap # no jog, direct return the source/target center position # the target center position, should consider enought space for via target_point = vector(tmp_center.x, (target_egde_y - 0.5 * self.track_wire)) @@ -292,13 +292,13 @@ def check_overlap(self, moat_pin, io_pin_names): ll = vector(source_point.x - 0.5 * self.track_wire, source_point.y - 0.5 * self.track_wire) ur = vector(source_point.x + 0.5 * self.track_wire, source_point.y + 0.5 * self.track_wire) rect = [ll, ur] - moat_pin_route = graph_shape("vdd", rect, "m4") - self.moat_pins_bottom.append(moat_pin_route) + moat_pin_route = graph_shape("vdd", rect, "m4") + self.moat_pins_bottom.append(moat_pin_route) else: # need jog # shift the center # add rectangle at same layer (original) - intermediate_point = vector(tmp_center.x, tmp_center.y) - source_point = vector(source_center.x, source_center.y) + intermediate_point = vector(tmp_center.x, tmp_center.y) + source_point = vector(source_center.x, source_center.y) target_point = vector(tmp_center.x, (target_egde_y - 0.5 * self.track_wire)) point_list = [source_point, intermediate_point, target_point] self.add_wire(point_list, vertical=True) @@ -306,8 +306,8 @@ def check_overlap(self, moat_pin, io_pin_names): ll = vector(intermediate_point.x - 0.5 * self.track_wire, intermediate_point.y - 0.5 * self.track_wire) ur = vector(intermediate_point.x + 0.5 * self.track_wire, intermediate_point.y + 0.5 * self.track_wire) rect = [ll, ur] - moat_pin_route = graph_shape("vdd", rect, "m4") - self.moat_pins_bottom.append(moat_pin_route) + moat_pin_route = graph_shape("vdd", rect, "m4") + self.moat_pins_bottom.append(moat_pin_route) elif edge == "top": add_distance = self.via2_via3_pitch # if shift, need to fulfill via2-via3 spacing, top/bottom only pin_too_close = any(abs(io_pin.center().x - source_center.x) < (self.track_width + 0.1) for io_pin in self.io_pins_top) @@ -325,7 +325,7 @@ def check_overlap(self, moat_pin, io_pin_names): vdd_ring = self.new_pins["vdd"][0] # order in list -> "top", "bottom", "right", "left"] # top ring's y position at it's bottom target_egde_y = vdd_ring.center().y - 0.5 * vdd_ring.height() - if tmp_center == source_center: # no overlap + if tmp_center == source_center: # no overlap # no jog, direct return the source/target center position # the target center position, should consider enought space for via target_point = vector(tmp_center.x, (target_egde_y + 0.5 * self.track_wire)) @@ -336,13 +336,13 @@ def check_overlap(self, moat_pin, io_pin_names): ll = vector(source_point.x - 0.5 * self.track_wire, source_point.y - 0.5 * self.track_wire) ur = vector(source_point.x + 0.5 * self.track_wire, source_point.y + 0.5 * self.track_wire) rect = [ll, ur] - moat_pin_route = graph_shape("vdd", rect, "m4") - self.moat_pins_top.append(moat_pin_route) + moat_pin_route = graph_shape("vdd", rect, "m4") + self.moat_pins_top.append(moat_pin_route) else: # need jog # shift the center # add rectangle at same layer (original) - intermediate_point = vector(tmp_center.x, tmp_center.y) - source_point = vector(source_center.x, source_center.y) + intermediate_point = vector(tmp_center.x, tmp_center.y) + source_point = vector(source_center.x, source_center.y) target_point = vector(tmp_center.x, (target_egde_y + 0.5 * self.track_wire)) point_list = [source_point, intermediate_point, target_point] self.add_wire(point_list, vertical=True) @@ -350,8 +350,8 @@ def check_overlap(self, moat_pin, io_pin_names): ll = vector(intermediate_point.x - 0.5 * self.track_wire, intermediate_point.y - 0.5 * self.track_wire) ur = vector(intermediate_point.x + 0.5 * self.track_wire, intermediate_point.y + 0.5 * self.track_wire) rect = [ll, ur] - moat_pin_route = graph_shape("vdd", rect, "m4") - self.moat_pins_top.append(moat_pin_route) + moat_pin_route = graph_shape("vdd", rect, "m4") + self.moat_pins_top.append(moat_pin_route) elif edge == "left": pin_too_close = any(abs(io_pin.center().y - source_center.y) < self.track_width for io_pin in self.io_pins_left) tmp_center = vector(source_center.x, source_center.y) @@ -368,7 +368,7 @@ def check_overlap(self, moat_pin, io_pin_names): vdd_ring = self.new_pins["vdd"][3] # order in list -> "top", "bottom", "right", "left"] # left ring's x position at it's right target_egde_x = vdd_ring.center().x + 0.5 * vdd_ring.width() - if tmp_center == source_center: # no overlap + if tmp_center == source_center: # no overlap # no jog, direct return the source/target center position # the target center position, should consider enought space for via target_point = vector((target_egde_x - 0.5 * self.track_wire), tmp_center.y) @@ -379,13 +379,13 @@ def check_overlap(self, moat_pin, io_pin_names): ll = vector(source_point.x - 0.5 * self.track_wire, source_point.y - 0.5 * self.track_wire) ur = vector(source_point.x + 0.5 * self.track_wire, source_point.y + 0.5 * self.track_wire) rect = [ll, ur] - moat_pin_route = graph_shape("vdd", rect, "m3") + moat_pin_route = graph_shape("vdd", rect, "m3") self.moat_pins_left.append(moat_pin_route) else: # need jog # shift the center # add rectangle at same layer (original) - intermediate_point = vector(tmp_center.x, tmp_center.y) - source_point = vector(source_center.x, source_center.y) + intermediate_point = vector(tmp_center.x, tmp_center.y) + source_point = vector(source_center.x, source_center.y) target_point = vector((target_egde_x - 0.5 * self.track_wire), tmp_center.y) point_list = [source_point, intermediate_point, target_point] self.add_wire(point_list, vertical=False) @@ -393,7 +393,7 @@ def check_overlap(self, moat_pin, io_pin_names): ll = vector(intermediate_point.x - 0.5 * self.track_wire, intermediate_point.y - 0.5 * self.track_wire) ur = vector(intermediate_point.x + 0.5 * self.track_wire, intermediate_point.y + 0.5 * self.track_wire) rect = [ll, ur] - moat_pin_route = graph_shape("vdd", rect, "m3") + moat_pin_route = graph_shape("vdd", rect, "m3") self.moat_pins_left.append(moat_pin_route) else: #right pin_too_close = any(abs(io_pin.center().y - source_center.y) < self.track_width for io_pin in self.io_pins_right) @@ -411,7 +411,7 @@ def check_overlap(self, moat_pin, io_pin_names): vdd_ring = self.new_pins["vdd"][2] # order in list -> "top", "bottom", "right", "left"] # right ring's y position at it's left target_egde_x = vdd_ring.center().x - 0.5 * vdd_ring.width() - if tmp_center == source_center: # no overlap + if tmp_center == source_center: # no overlap # no jog, direct return the source/target center position # the target center position, should consider enought space for via target_point = vector((target_egde_x + 0.5 * self.track_wire), tmp_center.y) @@ -422,13 +422,13 @@ def check_overlap(self, moat_pin, io_pin_names): ll = vector(source_point.x - 0.5 * self.track_wire, source_point.y - 0.5 * self.track_wire) ur = vector(source_point.x + 0.5 * self.track_wire, source_point.y + 0.5 * self.track_wire) rect = [ll, ur] - moat_pin_route = graph_shape("vdd", rect, "m3") - self.moat_pins_right.append(moat_pin_route) + moat_pin_route = graph_shape("vdd", rect, "m3") + self.moat_pins_right.append(moat_pin_route) else: # need jog # shift the center # add rectangle at same layer (original) - intermediate_point = vector(tmp_center.x, tmp_center.y) - source_point = vector(source_center.x, source_center.y) + intermediate_point = vector(tmp_center.x, tmp_center.y) + source_point = vector(source_center.x, source_center.y) target_point = vector((target_egde_x + 0.5 * self.track_wire) ,tmp_center.y) point_list = [source_point, intermediate_point, target_point] self.add_wire(point_list, vertical=False) @@ -436,15 +436,15 @@ def check_overlap(self, moat_pin, io_pin_names): ll = vector(intermediate_point.x - 0.5 * self.track_wire, intermediate_point.y - 0.5 * self.track_wire) ur = vector(intermediate_point.x + 0.5 * self.track_wire, intermediate_point.y + 0.5 * self.track_wire) rect = [ll, ur] - moat_pin_route = graph_shape("vdd", rect, "m3") - self.moat_pins_right.append(moat_pin_route) + moat_pin_route = graph_shape("vdd", rect, "m3") + self.moat_pins_right.append(moat_pin_route) def add_wire(self, point_list, vertical=False): if vertical == True: # m4 line, need start via3(m3 -> m4), end via3(m3 -> m4) if len(point_list) == 2: # direct connect # start via - self.add_via(point=point_list[0], + self.add_via(point=point_list[0], from_layer="m3", to_layer="m4") self.add_via(point=point_list[0], @@ -484,10 +484,10 @@ def add_wire(self, point_list, vertical=False): self.add_via(point=point_list[2], from_layer="m4", to_layer="m4") # shape - else: # m3 line, need start via2(m2 -> m3), end via3(m3 -> m4) + else: # m3 line, need start via2(m2 -> m3), end via3(m3 -> m4) if len(point_list) == 2: # direct connect # start via - self.add_via(point=point_list[0], + self.add_via(point=point_list[0], from_layer="m2", to_layer="m3") self.add_via(point=point_list[0], @@ -500,11 +500,11 @@ def add_wire(self, point_list, vertical=False): # end via self.add_via(point=point_list[1], from_layer="m3", - to_layer="m4") + to_layer="m4") self.add_via(point=point_list[1], from_layer="m3", to_layer="m3") # shape - elif len(point_list) == 3: # need intermediate point + elif len(point_list) == 3: # need intermediate point # jog self.add_line(point_1=point_list[0], point_2=point_list[1], @@ -537,9 +537,9 @@ def add_via(self, point, from_layer="m3", to_layer="m4"): # via could be via2(m2 -> m3), via3(m3 -> m4) # or a shape at same layer if from_layer == to_layer: - self.design.add_rect_center(layer=from_layer, - offset=point, - width=self.track_wire, + self.design.add_rect_center(layer=from_layer, + offset=point, + width=self.track_wire, height=self.track_wire) else: self.design.add_via_stack_center(from_layer=from_layer, @@ -598,7 +598,7 @@ def find_closest_edge(self, pin): elif min_diff == ur_diff_x: self.io_pins_right.append(pin) else: - self.io_pins_top.append(pin) + self.io_pins_top.append(pin) def add_side_pin(self, pin_name, side, num_vias=3, num_fake_pins=4): @@ -742,7 +742,7 @@ def get_mst_with_ring(self, pins, ring_pins, pin_name="vdd"): """ Extend the MST logic to connect internal pins to the nearest external ring pins. """ - # Prepare the pins that are allowed to connect to the moat pins. + # Prepare the pins that are allowed to connect to the moat pins. # Specical handle gnd ring candidate_pins = [] max_distance = 20#13 @@ -755,7 +755,7 @@ def get_mst_with_ring(self, pins, ring_pins, pin_name="vdd"): if max_distance is None or dist <= max_distance: candidate_pins.append(pin) break - + # Compute the MST for internal pins mst_pairs = self.get_mst_pairs(pins) @@ -780,7 +780,7 @@ def get_mst_with_ring(self, pins, ring_pins, pin_name="vdd"): internal_to_ring_pairs.append((pin, nearest_ring_pin)) # Mark the ring pin as used if the pin is VDD if pin_name == "vdd": - used_ring_pins.add(nearest_ring_pin) + used_ring_pins.add(nearest_ring_pin) # Combine internal MST pairs and external connections full_connections = mst_pairs + internal_to_ring_pairs From 691f7a989e342ac3538fc04b1d27be9525a1c79d Mon Sep 17 00:00:00 2001 From: FriedrichWu Date: Mon, 23 Dec 2024 17:19:19 +0100 Subject: [PATCH 23/26] speed up constructive approach in 1rw-only --- compiler/router/supply_placer.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/compiler/router/supply_placer.py b/compiler/router/supply_placer.py index bc759549d..bf72fddee 100644 --- a/compiler/router/supply_placer.py +++ b/compiler/router/supply_placer.py @@ -266,7 +266,7 @@ def check_overlap(self, moat_pin, io_pin_names): source_center = moat_pin.center() if edge == "bottom": add_distance = self.via2_via3_pitch # if shift, need to fulfill via2-via3 spacing, top/bottom only - pin_too_close = any(abs(io_pin.center().x - source_center.x) < self.track_width for io_pin in self.io_pins_bottom) + pin_too_close = any(abs(io_pin.center().x - source_center.x) < (self.track_width + 0.1) for io_pin in self.io_pins_bottom) tmp_center = vector(source_center.x, source_center.y) while pin_too_close: tmp_center = vector(source_center.x, source_center.y) @@ -275,7 +275,7 @@ def check_overlap(self, moat_pin, io_pin_names): tmp_center = vector((tmp_center.x + add_distance), tmp_center.y) else: # left shift tmp_center = vector((tmp_center.x - add_distance), tmp_center.y) - pin_too_close = any(abs(io_pin.center().x - tmp_center.x) < self.track_width for io_pin in self.io_pins_bottom) + pin_too_close = any(abs(io_pin.center().x - tmp_center.x) < (self.track_width + 0.1) for io_pin in self.io_pins_bottom) direction = - direction # the nearst vdd ring vdd_ring = self.new_pins["vdd"][1] # order in list -> "top", "bottom", "right", "left"] @@ -353,7 +353,7 @@ def check_overlap(self, moat_pin, io_pin_names): moat_pin_route = graph_shape("vdd", rect, "m4") self.moat_pins_top.append(moat_pin_route) elif edge == "left": - pin_too_close = any(abs(io_pin.center().y - source_center.y) < self.track_width for io_pin in self.io_pins_left) + pin_too_close = any(abs(io_pin.center().y - source_center.y) < (self.track_width + 0.1) for io_pin in self.io_pins_left) tmp_center = vector(source_center.x, source_center.y) while pin_too_close: tmp_center = vector(source_center.x, source_center.y) @@ -362,7 +362,7 @@ def check_overlap(self, moat_pin, io_pin_names): tmp_center = vector(tmp_center.x, (tmp_center.y + add_distance)) else: # down shift tmp_center = vector(tmp_center.x, (tmp_center.y - add_distance)) - pin_too_close = any(abs(io_pin.center().y - tmp_center.y) < self.track_width for io_pin in self.io_pins_left) + pin_too_close = any(abs(io_pin.center().y - tmp_center.y) < (self.track_width + 0.1) for io_pin in self.io_pins_left) direction = - direction # the nearst vdd ring vdd_ring = self.new_pins["vdd"][3] # order in list -> "top", "bottom", "right", "left"] @@ -396,7 +396,7 @@ def check_overlap(self, moat_pin, io_pin_names): moat_pin_route = graph_shape("vdd", rect, "m3") self.moat_pins_left.append(moat_pin_route) else: #right - pin_too_close = any(abs(io_pin.center().y - source_center.y) < self.track_width for io_pin in self.io_pins_right) + pin_too_close = any(abs(io_pin.center().y - source_center.y) < (self.track_width + 0.1) for io_pin in self.io_pins_right) tmp_center = vector(source_center.x, source_center.y) while pin_too_close: tmp_center = vector(source_center.x, source_center.y) @@ -405,7 +405,7 @@ def check_overlap(self, moat_pin, io_pin_names): tmp_center = vector(tmp_center.x, (tmp_center.y + add_distance)) else: # down shift tmp_center = vector(tmp_center.x, (tmp_center.y - add_distance)) - pin_too_close = any(abs(io_pin.center().y - tmp_center.y) < self.track_width for io_pin in self.io_pins_right) + pin_too_close = any(abs(io_pin.center().y - tmp_center.y) < (self.track_width + 0.1) for io_pin in self.io_pins_right) direction = - direction # the nearst vdd ring vdd_ring = self.new_pins["vdd"][2] # order in list -> "top", "bottom", "right", "left"] @@ -750,6 +750,17 @@ def get_mst_with_ring(self, pins, ring_pins, pin_name="vdd"): ring_pins = [] ring_pins = self.new_pins[pin_name] for pin in pins: + # Special handle vdd/gnd in 1port sram(1rw), only at bank top-right is allowed + if len(self.design.all_ports) == 1: + # check if pin is inside bank area + if (self.design.bank_inst.lx() < pin.center().x < self.design.bank_inst.rx()) and (self.design.bank_inst.by() < pin.center().y < self.design.bank_inst.uy()): + # add pin at top-right as candidate, not care the distance + if (pin.center().x > self.design.bank_inst.rx() - 14) and (pin.center().y > self.design.bank_inst.uy() - 14): + candidate_pins.append(pin) + continue + else:# pin in the other area of bank, do not care + continue + # 2 port situation, or the other pins outer bank in 1port situation for ring_pin in ring_pins: dist = pin.distance(ring_pin) if max_distance is None or dist <= max_distance: From 48a7065b3161eef380fe5a03c0c6b453f315b164 Mon Sep 17 00:00:00 2001 From: FriedrichWu Date: Mon, 23 Dec 2024 19:38:42 +0100 Subject: [PATCH 24/26] make channel router better --- compiler/base/channel_route.py | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/compiler/base/channel_route.py b/compiler/base/channel_route.py index 3afaf611a..728230a16 100644 --- a/compiler/base/channel_route.py +++ b/compiler/base/channel_route.py @@ -12,7 +12,7 @@ from openram.tech import drc from .vector import vector from .design import design - +from openram import OPTS class channel_net(): def __init__(self, net_name, pins, vertical): @@ -326,13 +326,24 @@ def add_horizontal_trunk_with_jog(self, max_x = max([pin.center().x for pin in pins]) min_x = min([pin.center().x for pin in pins]) min_y = min([pin.center().y for pin in pins]) + max_y = max([pin.center().y for pin in pins]) # see the channel is at top or bottom - if min_y < 0: # port0, min_x need to change - min_x = min_x - 0.1 # in order to add jog at the dff pins, avoid overlap with vdd pins, left shift vertical line at dout pin - port = 0 - else: # port1, max_x need to change - max_x = max_x - 0.1 # in order to add jog at the dff pins, avoid overlap with vdd pins, left shift vertical line at dout pin - port = 1 + if min_y < 0: # port0 + for pin in pins: + if pin.center().x == max_x: + if pin.center().y == max_y: + min_x = min_x - 0.1 # in order to add jog at the dff pins, avoid overlap with vdd pins, left shift vertical line at dout pin + else: # pin has max_x is under track + max_x = max_x - 0.1 + port = 0 + else: # port1 + for pin in pins: + if pin.center().x == max_x: + if pin.center().y == max_y: + max_x = max_x - 0.1 # in order to add jog at the dff pins, avoid overlap with vdd pins, left shift vertical line at dout pin + else: # pin has max_x is under track + min_x = min_x - 0.1 + port = 1 # if we are less than a pitch, just create a non-preferred layer jog non_preferred_route = max_x - min_x <= pitch half_layer_width = 0.5 * drc["minwidth_{0}".format(self.vertical_layer)] @@ -368,6 +379,8 @@ def add_horizontal_trunk_with_jog(self, # Route each pin to the trunk for pin in pins: + debug.warning("pin name in net ----> {0}".format(pin.name)) + debug.warning("wmask or not --> {0}".format(OPTS.write_size)) # Find the correct side of the pin if pin.cy() < trunk_offset.y: pin_pos = pin.center() From a0ff83c00a307d3179527e2c82b81f0a7b3cb947 Mon Sep 17 00:00:00 2001 From: FriedrichWu Date: Mon, 23 Dec 2024 23:44:21 +0100 Subject: [PATCH 25/26] solve overlap of wmask in channel router --- compiler/base/channel_route.py | 186 +++++++++++++++++++++++++++------ 1 file changed, 154 insertions(+), 32 deletions(-) diff --git a/compiler/base/channel_route.py b/compiler/base/channel_route.py index 728230a16..515cce788 100644 --- a/compiler/base/channel_route.py +++ b/compiler/base/channel_route.py @@ -13,6 +13,7 @@ from .vector import vector from .design import design from openram import OPTS +import re class channel_net(): def __init__(self, net_name, pins, vertical): @@ -318,6 +319,17 @@ def get_layer_pitch(self, layer): debug.error("Cannot find layer pitch.", -1) return (nonpref_pitch, pitch, pitch - space, space) + def check_need_jog(self, pin_name): + match = re.search(r'^din\d+_(\d+)$', pin_name) + if match: + number = int(match.group(1)) + if number % int(OPTS.write_size) == 0: + return True + else: + return False + else: + return False + def add_horizontal_trunk_with_jog(self, pins, trunk_offset, @@ -364,13 +376,39 @@ def add_horizontal_trunk_with_jog(self, to_layer=self.vertical_layer, offset=pin_pos) else: - pin_pos = pin.bc() - # No bend needed here - mid = vector(pin_pos.x, trunk_offset.y) - self.add_path(self.vertical_layer, [pin_pos, mid]) - self.add_via_stack_center(from_layer=pin.layer, - to_layer=self.vertical_layer, - offset=pin.bc()) + if self.check_need_jog(pin.name) and (OPTS.write_size != OPTS.word_size):# for port0, only pin above track needs to check + # right first + self.add_via_stack_center(from_layer=pin.layer, + to_layer=self.vertical_layer, + offset=pin.rc()) + point_1 = vector(pin.center().x + pitch, pin.center().y) + self.add_path(self.vertical_layer, [pin.rc(), point_1]) + # straight out of bank area + self.add_via_stack_center(from_layer=self.vertical_layer, + to_layer=self.vertical_layer, + offset=point_1) + point_2 = vector(point_1.x, point_1.y - 5.6) + self.add_path(self.vertical_layer, [point_1, point_2]) + # then left + self.add_via_stack_center(from_layer=self.vertical_layer, + to_layer=self.vertical_layer, + offset=point_2) + point_3 = vector(pin.center().x, pin.center().y - 5.6) + self.add_path(self.vertical_layer, [point_2, point_3]) + # back to normal + self.add_via_stack_center(from_layer=self.vertical_layer, + to_layer=self.vertical_layer, + offset=point_3) + mid = vector(point_3.x, trunk_offset.y) + self.add_path(self.vertical_layer, [point_3, mid]) + else: # do not need to care of wmask + pin_pos = pin.bc() + # No bend needed here + mid = vector(pin_pos.x, trunk_offset.y) + self.add_path(self.vertical_layer, [pin_pos, mid]) + self.add_via_stack_center(from_layer=pin.layer, + to_layer=self.vertical_layer, + offset=pin.bc()) else: # Add the horizontal trunk self.add_path(self.horizontal_layer, @@ -393,15 +431,44 @@ def add_horizontal_trunk_with_jog(self, to_layer=self.vertical_layer, offset=pin_pos) else: - pin_pos = pin.bc() - mid = vector(pin_pos.x, trunk_offset.y) - self.add_path(self.vertical_layer, [pin_pos, mid]) - self.add_via_center(layers=self.layer_stack, - offset=mid, - directions=self.directions) - self.add_via_stack_center(from_layer=pin.layer, - to_layer=self.vertical_layer, - offset=pin.bc()) + if self.check_need_jog(pin.name) and (OPTS.write_size != OPTS.word_size):# for port0, only pin above track needs to check + # right first + self.add_via_stack_center(from_layer=pin.layer, + to_layer=self.vertical_layer, + offset=pin.rc()) + point_1 = vector(pin.center().x + pitch, pin.center().y) + self.add_path(self.vertical_layer, [pin.rc(), point_1]) + # straight out of bank area + self.add_via_stack_center(from_layer=self.vertical_layer, + to_layer=self.vertical_layer, + offset=point_1) + point_2 = vector(point_1.x, point_1.y - 5.6) + self.add_path(self.vertical_layer, [point_1, point_2]) + # then left + self.add_via_stack_center(from_layer=self.vertical_layer, + to_layer=self.vertical_layer, + offset=point_2) + point_3 = vector(pin.center().x, pin.center().y - 5.6) + self.add_path(self.vertical_layer, [point_2, point_3]) + # back to normal + self.add_via_stack_center(from_layer=self.vertical_layer, + to_layer=self.vertical_layer, + offset=point_3) + mid = vector(point_3.x, trunk_offset.y) + self.add_path(self.vertical_layer, [point_3, mid]) + self.add_via_center(layers=self.layer_stack, + offset=mid, + directions=self.directions) + else: # do not need to care of wmask + pin_pos = pin.bc() + mid = vector(pin_pos.x, trunk_offset.y) + self.add_path(self.vertical_layer, [pin_pos, mid]) + self.add_via_center(layers=self.layer_stack, + offset=mid, + directions=self.directions) + self.add_via_stack_center(from_layer=pin.layer, + to_layer=self.vertical_layer, + offset=pin.bc()) else: # port 1, situation different, top need shift if non_preferred_route: # Add the horizontal trunk on the vertical layer! @@ -412,13 +479,39 @@ def add_horizontal_trunk_with_jog(self, # Route each pin to the trunk for pin in pins: if pin.cy() < trunk_offset.y: - pin_pos = pin.uc() - # No bend needed here - mid = vector(pin_pos.x, trunk_offset.y) - self.add_path(self.vertical_layer, [pin_pos, mid]) - self.add_via_stack_center(from_layer=pin.layer, - to_layer=self.vertical_layer, - offset=pin.uc()) + if self.check_need_jog(pin.name) and (OPTS.write_size != OPTS.word_size):# for port0, only pin above track needs to check + # right first + self.add_via_stack_center(from_layer=pin.layer, + to_layer=self.vertical_layer, + offset=pin.rc()) + point_1 = vector(pin.center().x + pitch, pin.center().y) + self.add_path(self.vertical_layer, [pin.rc(), point_1]) + # straight out of bank area + self.add_via_stack_center(from_layer=self.vertical_layer, + to_layer=self.vertical_layer, + offset=point_1) + point_2 = vector(point_1.x, point_1.y + 5.6) + self.add_path(self.vertical_layer, [point_1, point_2]) + # then left + self.add_via_stack_center(from_layer=self.vertical_layer, + to_layer=self.vertical_layer, + offset=point_2) + point_3 = vector(pin.center().x, pin.center().y + 5.6) + self.add_path(self.vertical_layer, [point_2, point_3]) + # back to normal + self.add_via_stack_center(from_layer=self.vertical_layer, + to_layer=self.vertical_layer, + offset=point_3) + mid = vector(point_3.x, trunk_offset.y) + self.add_path(self.vertical_layer, [point_3, mid]) + else: # do not need to care of wmask + pin_pos = pin.uc() + # No bend needed here + mid = vector(pin_pos.x, trunk_offset.y) + self.add_path(self.vertical_layer, [pin_pos, mid]) + self.add_via_stack_center(from_layer=pin.layer, + to_layer=self.vertical_layer, + offset=pin.uc()) else: pin_pos = pin.center() mid = vector(pin_pos.x - 0.1, trunk_offset.y) @@ -436,15 +529,44 @@ def add_horizontal_trunk_with_jog(self, for pin in pins: # Find the correct side of the pin if pin.cy() < trunk_offset.y: - pin_pos = pin.uc() - mid = vector(pin_pos.x, trunk_offset.y) - self.add_path(self.vertical_layer, [pin_pos, mid]) - self.add_via_center(layers=self.layer_stack, - offset=mid, - directions=self.directions) - self.add_via_stack_center(from_layer=pin.layer, - to_layer=self.vertical_layer, - offset=pin.uc()) + if self.check_need_jog(pin.name) and (OPTS.write_size != OPTS.word_size):# for port0, only pin above track needs to check + # right first + self.add_via_stack_center(from_layer=pin.layer, + to_layer=self.vertical_layer, + offset=pin.rc()) + point_1 = vector(pin.center().x + pitch, pin.center().y) + self.add_path(self.vertical_layer, [pin.rc(), point_1]) + # straight out of bank area + self.add_via_stack_center(from_layer=self.vertical_layer, + to_layer=self.vertical_layer, + offset=point_1) + point_2 = vector(point_1.x, point_1.y + 5.6) + self.add_path(self.vertical_layer, [point_1, point_2]) + # then left + self.add_via_stack_center(from_layer=self.vertical_layer, + to_layer=self.vertical_layer, + offset=point_2) + point_3 = vector(pin.center().x, pin.center().y + 5.6) + self.add_path(self.vertical_layer, [point_2, point_3]) + # back to normal + self.add_via_stack_center(from_layer=self.vertical_layer, + to_layer=self.vertical_layer, + offset=point_3) + mid = vector(point_3.x, trunk_offset.y) + self.add_path(self.vertical_layer, [point_3, mid]) + self.add_via_center(layers=self.layer_stack, + offset=mid, + directions=self.directions) + else: # do not need to care of wmask + pin_pos = pin.uc() + mid = vector(pin_pos.x, trunk_offset.y) + self.add_path(self.vertical_layer, [pin_pos, mid]) + self.add_via_center(layers=self.layer_stack, + offset=mid, + directions=self.directions) + self.add_via_stack_center(from_layer=pin.layer, + to_layer=self.vertical_layer, + offset=pin.uc()) else: pin_pos = pin.center() mid = vector(pin_pos.x - 0.1, trunk_offset.y) From d4dc7e94a2d056f6c413e60ad13223fe5eb87d3b Mon Sep 17 00:00:00 2001 From: FriedrichWu Date: Wed, 12 Feb 2025 09:51:12 +0100 Subject: [PATCH 26/26] remove gitlab-ci --- .gitlab-ci.yml | 20 -------------------- 1 file changed, 20 deletions(-) delete mode 100644 .gitlab-ci.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index 871201e02..000000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,20 +0,0 @@ -default: - image: ubuntu:latest - before_script: - - apt-get update && apt-get install -y git wget make gcc curl - - git checkout ci_test - - git pull origin ci_test - - chmod +x ./install_conda.sh - - ./install_conda.sh - - echo "export OPENRAM_HOME="/builds/asic_non_nda/openramenhanced/compiler"" >> ~/.bashrc - - echo "export OPENRAM_TECH="/builds/asic_non_nda/openramenhance/technology"" >> ~/.bashrc - - echo "export PYTHONPATH=$OPENRAM_HOME" >> ~/.bashrc - - source ~/.bashrc - - source miniconda/bin/activate - - make sky130-pdk - - make sky130-install - script: - - pwd - - cd ./macros - - make sky130_sram_1rw_tiny - \ No newline at end of file