diff --git a/.github/workflows/regress.yml b/.github/workflows/regress.yml index 1da43009..b98153ab 100644 --- a/.github/workflows/regress.yml +++ b/.github/workflows/regress.yml @@ -3,14 +3,11 @@ on: push: branches-ignore: - stable - pull_request: - branches-ignore: - - stable jobs: # All tests should be run from this job regression_test: # This job runs on pull requests or any push that doesn't have a new version - if: ${{ github.event_name == 'pull_request' || startsWith(github.event.head_commit.message, 'Bump version:') == false }} + if: ${{ startsWith(github.event.head_commit.message, 'Bump version:') == false }} runs-on: self-hosted steps: - name: Checkout code @@ -52,14 +49,14 @@ jobs: path: ${{ github.workspace }}/compiler/tests/results/* # This job triggers sync.yml workflow sync_trigger: - if: ${{ always() && github.event_name == 'push' && github.ref_name == 'dev' && github.repository == 'VLSIDA/PrivateRAM' && needs.regression_test.result == 'failure' }} + if: ${{ always() && github.ref_name == 'dev' && github.repository == 'VLSIDA/PrivateRAM' && needs.regression_test.result == 'failure' }} needs: regression_test uses: ./.github/workflows/sync.yml secrets: WORKFLOW_ACCESS_TOKEN: ${{ secrets.WORKFLOW_ACCESS_TOKEN }} # This job triggers version.yml workflow version_trigger: - if: ${{ github.event_name == 'push' && github.ref_name == 'dev' }} + if: ${{ github.ref_name == 'dev' }} needs: regression_test uses: ./.github/workflows/version.yml secrets: diff --git a/common.py b/common.py new file mode 100644 index 00000000..e6ec2d73 --- /dev/null +++ b/common.py @@ -0,0 +1,31 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz +# All rights reserved. +# +""" +Common functions for top-level scripts +""" + +import sys +import os + + +def make_openram_package(): + """ Make sure that OpenRAM can be used as a Python package. """ + + import importlib.util + + # Find the package loader from python/site-packages + openram_loader = importlib.util.find_spec("openram") + + # If openram library isn't found as a python package, import it from + # the $OPENRAM_HOME path. + if openram_loader is None: + OPENRAM_HOME = os.getenv("OPENRAM_HOME") + # Import using spec since the directory can be named something other + # than "openram". + spec = importlib.util.spec_from_file_location("openram", "{}/../__init__.py".format(OPENRAM_HOME)) + module = importlib.util.module_from_spec(spec) + sys.modules["openram"] = module + spec.loader.exec_module(module) diff --git a/compiler/base/hierarchy_layout.py b/compiler/base/hierarchy_layout.py index 9100c674..9a11b68a 100644 --- a/compiler/base/hierarchy_layout.py +++ b/compiler/base/hierarchy_layout.py @@ -631,10 +631,11 @@ class layout(): """ return self.pins - def copy_layout_pin(self, instance, pin_name, new_name=""): + def copy_layout_pin(self, instance, pin_name, new_name="", relative_offset=vector(0, 0)): """ Create a copied version of the layout pin at the current level. You can optionally rename the pin to a new name. + You can optionally add an offset vector by which to move the pin. """ pins = instance.get_pins(pin_name) @@ -646,7 +647,7 @@ class layout(): new_name = pin_name self.add_layout_pin(new_name, pin.layer, - pin.ll(), + pin.ll() + relative_offset, pin.width(), pin.height()) diff --git a/compiler/modules/bank.py b/compiler/modules/bank.py index 80b03bb2..8cf92961 100644 --- a/compiler/modules/bank.py +++ b/compiler/modules/bank.py @@ -374,9 +374,12 @@ class bank(design): cols=cols, rows=self.num_rows) else: - self.bitcell_array = factory.create(module_type="replica_bitcell_array", + self.bitcell_array = factory.create(module_type="capped_replica_bitcell_array", cols=self.num_cols + self.num_spare_cols, - rows=self.num_rows) + rows=self.num_rows, + rbl=[1, 1 if len(self.all_ports)>1 else 0], + left_rbl=[0], + right_rbl=[1] if len(self.all_ports)>1 else []) self.port_address = [] for port in self.all_ports: diff --git a/compiler/modules/capped_replica_bitcell_array.py b/compiler/modules/capped_replica_bitcell_array.py new file mode 100644 index 00000000..65193bab --- /dev/null +++ b/compiler/modules/capped_replica_bitcell_array.py @@ -0,0 +1,561 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz +# All rights reserved. +# + +from openram import debug +from openram.base import vector +from openram.base import contact +from openram.sram_factory import factory +from openram.tech import drc, spice +from openram.tech import cell_properties as props +from openram import OPTS +from .bitcell_base_array import bitcell_base_array + + +class capped_replica_bitcell_array(bitcell_base_array): + """ + Creates a replica bitcell array then adds the row and column caps to all + sides of a bitcell array. + """ + def __init__(self, rows, cols, rbl=None, left_rbl=None, right_rbl=None, name=""): + super().__init__(name, rows, cols, column_offset=0) + debug.info(1, "Creating {0} {1} x {2} rbls: {3} left_rbl: {4} right_rbl: {5}".format(self.name, + rows, + cols, + rbl, + left_rbl, + right_rbl)) + self.add_comment("rows: {0} cols: {1}".format(rows, cols)) + self.add_comment("rbl: {0} left_rbl: {1} right_rbl: {2}".format(rbl, left_rbl, right_rbl)) + + self.column_size = cols + self.row_size = rows + # 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=[] + self.rbls = self.left_rbl + self.right_rbl + + # Two dummy rows plus replica even if we don't add the column + self.extra_rows = sum(self.rbl) + # If we aren't using row/col caps, then we need to use the bitcell + if not self.cell.end_caps: + self.extra_rows += 2 + + self.create_netlist() + if not OPTS.netlist_only: + self.create_layout() + + def create_netlist(self): + """ Create and connect the netlist """ + self.add_modules() + self.add_pins() + self.create_instances() + + def add_modules(self): + """ Array and dummy/replica columns + + d or D = dummy cell (caps to distinguish grouping) + r or R = replica cell (caps to distinguish grouping) + b or B = bitcell + replica columns 1 + v v + bdDDDDDDDDDDDDDDdb <- Dummy row + bdDDDDDDDDDDDDDDrb <- Dummy row + br--------------rb + br| Array |rb + br| row x col |rb + br--------------rb + brDDDDDDDDDDDDDDdb <- Dummy row + bdDDDDDDDDDDDDDDdb <- Dummy row + + ^^^^^^^^^^^^^^^ + dummy rows cols x 1 + + ^ dummy columns ^ + 1 x (rows + 4) + """ + + self.replica_bitcell_array = factory.create(module_type="replica_bitcell_array", + cols=self.column_size, + rows=self.row_size, + rbl=self.rbl, + left_rbl=self.left_rbl, + right_rbl=self.right_rbl) + + # Dummy Row or Col Cap, depending on bitcell array properties + col_cap_module_type = ("col_cap_array" if self.cell.end_caps else "dummy_array") + + # TODO: remove redundancy from arguments in pairs below (top/bottom, left/right) + # for example, cols takes the same value for top/bottom + self.col_cap_top = factory.create(module_type=col_cap_module_type, + cols=self.column_size + len(self.rbls), + rows=1, + # dummy column + left replica column(s) + column_offset=1, + mirror=0, + location="top") + + self.col_cap_bottom = factory.create(module_type=col_cap_module_type, + cols=self.column_size + len(self.rbls), + rows=1, + # dummy column + left replica column(s) + column_offset=1, + mirror=0, + location="bottom") + + # Dummy Col or Row Cap, depending on bitcell array properties + row_cap_module_type = ("row_cap_array" if self.cell.end_caps else "dummy_array") + + self.row_cap_left = factory.create(module_type=row_cap_module_type, + cols=1, + column_offset=0, + rows=self.row_size + self.extra_rows, + mirror=(self.rbl[0] + 1) % 2) + + self.row_cap_right = factory.create(module_type=row_cap_module_type, + cols=1, + # dummy column + # + left replica column(s) + # + bitcell columns + # + right replica column(s) + column_offset=1 + len(self.left_rbl) + self.column_size + self.rbl[0], + rows=self.row_size + self.extra_rows, + mirror=(self.rbl[0] + 1) %2) + + def add_pins(self): + + # Arrays are always: + # bitlines (column first then port order) + # word lines (row first then port order) + # dummy wordlines + # replica wordlines + # regular wordlines (bottom to top) + # # dummy bitlines + # replica bitlines (port order) + # regular bitlines (left to right port order) + # + # vdd + # gnd + + self.add_bitline_pins() + self.add_wordline_pins() + self.add_pin("vdd", "POWER") + self.add_pin("gnd", "GROUND") + + def add_bitline_pins(self): + # these four are only included for compatibility with other modules + self.bitline_names = self.replica_bitcell_array.bitline_names + self.all_bitline_names = self.replica_bitcell_array.all_bitline_names + self.rbl_bitline_names = self.replica_bitcell_array.rbl_bitline_names + self.all_rbl_bitline_names = self.replica_bitcell_array.all_rbl_bitline_names + + # this one is actually used (obviously) + self.bitline_pin_list = self.replica_bitcell_array.bitline_pin_list + self.add_pin_list(self.bitline_pin_list, "INOUT") + + def add_wordline_pins(self): + # some of these are just included for compatibility with modules instantiating this module + self.rbl_wordline_names = self.replica_bitcell_array.rbl_wordline_names + self.all_rbl_wordline_names = self.replica_bitcell_array.all_rbl_wordline_names + self.wordline_names = self.replica_bitcell_array.wordline_names + self.all_wordline_names = self.replica_bitcell_array.all_wordline_names + + self.used_wordline_names = self.replica_bitcell_array.used_wordline_names + self.unused_wordline_names = self.replica_bitcell_array.unused_wordline_names + self.replica_array_wordline_names_with_grounded_wls = ["gnd" if x in self.unused_wordline_names else x for x in self.replica_bitcell_array.wordline_pin_list] + + self.wordline_pin_list = [] + self.wordline_pin_list.extend(["gnd"] * len(self.col_cap_top.get_wordline_names())) + self.wordline_pin_list.extend(self.replica_array_wordline_names_with_grounded_wls) + self.wordline_pin_list.extend(["gnd"] * len(self.col_cap_bottom.get_wordline_names())) + + self.add_pin_list(self.used_wordline_names, "INPUT") + + def create_instances(self): + """ Create the module instances used in this design """ + self.supplies = ["vdd", "gnd"] + + # Main array + self.replica_bitcell_array_inst=self.add_inst(name="replica_bitcell_array", + mod=self.replica_bitcell_array) + self.connect_inst(self.bitline_pin_list + self.replica_array_wordline_names_with_grounded_wls + self.supplies) + + # Top/bottom dummy rows or col caps + self.dummy_row_insts = [] + self.dummy_row_insts.append(self.add_inst(name="dummy_row_bot", + mod=self.col_cap_bottom)) + self.connect_inst(self.bitline_pin_list + ["gnd"] * len(self.col_cap_bottom.get_wordline_names()) + self.supplies) + self.dummy_row_insts.append(self.add_inst(name="dummy_row_top", + mod=self.col_cap_top)) + self.connect_inst(self.bitline_pin_list + ["gnd"] * len(self.col_cap_top.get_wordline_names()) + self.supplies) + + # Left/right Dummy columns + self.dummy_col_insts = [] + self.dummy_col_insts.append(self.add_inst(name="dummy_col_left", + mod=self.row_cap_left)) + self.connect_inst(["dummy_left_" + bl for bl in self.row_cap_left.all_bitline_names] + self.wordline_pin_list + self.supplies) + self.dummy_col_insts.append(self.add_inst(name="dummy_col_right", + mod=self.row_cap_right)) + self.connect_inst(["dummy_right_" + bl for bl in self.row_cap_right.all_bitline_names] + self.wordline_pin_list + self.supplies) + + # bitcell array needed for some offset calculations + self.bitcell_array_inst = self.replica_bitcell_array.bitcell_array_inst + + def create_layout(self): + + # This creates space for the unused wordline connections as well as the + # row-based or column based power and ground lines. + self.vertical_pitch = 1.1 * getattr(self, "{}_pitch".format(self.supply_stack[0])) + self.horizontal_pitch = 1.1 * getattr(self, "{}_pitch".format(self.supply_stack[2])) + self.unused_offset = vector(0.25, 0.25) + + # This is a bitcell x bitcell offset to scale + self.bitcell_offset = vector(self.cell.width, self.cell.height) + self.col_end_offset = vector(self.cell.width, self.cell.height) + self.row_end_offset = vector(self.cell.width, self.cell.height) + + # Everything is computed with the replica array + self.replica_bitcell_array_inst.place(offset=self.unused_offset) + + self.add_end_caps() + + # shift everything up and right to account for cap cells + self.translate_all(self.bitcell_offset.scale(-1, -1)) + + self.width = self.dummy_col_insts[1].rx() + self.unused_offset.x + self.height = self.dummy_row_insts[1].uy() + + self.add_layout_pins() + + self.route_supplies() + + self.route_unused_wordlines() + + lower_left = self.find_lowest_coords() + upper_right = self.find_highest_coords() + self.width = upper_right.x - lower_left.x + self.height = upper_right.y - lower_left.y + self.translate_all(lower_left) + + self.add_boundary() + + self.DRC_LVS() + + def get_main_array_top(self): + return self.replica_bitcell_array_inst.by() + self.replica_bitcell_array.get_main_array_top() + + def get_main_array_bottom(self): + return self.replica_bitcell_array_inst.by() + self.replica_bitcell_array.get_main_array_bottom() + + def get_main_array_left(self): + return self.replica_bitcell_array_inst.lx() + self.replica_bitcell_array.get_main_array_left() + + def get_main_array_right(self): + return self.replica_bitcell_array_inst.lx() + self.replica_bitcell_array.get_main_array_right() + + # FIXME: these names need to be changed to reflect what they're actually returning + def get_replica_top(self): + return self.dummy_row_insts[1].by() + + def get_replica_bottom(self): + return self.dummy_row_insts[0].uy() + + def get_replica_left(self): + return self.dummy_col_insts[0].lx() + + def get_replica_right(self): + return self.dummy_col_insts[1].rx() + + + 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.replica_bitcell_array_inst.lx() + x for x in self.replica_bitcell_array.get_column_offsets()] + return offsets + + def add_end_caps(self): + """ Add dummy cells or end caps around the array """ + + # Far top dummy row (first row above array is NOT flipped if even number of rows) + flip_dummy = (self.row_size + self.rbl[1]) % 2 + dummy_row_offset = self.bitcell_offset.scale(0, flip_dummy) + self.replica_bitcell_array_inst.ul() + self.dummy_row_insts[1].place(offset=dummy_row_offset, + mirror="MX" if flip_dummy else "R0") + + # Far bottom dummy row (first row below array IS flipped) + flip_dummy = (self.rbl[0] + 1) % 2 + dummy_row_offset = self.bitcell_offset.scale(0, flip_dummy - 1) + self.unused_offset + self.dummy_row_insts[0].place(offset=dummy_row_offset, + mirror="MX" if flip_dummy else "R0") + # Far left dummy col + # Shifted down by the number of left RBLs even if we aren't adding replica column to this bitcell array + dummy_col_offset = self.bitcell_offset.scale(-1, -1) + self.unused_offset + self.dummy_col_insts[0].place(offset=dummy_col_offset) + + # Far right dummy col + # Shifted down by the number of left RBLs even if we aren't adding replica column to this bitcell array + dummy_col_offset = self.bitcell_offset.scale(0, -1) + self.replica_bitcell_array_inst.lr() + self.dummy_col_insts[1].place(offset=dummy_col_offset) + + def add_layout_pins(self): + for pin_name in self.used_wordline_names + self.bitline_pin_list: + pin = self.replica_bitcell_array_inst.get_pin(pin_name) + + if "wl" in pin_name: + # wordlines + pin_offset = pin.ll().scale(0, 1) + pin_width = self.width + pin_height = pin.height() + else: + # bitlines + pin_offset = pin.ll().scale(1, 0) + pin_width = pin.width() + pin_height = self.height + + self.add_layout_pin(text=pin_name, + layer=pin.layer, + offset=pin_offset, + width=pin_width, + height=pin_height) + + def route_supplies(self): + + if OPTS.bitcell == "pbitcell": + bitcell = factory.create(module_type="pbitcell") + else: + bitcell = getattr(props, "bitcell_{}port".format(OPTS.num_ports)) + + vdd_dir = bitcell.vdd_dir + gnd_dir = bitcell.gnd_dir + + # vdd/gnd are only connected in the perimeter cells + supply_insts = self.dummy_col_insts + self.dummy_row_insts + + # For the wordlines + top_bot_mult = 1 + left_right_mult = 1 + + # There are always vertical pins for the WLs on the left/right if we have unused wordlines + self.left_gnd_locs = self.route_side_pin("gnd", "left", left_right_mult) + self.right_gnd_locs = self.route_side_pin("gnd", "right", left_right_mult) + # This needs to be big enough so that they aren't in the same supply routing grid + left_right_mult = 4 + + if gnd_dir == "V": + self.top_gnd_locs = self.route_side_pin("gnd", "top", top_bot_mult) + self.bot_gnd_locs = self.route_side_pin("gnd", "bot", top_bot_mult) + # This needs to be big enough so that they aren't in the same supply routing grid + top_bot_mult = 4 + + if vdd_dir == "V": + self.top_vdd_locs = self.route_side_pin("vdd", "top", top_bot_mult) + self.bot_vdd_locs = self.route_side_pin("vdd", "bot", top_bot_mult) + elif vdd_dir == "H": + self.left_vdd_locs = self.route_side_pin("vdd", "left", left_right_mult) + self.right_vdd_locs = self.route_side_pin("vdd", "right", left_right_mult) + else: + debug.error("Invalid vdd direction {}".format(vdd_dir), -1) + + for inst in supply_insts: + for pin in inst.get_pins("vdd"): + if vdd_dir == "V": + self.connect_side_pin(pin, "top", self.top_vdd_locs[0].y) + self.connect_side_pin(pin, "bot", self.bot_vdd_locs[0].y) + elif vdd_dir == "H": + self.connect_side_pin(pin, "left", self.left_vdd_locs[0].x) + self.connect_side_pin(pin, "right", self.right_vdd_locs[0].x) + + for inst in supply_insts: + for pin in inst.get_pins("gnd"): + if gnd_dir == "V": + self.connect_side_pin(pin, "top", self.top_gnd_locs[0].y) + self.connect_side_pin(pin, "bot", self.bot_gnd_locs[0].y) + elif gnd_dir == "H": + self.connect_side_pin(pin, "left", self.left_gnd_locs[0].x) + self.connect_side_pin(pin, "right", self.right_gnd_locs[0].x) + + def route_unused_wordlines(self): + """ + Connect the unused RBL and dummy wordlines to gnd + """ + # This grounds all the dummy row word lines + for inst in self.dummy_row_insts: + for wl_name in self.col_cap_top.get_wordline_names(): + pin = inst.get_pin(wl_name) + self.connect_side_pin(pin, "left", self.left_gnd_locs[0].x) + self.connect_side_pin(pin, "right", self.right_gnd_locs[0].x) + + # Ground the unused replica wordlines + for wl_name in self.unused_wordline_names: + pin = self.replica_bitcell_array_inst.get_pin(wl_name) + self.connect_side_pin(pin, "left", self.left_gnd_locs[0].x) + self.connect_side_pin(pin, "right", self.right_gnd_locs[0].x) + + def route_side_pin(self, name, side, offset_multiple=1): + """ + Routes a vertical or horizontal pin on the side of the bbox. + The multiple specifies how many track offsets to be away from the side assuming + (0,0) (self.width, self.height) + """ + if side in ["left", "right"]: + return self.route_vertical_side_pin(name, side, offset_multiple) + elif side in ["top", "bottom", "bot"]: + return self.route_horizontal_side_pin(name, side, offset_multiple) + else: + debug.error("Invalid side {}".format(side), -1) + + def route_vertical_side_pin(self, name, side, offset_multiple=1): + """ + Routes a vertical pin on the side of the bbox. + """ + if side == "left": + bot_loc = vector(-offset_multiple * self.vertical_pitch, 0) + top_loc = vector(-offset_multiple * self.vertical_pitch, self.height) + elif side == "right": + bot_loc = vector(self.width + offset_multiple * self.vertical_pitch, 0) + top_loc = vector(self.width + offset_multiple * self.vertical_pitch, self.height) + + layer = self.supply_stack[2] + top_via = contact(layer_stack=self.supply_stack, + directions=("H", "H")) + + self.add_layout_pin_segment_center(text=name, + layer=layer, + start=bot_loc, + end=top_loc, + width=top_via.second_layer_width) + + return (bot_loc, top_loc) + + def route_horizontal_side_pin(self, name, side, offset_multiple=1): + """ + Routes a horizontal pin on the side of the bbox. + """ + if side in ["bottom", "bot"]: + left_loc = vector(0, -offset_multiple * self.horizontal_pitch) + right_loc = vector(self.width, -offset_multiple * self.horizontal_pitch) + elif side == "top": + left_loc = vector(0, self.height + offset_multiple * self.horizontal_pitch) + right_loc = vector(self.width, self.height + offset_multiple * self.horizontal_pitch) + + layer = self.supply_stack[0] + side_via = contact(layer_stack=self.supply_stack, + directions=("V", "V")) + + self.add_layout_pin_segment_center(text=name, + layer=layer, + start=left_loc, + end=right_loc, + width=side_via.first_layer_height) + + return (left_loc, right_loc) + + def connect_side_pin(self, pin, side, offset): + """ + Used to connect a pin to the a horizontal or vertical strap + offset gives the location of the strap + """ + if side in ["left", "right"]: + self.connect_vertical_side_pin(pin, offset) + elif side in ["top", "bottom", "bot"]: + self.connect_horizontal_side_pin(pin, offset) + else: + debug.error("Invalid side {}".format(side), -1) + + def connect_horizontal_side_pin(self, pin, yoffset): + """ + Used to connect a pin to the top/bottom horizontal straps + """ + cell_loc = pin.center() + pin_loc = vector(cell_loc.x, yoffset) + + # Place the pins a track outside of the array + self.add_via_stack_center(offset=pin_loc, + from_layer=pin.layer, + to_layer=self.supply_stack[0], + directions=("V", "V")) + + # Add a path to connect to the array + self.add_path(pin.layer, [cell_loc, pin_loc]) + + def connect_vertical_side_pin(self, pin, xoffset): + """ + Used to connect a pin to the left/right vertical straps + """ + cell_loc = pin.center() + pin_loc = vector(xoffset, cell_loc.y) + + # Place the pins a track outside of the array + self.add_via_stack_center(offset=pin_loc, + from_layer=pin.layer, + to_layer=self.supply_stack[2], + directions=("H", "H")) + + # Add a path to connect to the array + self.add_path(pin.layer, [cell_loc, pin_loc]) + + def analytical_power(self, corner, load): + """Power of Bitcell array and bitline in nW.""" + # Dynamic Power from Bitline + bl_wire = self.gen_bl_wire() + cell_load = 2 * bl_wire.return_input_cap() + bl_swing = OPTS.rbl_delay_percentage + freq = spice["default_event_frequency"] + bitline_dynamic = self.calc_dynamic_power(corner, cell_load, freq, swing=bl_swing) + + # Calculate the bitcell power which currently only includes leakage + cell_power = self.cell.analytical_power(corner, load) + + # Leakage power grows with entire array and bitlines. + total_power = self.return_power(cell_power.dynamic + bitline_dynamic * self.column_size, + cell_power.leakage * self.column_size * self.row_size) + return total_power + + + def gen_bl_wire(self): + if OPTS.netlist_only: + height = 0 + else: + height = self.height + bl_pos = 0 + bl_wire = self.generate_rc_net(int(self.row_size - bl_pos), height, drc("minwidth_m1")) + bl_wire.wire_c =spice["min_tx_drain_c"] + bl_wire.wire_c # 1 access tx d/s per cell + return bl_wire + + def graph_exclude_bits(self, targ_row=None, targ_col=None): + """ + Excludes bits in column from being added to graph except target + """ + self.replica_bitcell_array.graph_exclude_bits(targ_row, targ_col) + + def graph_exclude_replica_col_bits(self): + """ + Exclude all replica/dummy cells in the replica columns except the replica bit. + """ + self.replica_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.replica_bitcell_array.get_cell_name(inst_name + "{}x".format(OPTS.hier_seperator) + self.replica_bitcell_array_inst.name, row, col) + + def clear_exclude_bits(self): + """ + Clears the bit exclusions + """ + self.replica_bitcell_array.clear_exclude_bits() diff --git a/compiler/modules/local_bitcell_array.py b/compiler/modules/local_bitcell_array.py index 9c4892ce..f2bb8022 100644 --- a/compiler/modules/local_bitcell_array.py +++ b/compiler/modules/local_bitcell_array.py @@ -19,7 +19,7 @@ class local_bitcell_array(bitcell_base_array): 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, left_rbl=[], right_rbl=[], name=""): + 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, @@ -30,9 +30,19 @@ class local_bitcell_array(bitcell_base_array): self.rows = rows self.cols = cols + # This is how many RBLs are in all the arrays self.rbl = rbl - self.left_rbl = left_rbl - self.right_rbl = right_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.") @@ -67,7 +77,7 @@ class local_bitcell_array(bitcell_base_array): # This is just used for names self.cell = factory.create(module_type=OPTS.bitcell) - self.bitcell_array = factory.create(module_type="replica_bitcell_array", + self.bitcell_array = factory.create(module_type="capped_replica_bitcell_array", cols=self.cols, rows=self.rows, rbl=self.rbl, diff --git a/compiler/modules/replica_bitcell_array.py b/compiler/modules/replica_bitcell_array.py index a6c1bbba..87a96827 100644 --- a/compiler/modules/replica_bitcell_array.py +++ b/compiler/modules/replica_bitcell_array.py @@ -15,15 +15,15 @@ from .bitcell_base_array import bitcell_base_array class replica_bitcell_array(bitcell_base_array): """ - Creates a bitcell arrow of cols x rows and then adds the replica + Creates a bitcell array of cols x rows and then adds the replica and dummy columns and rows. Replica columns are on the left and right, respectively and connected to the given bitcell ports. Dummy are the outside columns/rows with WL and BL tied to gnd. Requires a regular bitcell array, replica bitcell, and dummy - bitcell (Bl/BR disconnected). + bitcell (BL/BR disconnected). """ def __init__(self, rows, cols, rbl=None, left_rbl=None, right_rbl=None, name=""): - super().__init__(name, rows, cols, column_offset=0) + super().__init__(name=name, rows=rows, cols=cols, column_offset=0) debug.info(1, "Creating {0} {1} x {2} rbls: {3} left_rbl: {4} right_rbl: {5}".format(self.name, rows, cols, @@ -36,47 +36,27 @@ class replica_bitcell_array(bitcell_base_array): self.column_size = cols self.row_size = rows # This is how many RBLs are in all the arrays - if rbl: - self.rbl = rbl - else: - self.rbl=[1, 1 if len(self.all_ports)>1 else 0] - # This specifies which RBL to put on the left or right - # by port number + 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 != None: + if left_rbl is not None: self.left_rbl = left_rbl else: - self.left_rbl = [0] + self.left_rbl = [] # This could be an empty list - if right_rbl != None: + if right_rbl is not None: self.right_rbl = right_rbl else: - self.right_rbl=[1] if len(self.all_ports) > 1 else [] + self.right_rbl=[] self.rbls = self.left_rbl + self.right_rbl - debug.check(sum(self.rbl) == len(self.all_ports), - "Invalid number of RBLs for port configuration.") debug.check(sum(self.rbl) >= len(self.left_rbl) + len(self.right_rbl), "Invalid number of RBLs for port configuration.") - # Two dummy rows plus replica even if we don't add the column - self.extra_rows = sum(self.rbl) - # Two dummy cols plus replica if we add the column - self.extra_cols = len(self.left_rbl) + len(self.right_rbl) - - # If we aren't using row/col caps, then we need to use the bitcell - if not self.cell.end_caps: - self.extra_rows += 2 - self.extra_cols += 2 - 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() @@ -120,16 +100,16 @@ class replica_bitcell_array(bitcell_base_array): # We will always have self.rbl[0] rows of replica wordlines below # the array. # These go from the top (where the bitcell array starts ) down - replica_bit = self.rbl[0] - port - column_offset = self.rbl[0] + replica_bit = self.rbl[0] - port - 1 + column_offset = len(self.left_rbl) elif port in self.right_rbl: # We will always have self.rbl[0] rows of replica wordlines below # the array. # These go from the bottom up - replica_bit = self.rbl[0] + self.row_size + port - column_offset = self.rbl[0] + self.column_size + 1 + replica_bit = self.rbl[0] + self.row_size + port - 1 + column_offset = len(self.left_rbl) + self.column_size + 1 else: continue @@ -147,43 +127,6 @@ class replica_bitcell_array(bitcell_base_array): column_offset=1 + len(self.left_rbl), mirror=0) - # Dummy Row or Col Cap, depending on bitcell array properties - col_cap_module_type = ("col_cap_array" if self.cell.end_caps else "dummy_array") - self.col_cap_top = factory.create(module_type=col_cap_module_type, - cols=self.column_size, - rows=1, - # dummy column + left replica column(s) - column_offset=1 + len(self.left_rbl), - mirror=0, - location="top") - - self.col_cap_bottom = factory.create(module_type=col_cap_module_type, - cols=self.column_size, - rows=1, - # dummy column + left replica column(s) - column_offset=1 + len(self.left_rbl), - mirror=0, - location="bottom") - - # Dummy Col or Row Cap, depending on bitcell array properties - row_cap_module_type = ("row_cap_array" if self.cell.end_caps else "dummy_array") - - self.row_cap_left = factory.create(module_type=row_cap_module_type, - cols=1, - column_offset=0, - rows=self.row_size + self.extra_rows, - mirror=(self.rbl[0] + 1) % 2) - - self.row_cap_right = factory.create(module_type=row_cap_module_type, - cols=1, - # dummy column - # + left replica column(s) - # + bitcell columns - # + right replica column(s) - column_offset=1 + len(self.left_rbl) + self.column_size + self.rbl[0], - rows=self.row_size + self.extra_rows, - mirror=(self.rbl[0] + 1) %2) - def add_pins(self): # Arrays are always: @@ -218,54 +161,54 @@ class replica_bitcell_array(bitcell_base_array): # Make a flat list too self.all_bitline_names = [x for sl in zip(*self.bitline_names) for x in sl] + self.bitline_pin_list = [] 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") + self.bitline_pin_list.extend(self.rbl_bitline_names[port]) + self.bitline_pin_list.extend(self.all_bitline_names) for port in self.right_rbl: - self.add_pin_list(self.rbl_bitline_names[port], "INOUT") + self.bitline_pin_list.extend(self.rbl_bitline_names[port]) + + self.add_pin_list(self.bitline_pin_list, "INOUT") def add_wordline_pins(self): - - # Wordlines to ground - self.gnd_wordline_names = [] + # Unused wordlines are connected to ground at the next level of hierarchy + self.unused_wordline_names = [] for port in self.all_ports: for bit in self.all_ports: self.rbl_wordline_names[port].append("rbl_wl_{0}_{1}".format(port, bit)) if bit != port: - self.gnd_wordline_names.append("rbl_wl_{0}_{1}".format(port, bit)) + self.unused_wordline_names.append("rbl_wl_{0}_{1}".format(port, bit)) self.all_rbl_wordline_names = [x for sl in self.rbl_wordline_names for x in sl] self.wordline_names = self.bitcell_array.wordline_names self.all_wordline_names = self.bitcell_array.all_wordline_names - # All wordlines including dummy and RBL - self.replica_array_wordline_names = [] - self.replica_array_wordline_names.extend(["gnd"] * len(self.col_cap_top.get_wordline_names())) + # All wordlines including RBL + self.wordline_pin_list = [] for bit in range(self.rbl[0]): - self.replica_array_wordline_names.extend([x if x not in self.gnd_wordline_names else "gnd" for x in self.rbl_wordline_names[bit]]) - self.replica_array_wordline_names.extend(self.all_wordline_names) + self.wordline_pin_list.extend(self.rbl_wordline_names[bit]) + self.wordline_pin_list.extend(self.all_wordline_names) for bit in range(self.rbl[1]): - self.replica_array_wordline_names.extend([x if x not in self.gnd_wordline_names else "gnd" for x in self.rbl_wordline_names[self.rbl[0] + bit]]) - self.replica_array_wordline_names.extend(["gnd"] * len(self.col_cap_top.get_wordline_names())) + self.wordline_pin_list.extend(self.rbl_wordline_names[self.rbl[0] + bit]) + self.used_wordline_names = [] for port in range(self.rbl[0]): - self.add_pin(self.rbl_wordline_names[port][port], "INPUT") - self.add_pin_list(self.all_wordline_names, "INPUT") + self.used_wordline_names.append(self.rbl_wordline_names[port][port]) + self.used_wordline_names.extend(self.all_wordline_names) for port in range(self.rbl[0], self.rbl[0] + self.rbl[1]): - self.add_pin(self.rbl_wordline_names[port][port], "INPUT") + self.used_wordline_names.append(self.rbl_wordline_names[port][port]) + + self.add_pin_list(self.wordline_pin_list, "INPUT") def create_instances(self): """ Create the module instances used in this design """ self.supplies = ["vdd", "gnd"] - # Used for names/dimensions only - self.cell = factory.create(module_type=OPTS.bitcell) - # Main array self.bitcell_array_inst=self.add_inst(name="bitcell_array", - mod=self.bitcell_array) + mod=self.bitcell_array) self.connect_inst(self.all_bitline_names + self.all_wordline_names + self.supplies) # Replica columns @@ -274,35 +217,17 @@ class replica_bitcell_array(bitcell_base_array): if port in self.rbls: self.replica_col_insts.append(self.add_inst(name="replica_col_{}".format(port), mod=self.replica_columns[port])) - self.connect_inst(self.rbl_bitline_names[port] + self.replica_array_wordline_names + self.supplies) + self.connect_inst(self.rbl_bitline_names[port] + self.wordline_pin_list + self.supplies) else: self.replica_col_insts.append(None) - # Dummy rows under the bitcell array (connected with with the replica cell wl) + # Dummy rows above/below the bitcell array (connected with the replica cell wl) self.dummy_row_replica_insts = [] # Note, this is the number of left and right even if we aren't adding the columns to this bitcell array! - for port in self.all_ports: + for port in self.all_ports: # TODO: tie to self.rbl or whatever self.dummy_row_replica_insts.append(self.add_inst(name="dummy_row_{}".format(port), mod=self.dummy_row)) - self.connect_inst(self.all_bitline_names + [x if x not in self.gnd_wordline_names else "gnd" for x in self.rbl_wordline_names[port]] + self.supplies) - - # Top/bottom dummy rows or col caps - self.dummy_row_insts = [] - self.dummy_row_insts.append(self.add_inst(name="dummy_row_bot", - mod=self.col_cap_bottom)) - self.connect_inst(self.all_bitline_names + ["gnd"] * len(self.col_cap_bottom.get_wordline_names()) + self.supplies) - self.dummy_row_insts.append(self.add_inst(name="dummy_row_top", - mod=self.col_cap_top)) - self.connect_inst(self.all_bitline_names + ["gnd"] * len(self.col_cap_top.get_wordline_names()) + self.supplies) - - # Left/right Dummy columns - self.dummy_col_insts = [] - self.dummy_col_insts.append(self.add_inst(name="dummy_col_left", - mod=self.row_cap_left)) - self.connect_inst(["dummy_left_" + bl for bl in self.row_cap_left.all_bitline_names] + self.replica_array_wordline_names + self.supplies) - self.dummy_col_insts.append(self.add_inst(name="dummy_col_right", - mod=self.row_cap_right)) - self.connect_inst(["dummy_right_" + bl for bl in self.row_cap_right.all_bitline_names] + self.replica_array_wordline_names + self.supplies) + self.connect_inst(self.all_bitline_names + self.rbl_wordline_names[port] + self.supplies) def create_layout(self): @@ -310,7 +235,6 @@ class replica_bitcell_array(bitcell_base_array): # row-based or column based power and ground lines. self.vertical_pitch = 1.1 * getattr(self, "{}_pitch".format(self.supply_stack[0])) self.horizontal_pitch = 1.1 * getattr(self, "{}_pitch".format(self.supply_stack[2])) - self.unused_offset = vector(0.25, 0.25) # This is a bitcell x bitcell offset to scale self.bitcell_offset = vector(self.cell.width, self.cell.height) @@ -318,34 +242,22 @@ class replica_bitcell_array(bitcell_base_array): self.row_end_offset = vector(self.cell.width, self.cell.height) # Everything is computed with the main array - self.bitcell_array_inst.place(offset=self.unused_offset) + self.bitcell_array_inst.place(offset=0) self.add_replica_columns() - self.add_end_caps() - # Array was at (0, 0) but move everything so it is at the lower left # We move DOWN the number of left RBL even if we didn't add the column to this bitcell array # Note that this doesn't include the row/col cap - array_offset = self.bitcell_offset.scale(1 + len(self.left_rbl), 1 + self.rbl[0]) + array_offset = self.bitcell_offset.scale(len(self.left_rbl), self.rbl[0]) self.translate_all(array_offset.scale(-1, -1)) - # Add extra width on the left and right for the unused WLs - - self.width = self.dummy_col_insts[1].rx() + self.unused_offset.x - self.height = self.dummy_row_insts[1].uy() - self.add_layout_pins() self.route_supplies() - self.route_unused_wordlines() - - lower_left = self.find_lowest_coords() - upper_right = self.find_highest_coords() - self.width = upper_right.x - lower_left.x - self.height = upper_right.y - lower_left.y - self.translate_all(lower_left) + self.height = (sum(self.rbl) + self.row_size) * self.cell.height + self.width = (len(self.rbls) + self.column_size) * self.cell.width self.add_boundary() @@ -367,22 +279,6 @@ class replica_bitcell_array(bitcell_base_array): """ Return the right of the main bitcell array. """ return self.bitcell_array_inst.rx() - def get_replica_top(self): - """ Return the top of all replica columns. """ - return self.dummy_row_insts[0].by() - - def get_replica_bottom(self): - """ Return the bottom of all replica columns. """ - return self.dummy_row_insts[0].uy() - - def get_replica_left(self): - """ Return the left of all replica columns. """ - return self.dummy_col_insts[0].rx() - - def get_replica_right(self): - """ Return the right of all replica columns. """ - return self.dummy_col_insts[1].rx() - def get_column_offsets(self): """ Return an array of the x offsets of all the regular bits @@ -395,18 +291,18 @@ class replica_bitcell_array(bitcell_base_array): # Grow from left to right, toward the array for bit, port in enumerate(self.left_rbl): - offset = self.bitcell_offset.scale(-len(self.left_rbl) + bit, -self.rbl[0] - 1) + self.unused_offset + offset = self.bitcell_offset.scale(-len(self.left_rbl) + bit, -self.rbl[0]) self.replica_col_insts[bit].place(offset) # Grow to the right of the bitcell array, array outward for bit, port in enumerate(self.right_rbl): - offset = self.bitcell_array_inst.lr() + self.bitcell_offset.scale(bit, -self.rbl[0] - 1) + offset = self.bitcell_array_inst.lr() + self.bitcell_offset.scale(bit, -self.rbl[0]) self.replica_col_insts[self.rbl[0] + bit].place(offset) # Replica dummy rows # Add the dummy rows even if we aren't adding the replica column to this bitcell array # These grow up, toward the array for bit in range(self.rbl[0]): - dummy_offset = self.bitcell_offset.scale(0, -self.rbl[0] + bit + (-self.rbl[0] + bit) % 2) + self.unused_offset + dummy_offset = self.bitcell_offset.scale(0, -self.rbl[0] + bit + (-self.rbl[0] + bit) % 2) self.dummy_row_replica_insts[bit].place(offset=dummy_offset, mirror="MX" if (-self.rbl[0] + bit) % 2 else "R0") # These grow up, away from the array @@ -415,35 +311,11 @@ class replica_bitcell_array(bitcell_base_array): self.dummy_row_replica_insts[self.rbl[0] + bit].place(offset=dummy_offset, mirror="MX" if (self.row_size + bit) % 2 else "R0") - def add_end_caps(self): - """ Add dummy cells or end caps around the array """ - - # Far top dummy row (first row above array is NOT flipped if even number of rows) - flip_dummy = (self.row_size + self.rbl[1]) % 2 - dummy_row_offset = self.bitcell_offset.scale(0, self.rbl[1] + flip_dummy) + self.bitcell_array_inst.ul() - self.dummy_row_insts[1].place(offset=dummy_row_offset, - mirror="MX" if flip_dummy else "R0") - - # Far bottom dummy row (first row below array IS flipped) - flip_dummy = (self.rbl[0] + 1) % 2 - dummy_row_offset = self.bitcell_offset.scale(0, -self.rbl[0] - 1 + flip_dummy) + self.unused_offset - self.dummy_row_insts[0].place(offset=dummy_row_offset, - mirror="MX" if flip_dummy else "R0") - # Far left dummy col - # Shifted down by the number of left RBLs even if we aren't adding replica column to this bitcell array - dummy_col_offset = self.bitcell_offset.scale(-len(self.left_rbl) - 1, -self.rbl[0] - 1) + self.unused_offset - self.dummy_col_insts[0].place(offset=dummy_col_offset) - - # Far right dummy col - # Shifted down by the number of left RBLs even if we aren't adding replica column to this bitcell array - dummy_col_offset = self.bitcell_offset.scale(len(self.right_rbl), -self.rbl[0] - 1) + self.bitcell_array_inst.lr() - self.dummy_col_insts[1].place(offset=dummy_col_offset) - def add_layout_pins(self): """ Add the layout pins """ # All wordlines - # Main array wl and bl/br + # Main array wl for pin_name in self.all_wordline_names: pin_list = self.bitcell_array_inst.get_pins(pin_name) for pin in pin_list: @@ -457,8 +329,6 @@ class replica_bitcell_array(bitcell_base_array): # even though the column is in another local bitcell array) for (names, inst) in zip(self.rbl_wordline_names, self.dummy_row_replica_insts): for (wl_name, pin_name) in zip(names, self.dummy_row.get_wordline_names()): - if wl_name in self.gnd_wordline_names: - continue pin = inst.get_pin(pin_name) self.add_layout_pin(text=wl_name, layer=pin.layer, @@ -466,6 +336,7 @@ class replica_bitcell_array(bitcell_base_array): width=self.width, height=pin.height()) + # Main array bl/br for pin_name in self.all_bitline_names: pin_list = self.bitcell_array_inst.get_pins(pin_name) for pin in pin_list: @@ -489,192 +360,9 @@ class replica_bitcell_array(bitcell_base_array): def route_supplies(self): - if OPTS.bitcell == "pbitcell": - bitcell = factory.create(module_type="pbitcell") - else: - bitcell = getattr(props, "bitcell_{}port".format(OPTS.num_ports)) - - vdd_dir = bitcell.vdd_dir - gnd_dir = bitcell.gnd_dir - - # vdd/gnd are only connected in the perimeter cells - supply_insts = self.dummy_col_insts + self.dummy_row_insts - - # For the wordlines - top_bot_mult = 1 - left_right_mult = 1 - - # There are always vertical pins for the WLs on the left/right if we have unused wordlines - self.left_gnd_locs = self.route_side_pin("gnd", "left", left_right_mult) - self.right_gnd_locs = self.route_side_pin("gnd","right", left_right_mult) - # This needs to be big enough so that they aren't in the same supply routing grid - left_right_mult = 4 - - if gnd_dir == "V": - self.top_gnd_locs = self.route_side_pin("gnd", "top", top_bot_mult) - self.bot_gnd_locs = self.route_side_pin("gnd", "bot", top_bot_mult) - # This needs to be big enough so that they aren't in the same supply routing grid - top_bot_mult = 4 - - if vdd_dir == "V": - self.top_vdd_locs = self.route_side_pin("vdd", "top", top_bot_mult) - self.bot_vdd_locs = self.route_side_pin("vdd", "bot", top_bot_mult) - elif vdd_dir == "H": - self.left_vdd_locs = self.route_side_pin("vdd", "left", left_right_mult) - self.right_vdd_locs = self.route_side_pin("vdd", "right", left_right_mult) - else: - debug.error("Invalid vdd direction {}".format(vdd_dir), -1) - - - for inst in supply_insts: - for pin in inst.get_pins("vdd"): - if vdd_dir == "V": - self.connect_side_pin(pin, "top", self.top_vdd_locs[0].y) - self.connect_side_pin(pin, "bot", self.bot_vdd_locs[0].y) - elif vdd_dir == "H": - self.connect_side_pin(pin, "left", self.left_vdd_locs[0].x) - self.connect_side_pin(pin, "right", self.right_vdd_locs[0].x) - - - for inst in supply_insts: - for pin in inst.get_pins("gnd"): - if gnd_dir == "V": - self.connect_side_pin(pin, "top", self.top_gnd_locs[0].y) - self.connect_side_pin(pin, "bot", self.bot_gnd_locs[0].y) - elif gnd_dir == "H": - self.connect_side_pin(pin, "left", self.left_gnd_locs[0].x) - self.connect_side_pin(pin, "right", self.right_gnd_locs[0].x) - - - def route_unused_wordlines(self): - """ Connect the unused RBL and dummy wordlines to gnd """ - # This grounds all the dummy row word lines - for inst in self.dummy_row_insts: - for wl_name in self.col_cap_top.get_wordline_names(): - pin = inst.get_pin(wl_name) - self.connect_side_pin(pin, "left", self.left_gnd_locs[0].x) - self.connect_side_pin(pin, "right", self.right_gnd_locs[0].x) - - # Ground the unused replica wordlines - for (names, inst) in zip(self.rbl_wordline_names, self.dummy_row_replica_insts): - for (wl_name, pin_name) in zip(names, self.dummy_row.get_wordline_names()): - if wl_name in self.gnd_wordline_names: - pin = inst.get_pin(pin_name) - self.connect_side_pin(pin, "left", self.left_gnd_locs[0].x) - self.connect_side_pin(pin, "right", self.right_gnd_locs[0].x) - - def route_side_pin(self, name, side, offset_multiple=1): - """ - Routes a vertical or horizontal pin on the side of the bbox. - The multiple specifies how many track offsets to be away from the side assuming - (0,0) (self.width, self.height) - """ - if side in ["left", "right"]: - return self.route_vertical_side_pin(name, side, offset_multiple) - elif side in ["top", "bottom", "bot"]: - return self.route_horizontal_side_pin(name, side, offset_multiple) - else: - debug.error("Invalid side {}".format(side), -1) - - def route_vertical_side_pin(self, name, side, offset_multiple=1): - """ - Routes a vertical pin on the side of the bbox. - """ - if side == "left": - bot_loc = vector(-offset_multiple * self.vertical_pitch, 0) - top_loc = vector(-offset_multiple * self.vertical_pitch, self.height) - elif side == "right": - bot_loc = vector(self.width + offset_multiple * self.vertical_pitch, 0) - top_loc = vector(self.width + offset_multiple * self.vertical_pitch, self.height) - - layer = self.supply_stack[2] - top_via = contact(layer_stack=self.supply_stack, - directions=("H", "H")) - - -# self.add_layout_pin_rect_ends(text=name, -# layer=layer, -# start=bot_loc, -# end=top_loc) - self.add_layout_pin_segment_center(text=name, - layer=layer, - start=bot_loc, - end=top_loc, - width=top_via.second_layer_width) - - return (bot_loc, top_loc) - - def route_horizontal_side_pin(self, name, side, offset_multiple=1): - """ - Routes a horizontal pin on the side of the bbox. - """ - if side in ["bottom", "bot"]: - left_loc = vector(0, -offset_multiple * self.horizontal_pitch) - right_loc = vector(self.width, -offset_multiple * self.horizontal_pitch) - elif side == "top": - left_loc = vector(0, self.height + offset_multiple * self.horizontal_pitch) - right_loc = vector(self.width, self.height + offset_multiple * self.horizontal_pitch) - - layer = self.supply_stack[0] - side_via = contact(layer_stack=self.supply_stack, - directions=("V", "V")) - -# self.add_layout_pin_rect_ends(text=name, -# layer=layer, -# start=left_loc, -# end=right_loc) - self.add_layout_pin_segment_center(text=name, - layer=layer, - start=left_loc, - end=right_loc, - width=side_via.first_layer_height) - - return (left_loc, right_loc) - - def connect_side_pin(self, pin, side, offset): - """ - Used to connect horizontal layers of pins to the left/right straps - locs provides the offsets of the pin strip end points. - """ - if side in ["left", "right"]: - self.connect_vertical_side_pin(pin, side, offset) - elif side in ["top", "bottom", "bot"]: - self.connect_horizontal_side_pin(pin, side, offset) - else: - debug.error("Invalid side {}".format(side), -1) - - def connect_horizontal_side_pin(self, pin, side, yoffset): - """ - Used to connect vertical layers of pins to the top/bottom horizontal straps - """ - cell_loc = pin.center() - pin_loc = vector(cell_loc.x, yoffset) - - # Place the pins a track outside of the array - self.add_via_stack_center(offset=pin_loc, - from_layer=pin.layer, - to_layer=self.supply_stack[0], - directions=("V", "V")) - - # Add a path to connect to the array - self.add_path(pin.layer, [cell_loc, pin_loc]) - - - def connect_vertical_side_pin(self, pin, side, xoffset): - """ - Used to connect vertical layers of pins to the top/bottom vertical straps - """ - cell_loc = pin.center() - pin_loc = vector(xoffset, cell_loc.y) - - # Place the pins a track outside of the array - self.add_via_stack_center(offset=pin_loc, - from_layer=pin.layer, - to_layer=self.supply_stack[2], - directions=("H", "H")) - - # Add a path to connect to the array - self.add_path(pin.layer, [cell_loc, pin_loc]) + for inst in self.insts: + for pin_name in ["vdd", "gnd"]: + self.copy_layout_pin(inst, pin_name) def analytical_power(self, corner, load): """Power of Bitcell array and bitline in nW.""" diff --git a/compiler/modules/replica_column.py b/compiler/modules/replica_column.py index a091cad2..79a6e276 100644 --- a/compiler/modules/replica_column.py +++ b/compiler/modules/replica_column.py @@ -26,11 +26,9 @@ class replica_column(bitcell_base_array): # Row size is the number of rows with word lines self.row_size = sum(rbl) + rows # Start of regular word line rows - self.row_start = rbl[0] + 1 + self.row_start = rbl[0] # End of regular word line rows self.row_end = self.row_start + rows - if not self.cell.end_caps: - self.row_size += 2 super().__init__(rows=self.row_size, cols=1, column_offset=column_offset, name=name) self.rows = rows @@ -38,15 +36,14 @@ class replica_column(bitcell_base_array): self.right_rbl = rbl[1] self.replica_bit = replica_bit - # Total size includes the replica rows and column cap rows - self.total_size = self.left_rbl + rows + self.right_rbl + 2 + # Total size includes the replica rows + self.total_size = self.left_rbl + rows + self.right_rbl self.column_offset = column_offset - debug.check(replica_bit != 0 and replica_bit != self.total_size - 1, - "Replica bit cannot be the dummy/cap row.") debug.check(replica_bit < self.row_start or replica_bit >= self.row_end, "Replica bit cannot be in the regular array.") + if layer_props.replica_column.even_rows: debug.check(rows % 2 == 0 and (self.left_rbl + 1) % 2 == 0, "sky130 currently requires rows to be even and to start with X mirroring" @@ -90,44 +87,28 @@ class replica_column(bitcell_base_array): self.dummy_cell = factory.create(module_type=OPTS.dummy_bitcell) - try: - edge_module_type = ("col_cap" if self.cell.end_caps else "dummy") - except AttributeError: - edge_module_type = "dummy" - self.edge_cell = factory.create(module_type=edge_module_type + "_" + OPTS.bitcell) - def create_instances(self): self.cell_inst = [] for row in range(self.total_size): - name="rbc_{0}".format(row) - real_row = row - if self.cell.end_caps: - real_row -= 1 + name = "rbc_{0}".format(row) # Regular array cells are replica cells # Replic bit specifies which other bit (in the full range (0,total_size) to make a replica cell. - if (row == 0 or row == self.total_size - 1): - self.cell_inst.append(self.add_inst(name=name, - mod=self.edge_cell)) - if self.cell.end_caps: - self.connect_inst(self.get_bitcell_pins_col_cap(real_row, 0)) - else: - self.connect_inst(self.get_bitcell_pins(real_row, 0)) - elif (row==self.replica_bit) or (row >= self.row_start and row < self.row_end): + # All other cells are dummies + if (row == self.replica_bit) or (row >= self.row_start and row < self.row_end): self.cell_inst.append(self.add_inst(name=name, mod=self.replica_cell)) - self.connect_inst(self.get_bitcell_pins(real_row, 0)) + self.connect_inst(self.get_bitcell_pins(row, 0)) else: - # Top/bottom cell are always dummy/cap cells. self.cell_inst.append(self.add_inst(name=name, mod=self.dummy_cell)) - self.connect_inst(self.get_bitcell_pins(real_row, 0)) + self.connect_inst(self.get_bitcell_pins(row, 0)) def place_instances(self): # Flip the mirrors if we have an odd number of replica+dummy rows at the bottom # so that we will start with mirroring rather than not mirroring - rbl_offset = (self.left_rbl + 1) %2 + rbl_offset = (self.left_rbl) % 2 # if our bitcells are mirrored on the y axis, check if we are in global # column that needs to be flipped. @@ -156,7 +137,6 @@ class replica_column(bitcell_base_array): mirror=dir_key) def add_layout_pins(self): - """ Add the layout pins """ for port in self.all_ports: bl_pin = self.cell_inst[0].get_pin(self.cell.get_bl_name(port)) self.add_layout_pin(text="bl_{0}_{1}".format(port, 0), @@ -171,17 +151,10 @@ class replica_column(bitcell_base_array): width=bl_pin.width(), height=self.height) - if self.cell.end_caps: - row_range_max = self.total_size - 1 - row_range_min = 1 - else: - row_range_max = self.total_size - row_range_min = 0 - for port in self.all_ports: - for row in range(row_range_min, row_range_max): + for row in range(self.total_size): wl_pin = self.cell_inst[row].get_pin(self.cell.get_wl_name(port)) - self.add_layout_pin(text="wl_{0}_{1}".format(port, row - row_range_min), + self.add_layout_pin(text="wl_{0}_{1}".format(port, row), layer=wl_pin.layer, offset=wl_pin.ll().scale(0, 1), width=self.width, diff --git a/compiler/tests/14_capped_replica_bitcell_array_bothrbl_1rw_1r_test.py b/compiler/tests/14_capped_replica_bitcell_array_bothrbl_1rw_1r_test.py new file mode 100755 index 00000000..a2f57e45 --- /dev/null +++ b/compiler/tests/14_capped_replica_bitcell_array_bothrbl_1rw_1r_test.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz +# All rights reserved. +# +import sys, os +import unittest +from testutils import * + +import openram +from openram import debug +from openram.sram_factory import factory +from openram import OPTS + + +class capped_replica_bitcell_array_1rw_1r_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + openram.init_openram(config_file, is_unit_test=True) + + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + openram.setup_bitcell() + + debug.info(2, "Testing 4x4 array left and right replica for dp cell") + a = factory.create(module_type="capped_replica_bitcell_array", + cols=4, + rows=4, + rbl=[1, 1], + left_rbl=[0], + right_rbl=[1]) + self.local_check(a) + + openram.end_openram() + + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = openram.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/14_capped_replica_bitcell_array_leftrbl_1rw_1r_test.py b/compiler/tests/14_capped_replica_bitcell_array_leftrbl_1rw_1r_test.py new file mode 100755 index 00000000..3e1bd805 --- /dev/null +++ b/compiler/tests/14_capped_replica_bitcell_array_leftrbl_1rw_1r_test.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz +# All rights reserved. +# +import sys, os +import unittest +from testutils import * + +import openram +from openram import debug +from openram.sram_factory import factory +from openram import OPTS + + +class capped_replica_bitcell_array_1rw_1r_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + openram.init_openram(config_file, is_unit_test=True) + + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + openram.setup_bitcell() + + debug.info(2, "Testing 4x4 left replica array for dp cell") + a = factory.create(module_type="capped_replica_bitcell_array", + cols=4, + rows=4, + rbl=[1, 1], + left_rbl=[0]) + self.local_check(a) + + openram.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = openram.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/14_capped_replica_bitcell_array_leftrbl_1rw_test.py b/compiler/tests/14_capped_replica_bitcell_array_leftrbl_1rw_test.py new file mode 100755 index 00000000..b4ba91d1 --- /dev/null +++ b/compiler/tests/14_capped_replica_bitcell_array_leftrbl_1rw_test.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz +# All rights reserved. +# +import sys, os +import unittest +from testutils import * + +import openram +from openram import debug +from openram.sram_factory import factory +from openram import OPTS + + +class capped_replica_bitcell_array_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + openram.init_openram(config_file, is_unit_test=True) + + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 0 + OPTS.num_w_ports = 0 + + factory.reset() + debug.info(2, "Testing 4x4 array for bitcell") + a = factory.create(module_type="capped_replica_bitcell_array", cols=7, rows=5, rbl=[1, 0], left_rbl=[0]) + self.local_check(a) + + openram.end_openram() + + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = openram.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/14_capped_replica_bitcell_array_norbl_1rw_1r_test.py b/compiler/tests/14_capped_replica_bitcell_array_norbl_1rw_1r_test.py new file mode 100755 index 00000000..bc432c6b --- /dev/null +++ b/compiler/tests/14_capped_replica_bitcell_array_norbl_1rw_1r_test.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz +# All rights reserved. +# +import sys, os +import unittest +from testutils import * + +import openram +from openram import debug +from openram.sram_factory import factory +from openram import OPTS + + +class capped_replica_bitcell_array_1rw_1r_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + openram.init_openram(config_file, is_unit_test=True) + + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + openram.setup_bitcell() + + debug.info(2, "Testing 4x4 non-replica array for dp cell") + a = factory.create(module_type="capped_replica_bitcell_array", + cols=4, + rows=4, + rbl=[1, 1]) + self.local_check(a) + + openram.end_openram() + + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = openram.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/14_capped_replica_bitcell_array_norbl_1rw_test.py b/compiler/tests/14_capped_replica_bitcell_array_norbl_1rw_test.py new file mode 100755 index 00000000..175689c3 --- /dev/null +++ b/compiler/tests/14_capped_replica_bitcell_array_norbl_1rw_test.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz +# All rights reserved. +# +import sys, os +import unittest +from testutils import * + +import openram +from openram import debug +from openram.sram_factory import factory +from openram import OPTS + + +class capped_replica_bitcell_array_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + openram.init_openram(config_file, is_unit_test=True) + + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 0 + OPTS.num_w_ports = 0 + + factory.reset() + debug.info(2, "Testing 4x4 array for bitcell") + a = factory.create(module_type="capped_replica_bitcell_array", cols=7, rows=5, rbl=[1, 0]) + self.local_check(a) + + openram.end_openram() + + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = openram.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/14_capped_replica_bitcell_array_rightrbl_1rw_1r_test.py b/compiler/tests/14_capped_replica_bitcell_array_rightrbl_1rw_1r_test.py new file mode 100755 index 00000000..deac7075 --- /dev/null +++ b/compiler/tests/14_capped_replica_bitcell_array_rightrbl_1rw_1r_test.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz +# All rights reserved. +# +import sys, os +import unittest +from testutils import * + +import openram +from openram import debug +from openram.sram_factory import factory +from openram import OPTS + + +class capped_replica_bitcell_array_1rw_1r_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + openram.init_openram(config_file, is_unit_test=True) + + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + openram.setup_bitcell() + + debug.info(2, "Testing 4x4 left replica array for dp cell") + a = factory.create(module_type="capped_replica_bitcell_array", + cols=4, + rows=4, + rbl=[1, 1], + right_rbl=[1]) + self.local_check(a) + + openram.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = openram.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/14_replica_bitcell_array_leftrbl_1rw_test.py b/compiler/tests/14_replica_bitcell_array_leftrbl_1rw_test.py new file mode 100755 index 00000000..f77a571d --- /dev/null +++ b/compiler/tests/14_replica_bitcell_array_leftrbl_1rw_test.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz +# All rights reserved. +# +import sys, os +import unittest +from testutils import * + +import openram +from openram import debug +from openram.sram_factory import factory +from openram import OPTS + + +class replica_bitcell_array_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + openram.init_openram(config_file, is_unit_test=True) + + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 0 + OPTS.num_w_ports = 0 + + factory.reset() + debug.info(2, "Testing 4x4 array for bitcell") + a = factory.create(module_type="replica_bitcell_array", cols=7, rows=5, rbl=[1, 0], left_rbl=[0]) + self.local_check(a) + + openram.end_openram() + + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = openram.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/14_replica_bitcell_array_test.py b/compiler/tests/14_replica_bitcell_array_norbl_1rw_test.py similarity index 100% rename from compiler/tests/14_replica_bitcell_array_test.py rename to compiler/tests/14_replica_bitcell_array_norbl_1rw_test.py diff --git a/compiler/tests/14_replica_bitcell_array_rightrbl_1rw_1r_test.py b/compiler/tests/14_replica_bitcell_array_rightrbl_1rw_1r_test.py new file mode 100755 index 00000000..8d1d8f23 --- /dev/null +++ b/compiler/tests/14_replica_bitcell_array_rightrbl_1rw_1r_test.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz +# All rights reserved. +# +import sys, os +import unittest +from testutils import * + +import openram +from openram import debug +from openram.sram_factory import factory +from openram import OPTS + + +class replica_bitcell_array_1rw_1r_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + openram.init_openram(config_file, is_unit_test=True) + + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + openram.setup_bitcell() + + debug.info(2, "Testing 4x4 left replica array for dp cell") + a = factory.create(module_type="replica_bitcell_array", + cols=4, + rows=4, + rbl=[1, 1], + right_rbl=[1]) + self.local_check(a) + + openram.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = openram.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/14_replica_column_1rw_1r_test.py b/compiler/tests/14_replica_column_1rw_1r_test.py index 555bad7a..ff3fd122 100755 --- a/compiler/tests/14_replica_column_1rw_1r_test.py +++ b/compiler/tests/14_replica_column_1rw_1r_test.py @@ -26,19 +26,19 @@ class replica_column_test(openram_test): openram.setup_bitcell() debug.info(2, "Testing one left replica column for dual port") - a = factory.create(module_type="replica_column", rows=4, rbl=[1, 0], replica_bit=1) + a = factory.create(module_type="replica_column", rows=4, rbl=[1, 0], replica_bit=0) self.local_check(a) debug.info(2, "Testing one right replica column for dual port") - a = factory.create(module_type="replica_column", rows=4, rbl=[0, 1], replica_bit=5) + a = factory.create(module_type="replica_column", rows=4, rbl=[0, 1], replica_bit=4) self.local_check(a) debug.info(2, "Testing two (left, right) replica columns for dual port") - a = factory.create(module_type="replica_column", rows=4, rbl=[1, 1], replica_bit=1) + a = factory.create(module_type="replica_column", rows=4, rbl=[1, 1], replica_bit=0) self.local_check(a) debug.info(2, "Testing two (left, right) replica columns for dual port") - a = factory.create(module_type="replica_column", rows=4, rbl=[1, 1], replica_bit=6) + a = factory.create(module_type="replica_column", rows=4, rbl=[1, 1], replica_bit=5) self.local_check(a) openram.end_openram() diff --git a/compiler/tests/14_replica_column_test.py b/compiler/tests/14_replica_column_1rw_test.py similarity index 96% rename from compiler/tests/14_replica_column_test.py rename to compiler/tests/14_replica_column_1rw_test.py index 31a1a66a..bde76379 100755 --- a/compiler/tests/14_replica_column_test.py +++ b/compiler/tests/14_replica_column_1rw_test.py @@ -30,7 +30,7 @@ class replica_column_test(openram_test): a = factory.create(module_type="replica_column", rows=4 + num_spare_rows, rbl=[1, 0], - replica_bit=1, + replica_bit=0, column_offset=num_spare_cols) self.local_check(a) diff --git a/sram_compiler.py b/sram_compiler.py index ca07391a..10f32393 100755 --- a/sram_compiler.py +++ b/sram_compiler.py @@ -19,20 +19,11 @@ a Liberty (.lib) file for timing analysis/optimization import sys import os import datetime -try: - import openram -except: - # If openram library isn't found as a python package, - # import it from the $OPENRAM_HOME path. - import importlib.util - OPENRAM_HOME = os.getenv("OPENRAM_HOME") - # Import using spec since the directory can be named something - # other than "openram". - spec = importlib.util.spec_from_file_location("openram", "{}/../__init__.py".format(OPENRAM_HOME)) - module = importlib.util.module_from_spec(spec) - sys.modules["openram"] = module - spec.loader.exec_module(module) - import openram + +# You don't need the next two lines if you're sure that openram package is installed +from common import * +make_openram_package() +import openram (OPTS, args) = openram.parse_args()