# See LICENSE for licensing information. # # Copyright (c) 2016-2023 Regents of the University of California and The Board # of Regents for the Oklahoma Agricultural and Mechanical College # (acting for and on behalf of Oklahoma State University) # All rights reserved. # from openram import debug from openram.base import vector from openram.sram_factory import factory from openram.tech import layer_properties as layer_props from openram import OPTS from .bitcell_base_array import bitcell_base_array class local_bitcell_array(bitcell_base_array): """ A local bitcell array is a bitcell array with a wordline driver. This can either be a single aray on its own if there is no hierarchical WL or it can be combined into a larger array with hierarchical WL. """ def __init__(self, rows, cols, rbl=None, left_rbl=None, right_rbl=None, name=""): super().__init__(name=name, rows=rows, cols=cols, column_offset=0) debug.info(2, "Creating {0} {1}x{2} rbl: {3} left_rbl: {4} right_rbl: {5}".format(name, rows, cols, rbl, left_rbl, right_rbl)) self.rows = rows self.cols = cols # This is how many RBLs are in all the arrays self.rbl = rbl # This specifies which RBL to put on the left or right by port number # This could be an empty list if left_rbl is not None: self.left_rbl = left_rbl else: self.left_rbl = [] # This could be an empty list if right_rbl is not None: self.right_rbl = right_rbl else: self.right_rbl=[] debug.check(len(self.all_ports) < 3, "Local bitcell array only supports dual port or less.") self.create_netlist() if not OPTS.netlist_only: self.create_layout() # We don't offset this because we need to align # the replica bitcell in the control logic # self.offset_all_coordinates() def create_netlist(self): """ Create and connect the netlist """ self.add_modules() self.add_pins() self.create_instances() def create_layout(self): self.place() self.add_layout_pins() self.route() self.add_boundary() self.DRC_LVS() def add_modules(self): """ Add the modules used in this design """ # This is just used for names self.cell = factory.create(module_type=OPTS.bitcell) self.bitcell_array = factory.create(module_type="capped_replica_bitcell_array", cols=self.cols, rows=self.rows, rbl=self.rbl, left_rbl=self.left_rbl, right_rbl=self.right_rbl) self.wl_array = factory.create(module_type="wordline_buffer_array", rows=self.rows + 1, cols=self.cols) def add_pins(self): # Outputs from the wordline driver (by port) self.driver_wordline_outputs = [] # Inputs to the bitcell array (by port) self.array_wordline_inputs = [] self.wordline_names = self.bitcell_array.wordline_names self.all_wordline_names = self.bitcell_array.all_wordline_names self.bitline_names = self.bitcell_array.bitline_names self.all_bitline_names = self.bitcell_array.all_bitline_names self.rbl_wordline_names = self.bitcell_array.rbl_wordline_names self.all_rbl_wordline_names = self.bitcell_array.all_rbl_wordline_names self.rbl_bitline_names = self.bitcell_array.rbl_bitline_names self.all_rbl_bitline_names = self.bitcell_array.all_rbl_bitline_names self.all_array_wordline_inputs = [x + "i" for x in self.bitcell_array.get_all_wordline_names()] # Arrays are always: # bit lines (left to right) # word lines (bottom to top) # vdd # gnd for port in self.left_rbl: self.add_pin_list(self.rbl_bitline_names[port], "INOUT") self.add_pin_list(self.all_bitline_names, "INOUT") for port in self.right_rbl: self.add_pin_list(self.rbl_bitline_names[port], "INOUT") for port in range(self.rbl[0]): self.add_pin(self.rbl_wordline_names[port][port], "INPUT") for port in self.all_ports: self.add_pin_list(self.wordline_names[port], "INPUT") for port in range(self.rbl[0], self.rbl[0] + self.rbl[1]): self.add_pin(self.rbl_wordline_names[port][port], "INPUT") self.add_pin("vdd", "POWER") self.add_pin("gnd", "GROUND") def create_instances(self): """ Create the module instances used in this design """ self.wl_insts = [] self.driver_wordline_outputs = [] for port in self.all_ports: self.wl_insts.append(self.add_inst(name="wl_driver{}".format(port), mod=self.wl_array)) temp = [] temp += [self.get_rbl_wordline_names(port)[port]] if port == 0: temp += self.get_wordline_names(port) else: temp += self.get_wordline_names(port)[::-1] self.driver_wordline_outputs.append([x + "i" for x in temp]) temp += self.driver_wordline_outputs[-1] temp += ["vdd", "gnd"] self.connect_inst(temp) self.bitcell_array_inst = self.add_inst(name="array", mod=self.bitcell_array) temp = [] for port in self.left_rbl: temp += self.get_rbl_bitline_names(port) temp += self.all_bitline_names for port in self.right_rbl: temp += self.get_rbl_bitline_names(port) wl_temp = [] for port in range(self.rbl[0]): wl_temp += [self.get_rbl_wordline_names(port)[port]] wl_temp += self.get_wordline_names() for port in range(self.rbl[0], sum(self.rbl)): wl_temp += [self.get_rbl_wordline_names(port)[port]] temp += [x + "i" for x in wl_temp] temp += ["vdd", "gnd"] self.connect_inst(temp) def place(self): """ Place the bitcelll array to the right of the wl driver. """ # FIXME: Replace this with a tech specific parameter driver_to_array_spacing = 3 * self.m3_pitch wl_offset = vector(0, self.bitcell_array.get_replica_bottom()) self.wl_insts[0].place(wl_offset) bitcell_array_offset = vector(self.wl_insts[0].rx() + driver_to_array_spacing, 0) self.bitcell_array_inst.place(bitcell_array_offset) if len(self.all_ports) > 1: wl_offset = vector(self.bitcell_array_inst.rx() + self.wl_array.width + driver_to_array_spacing, self.bitcell_array.get_replica_bottom() + self.wl_array.height + self.cell.height) self.wl_insts[1].place(wl_offset, mirror="XY") self.height = self.bitcell_array.height self.width = max(self.bitcell_array_inst.rx(), max([x.rx() for x in self.wl_insts])) def add_layout_pins(self): for x in self.get_inouts(): self.copy_layout_pin(self.bitcell_array_inst, x) supply_insts = [*self.wl_insts, self.bitcell_array_inst] for pin_name in ["vdd", "gnd"]: for inst in supply_insts: pin_list = inst.get_pins(pin_name) for pin in pin_list: self.copy_power_pin(pin) def route(self): global_wl_layer = layer_props.global_bitcell_array.wordline_layer global_wl_pitch = getattr(self, "{}_pitch".format(global_wl_layer)) global_wl_pitch_factor = layer_props.global_bitcell_array.wordline_pitch_factor local_wl_layer = layer_props.local_bitcell_array.wordline_layer # Route the global wordlines for port in self.all_ports: if port == 0: wordline_names = [self.get_rbl_wordline_names(port)[port]] + self.get_wordline_names(port) else: wordline_names = [self.get_rbl_wordline_names(port)[port]] + self.get_wordline_names(port)[::-1] wordline_pins = self.wl_array.get_inputs() for (wl_name, in_pin_name) in zip(wordline_names, wordline_pins): # wl_pin = self.bitcell_array_inst.get_pin(wl_name) in_pin = self.wl_insts[port].get_pin(in_pin_name) y_offset = in_pin.cy() if port == 0: y_offset -= global_wl_pitch_factor * global_wl_pitch else: y_offset += global_wl_pitch_factor * global_wl_pitch mid = vector(in_pin.cx(), y_offset) self.add_layout_pin_rect_center(text=wl_name, layer=global_wl_layer, offset=mid) self.add_path(local_wl_layer, [in_pin.center(), mid]) # A short jog to the global line self.add_via_stack_center(from_layer=in_pin.layer, to_layer=local_wl_layer, offset=in_pin.center(), min_area=True) self.add_path(local_wl_layer, [in_pin.center(), mid]) self.add_via_stack_center(from_layer=local_wl_layer, to_layer=global_wl_layer, offset=mid, min_area=True) # Add the global WL pin self.add_layout_pin_rect_center(text=wl_name, layer=global_wl_layer, offset=mid) # Route the buffers for port in self.all_ports: driver_outputs = self.driver_wordline_outputs[port] for (driver_name, net_name) in zip(self.wl_insts[port].mod.get_outputs(), driver_outputs): array_name = net_name[:-1] out_pin = self.wl_insts[port].get_pin(driver_name) in_pin = self.bitcell_array_inst.get_pin(array_name) if port == 0: out_loc = out_pin.rc() mid_loc = vector(self.wl_insts[port].rx() + 1.5 * self.m3_pitch, out_loc.y) in_loc = in_pin.lc() else: out_loc = out_pin.lc() mid_loc = vector(self.wl_insts[port].lx() - 1.5 * self.m3_pitch, out_loc.y) in_loc = in_pin.rc() self.add_path(out_pin.layer, [out_loc, mid_loc]) self.add_via_stack_center(from_layer=out_pin.layer, to_layer=in_pin.layer, offset=mid_loc) self.add_path(in_pin.layer, [mid_loc, in_loc]) def get_main_array_top(self): return self.bitcell_array_inst.by() + self.bitcell_array.get_main_array_top() def get_main_array_bottom(self): return self.bitcell_array_inst.by() + self.bitcell_array.get_main_array_bottom() def get_main_array_left(self): return self.bitcell_array_inst.lx() + self.bitcell_array.get_main_array_left() def get_main_array_right(self): return self.bitcell_array_inst.lx() + self.bitcell_array.get_main_array_right() def get_column_offsets(self): """ Return an array of the x offsets of all the regular bits """ # must add the offset of the instance offsets = [self.bitcell_array_inst.lx() + x for x in self.bitcell_array.get_column_offsets()] return offsets def graph_exclude_bits(self, targ_row=None, targ_col=None): """ Excludes bits in column from being added to graph except target """ self.bitcell_array.graph_exclude_bits(targ_row, targ_col) def graph_exclude_replica_col_bits(self): """ Exclude all but replica in the local array. """ self.bitcell_array.graph_exclude_replica_col_bits() def get_cell_name(self, inst_name, row, col): """Gets the spice name of the target bitcell.""" return self.bitcell_array.get_cell_name(inst_name + "{}x".format(OPTS.hier_seperator) + self.bitcell_array_inst.name, row, col) def clear_exclude_bits(self): """ Clears the bit exclusions """ self.bitcell_array.clear_exclude_bits()