# See LICENSE for licensing information. # # Copyright (c) 2016-2019 Regents of the University of California and The Board # of Regents for the Oklahoma Agricultural and Mechanical College # (acting for and on behalf of Oklahoma State University) # All rights reserved. # import datetime import debug from math import log, ceil from importlib import reload from vector import vector from globals import OPTS, print_time from design import design from verilog import verilog from lef import lef from sram_factory import factory class sram_base(design, verilog, lef): """ Dynamically generated SRAM by connecting banks to control logic. The number of banks should be 1 , 2 or 4 """ 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.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 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.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") 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") self.add_pin("vdd", "POWER") self.add_pin("gnd", "GROUND") def add_global_pex_labels(self): """ Add pex labels at the sram level for spice analysis """ # add pex labels for bitcells for bank_num in range(len(self.bank_insts)): bank = self.bank_insts[bank_num] pex_data = bank.reverse_transformation_bitcell(self.bitcell.name) bank_offset = pex_data[0] # offset bank relative to sram Q_offset = pex_data[1] # offset of storage relative to bank Q_bar_offset = pex_data[2] # offset of storage relative to bank bl_offsets = pex_data[3] br_offsets = pex_data[4] bl_meta = pex_data[5] br_meta = pex_data[6] bl = [] br = [] storage_layer_name = "m1" bitline_layer_name = self.bitcell.get_pin("bl").layer for cell in range(len(bank_offset)): Q = [bank_offset[cell][0] + Q_offset[cell][0], bank_offset[cell][1] + Q_offset[cell][1]] Q_bar = [bank_offset[cell][0] + Q_bar_offset[cell][0], bank_offset[cell][1] + Q_bar_offset[cell][1]] OPTS.words_per_row = self.words_per_row row = int(cell % (OPTS.num_words / self.words_per_row)) col = int(cell / (OPTS.num_words)) self.add_layout_pin_rect_center("bitcell_Q_b{}_r{}_c{}".format(bank_num, row, col), storage_layer_name, Q) self.add_layout_pin_rect_center("bitcell_Q_bar_b{}_r{}_c{}".format(bank_num, row, col), storage_layer_name, Q_bar) for cell in range(len(bl_offsets)): col = bl_meta[cell][0][2] for bitline in range(len(bl_offsets[cell])): bitline_location = [float(bank_offset[cell][0]) + bl_offsets[cell][bitline][0], float(bank_offset[cell][1]) + bl_offsets[cell][bitline][1]] bl.append([bitline_location, bl_meta[cell][bitline][3], col]) for cell in range(len(br_offsets)): col = br_meta[cell][0][2] for bitline in range(len(br_offsets[cell])): bitline_location = [float(bank_offset[cell][0]) + br_offsets[cell][bitline][0], float(bank_offset[cell][1]) + br_offsets[cell][bitline][1]] br.append([bitline_location, br_meta[cell][bitline][3], col]) for i in range(len(bl)): self.add_layout_pin_rect_center("bl{0}_{1}".format(bl[i][1], bl[i][2]), bitline_layer_name, bl[i][0]) for i in range(len(br)): self.add_layout_pin_rect_center("br{0}_{1}".format(br[i][1], br[i][2]), bitline_layer_name, br[i][0]) # add pex labels for control logic for i in range(len(self.control_logic_insts)): instance = self.control_logic_insts[i] control_logic_offset = instance.offset for output in instance.mod.output_list: pin = instance.mod.get_pin(output) pin.transform([0, 0], instance.mirror, instance.rotate) offset = [control_logic_offset[0] + pin.center()[0], control_logic_offset[1] + pin.center()[1]] self.add_layout_pin_rect_center("{0}{1}".format(pin.name, i), storage_layer_name, offset) def create_netlist(self): """ Netlist creation """ start_time = datetime.datetime.now() # Must create the control logic before pins to get the pins self.add_modules() self.add_pins() self.create_modules() # This is for the lib file if we don't create layout self.width=0 self.height=0 if not OPTS.is_unit_test: print_time("Submodules", datetime.datetime.now(), start_time) def create_layout(self): """ Layout creation """ start_time = datetime.datetime.now() self.place_instances() if not OPTS.is_unit_test: print_time("Placement", datetime.datetime.now(), start_time) start_time = datetime.datetime.now() self.route_layout() self.route_supplies() 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: self.add_global_pex_labels() self.add_boundary(ll=vector(0, 0), ur=vector(self.width, self.height)) start_time = datetime.datetime.now() # We only enable final verification if we have routed the design self.DRC_LVS(final_verification=OPTS.route_supplies, force_check=OPTS.check_lvsdrc) if not OPTS.is_unit_test: print_time("Verification", datetime.datetime.now(), start_time) def create_modules(self): debug.error("Must override pure virtual function.", -1) def route_supplies(self): """ 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 inst in self.insts: self.copy_power_pins(inst, "vdd") self.copy_power_pins(inst, "gnd") if not OPTS.route_supplies: # Do not route the power supply (leave as must-connect pins) return grid_stack = set() try: from tech import power_grid grid_stack = power_grid except ImportError: # if no power_grid is specified by tech we use sensible defaults import tech if "m4" in tech.layer: # Route a M3/M4 grid grid_stack = self.m3_stack elif "m3" in tech.layer: grid_stack =("m3",) from supply_grid_router import supply_grid_router as router rtr=router(grid_stack, self) rtr.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.addr_size + self.control_size + 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.addr_size)] self.vert_control_bus_positions.update(self.create_vertical_pin_bus(layer="m2", pitch=self.m2_pitch, offset=self.addr_bus_offset, names=self.addr_bus_names, length=self.addr_bus_height)) self.bank_sel_bus_names = ["bank_sel{0}_{1}".format(port, i) for i in range(self.num_banks)] self.vert_control_bus_positions.update(self.create_vertical_pin_bus(layer="m2", pitch=self.m2_pitch, offset=self.bank_sel_bus_offset, names=self.bank_sel_bus_names, length=self.vertical_bus_height)) # Horizontal data bus self.data_bus_names = ["DATA{0}[{1}]".format(port, i) for i in range(self.word_size)] self.data_bus_positions = self.create_horizontal_pin_bus(layer="m3", pitch=self.m3_pitch, offset=self.data_bus_offset, names=self.data_bus_names, length=self.data_bus_width) # Horizontal control logic bus # vdd/gnd in bus go along whole SRAM # FIXME: Fatten these wires? self.horz_control_bus_positions = self.create_horizontal_bus(layer="m1", pitch=self.m1_pitch, offset=self.supply_bus_offset, names=["vdd"], length=self.supply_bus_width) # The gnd rail must not be the entire width since we protrude the right-most vdd rail up for # the decoder in 4-bank SRAMs self.horz_control_bus_positions.update(self.create_horizontal_bus(layer="m1", pitch=self.m1_pitch, offset=self.supply_bus_offset + vector(0, self.m1_pitch), names=["gnd"], length=self.supply_bus_width)) self.horz_control_bus_positions.update(self.create_horizontal_bus(layer="m1", pitch=self.m1_pitch, offset=self.control_bus_offset, names=self.control_bus_names[port], length=self.control_bus_width)) def add_multi_bank_modules(self): """ Create the multibank address flops and bank decoder """ from dff_buf_array import dff_buf_array self.msb_address = dff_buf_array(name="msb_address", rows=1, columns=self.num_banks / 2) self.add_mod(self.msb_address) if self.num_banks>2: self.msb_decoder = self.bank.decoder.pre2_4 self.add_mod(self.msb_decoder) def add_modules(self): self.bitcell = factory.create(module_type=OPTS.bitcell) self.dff = factory.create(module_type="dff") # 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) self.add_mod(self.row_addr_dff) 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) self.add_mod(self.col_addr_dff) 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) self.add_mod(self.data_dff) if self.write_size: self.wmask_dff = factory.create("dff_array", module_name="wmask_dff", rows=1, columns=self.num_wmasks) self.add_mod(self.wmask_dff) 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.add_mod(self.spare_wen_dff) # Create the bank module (up to four are instantiated) self.bank = factory.create("bank", sram_config=self.sram_config, module_name="bank") self.add_mod(self.bank) # Create bank decoder if(self.num_banks > 1): self.add_multi_bank_modules() self.bank_count = 0 c = reload(__import__(OPTS.control_logic)) self.mod_control_logic = getattr(c, OPTS.control_logic) # Create the control logic module for each port type if len(self.readwrite_ports)>0: self.control_logic_rw = self.mod_control_logic(num_rows=self.num_rows, words_per_row=self.words_per_row, word_size=self.word_size, spare_columns=self.num_spare_cols, sram=self, port_type="rw") self.add_mod(self.control_logic_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") self.add_mod(self.control_logic_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") self.add_mod(self.control_logic_r) def create_bank(self, bank_num): """ Create a bank """ self.bank_insts.append(self.add_inst(name="bank{0}".format(bank_num), mod=self.bank)) temp = [] for port in self.read_ports: for bit in range(self.word_size + self.num_spare_cols): temp.append("dout{0}[{1}]".format(port, bit)) for port in self.all_ports: temp.append("rbl_bl{0}".format(port)) for port in self.write_ports: for bit in range(self.word_size + self.num_spare_cols): temp.append("bank_din{0}[{1}]".format(port, bit)) for port in self.all_ports: for bit in range(self.bank_addr_size): temp.append("a{0}[{1}]".format(port, bit)) if(self.num_banks > 1): for port in self.all_ports: temp.append("bank_sel{0}[{1}]".format(port, bank_num)) for port in self.read_ports: temp.append("s_en{0}".format(port)) for port in self.all_ports: temp.append("p_en_bar{0}".format(port)) for port in self.write_ports: temp.append("w_en{0}".format(port)) for bit in range(self.num_wmasks): temp.append("bank_wmask{}[{}]".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(["vdd", "gnd"]) 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), "vdd", "gnd"]) 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), "vdd", "gnd"]) 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), "vdd", "gnd"]) 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), "vdd", "gnd"]) 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), "vdd", "gnd"]) return insts def create_control_logic(self): """ Add control logic instances """ insts = [] for port in self.all_ports: if port in self.readwrite_ports: mod = self.control_logic_rw elif port in self.write_ports: mod = self.control_logic_w else: mod = self.control_logic_r insts.append(self.add_inst(name="control{}".format(port), mod=mod)) # Inputs temp = ["csb{}".format(port)] if port in self.readwrite_ports: temp.append("web{}".format(port)) temp.append("clk{}".format(port)) temp.append("rbl_bl{}".format(port)) # Outputs if port in self.read_ports: temp.append("s_en{}".format(port)) if port in self.write_ports: temp.append("w_en{}".format(port)) temp.append("p_en_bar{}".format(port)) temp.extend(["wl_en{}".format(port), "clk_buf{}".format(port), "vdd", "gnd"]) self.connect_inst(temp) return insts def sp_write(self, sp_name, lvs_netlist=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("**************************************************\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_netlist=lvs_netlist) del usedMODS sp.close() def lvs_write(self, sp_name): self.sp_write(sp_name, lvs_netlist=True) 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()