diff --git a/Makefile b/Makefile index db28296e..ff5f5954 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ SRAM_LIB_GIT_REPO ?= https://github.com/vlsida/sky130_fd_bd_sram.git # Use this for development #SRAM_LIB_GIT_REPO ?= git@github.com:VLSIDA/sky130_fd_bd_sram.git #SRAM_LIB_GIT_REPO ?= https://github.com/google/skywater-pdk-libs-sky130_fd_bd_sram.git -SRAM_LIB_GIT_COMMIT ?= dd64256961317205343a3fd446908b42bafba388 +SRAM_LIB_GIT_COMMIT ?= 8dccd8d8ddb0a9de1b02207b2cd0c0d697807aa9 SKY130_PDK ?= $(PDK_ROOT)/sky130A GF180_PDK ?= $(PDK_ROOT)/gf180mcuD diff --git a/compiler/base/contact.py b/compiler/base/contact.py index da108c37..2554dcd2 100644 --- a/compiler/base/contact.py +++ b/compiler/base/contact.py @@ -66,6 +66,8 @@ class contact(hierarchy_design): self.offset = vector(0, 0) self.implant_type = implant_type self.well_type = well_type + # Module does not have pins, but has empty pin list. + self.pins = [] self.create_layout() def create_layout(self): diff --git a/compiler/base/geometry.py b/compiler/base/geometry.py index e1835b3f..60fedc93 100644 --- a/compiler/base/geometry.py +++ b/compiler/base/geometry.py @@ -164,7 +164,7 @@ class instance(geometry): An instance of a module with a specified location, rotation, spice pins, and spice nets """ - def __init__(self, name, mod, offset=[0, 0], mirror="R0", rotate=0): + def __init__(self, name, mod, offset=[0, 0], mirror="R0", rotate=0, is_bitcell=False): """Initializes an instance to represent a module""" super().__init__() debug.check(mirror not in ["R90", "R180", "R270"], @@ -176,6 +176,8 @@ class instance(geometry): self.rotate = rotate self.offset = vector(offset).snap_to_grid() self.mirror = mirror + self.is_bitcell = is_bitcell + # track if the instance's spice pin connections have been made self.connected = False @@ -183,10 +185,11 @@ class instance(geometry): # change attributes in these spice objects self.spice_pins = copy.deepcopy(self.mod.pins) self.spice_nets = copy.deepcopy(self.mod.nets) - for pin in self.spice_pins.values(): - pin.set_inst(self) - for net in self.spice_nets.values(): - net.set_inst(self) + if "contact" not in mod.name: + for pin in self.spice_pins.values(): + pin.set_inst(self) + for net in self.spice_nets.values(): + net.set_inst(self) if OPTS.netlist_only: self.width = 0 diff --git a/compiler/base/hierarchy_layout.py b/compiler/base/hierarchy_layout.py index 31802ee7..0957bb7c 100644 --- a/compiler/base/hierarchy_layout.py +++ b/compiler/base/hierarchy_layout.py @@ -9,6 +9,7 @@ import sys import os import re from math import sqrt +from copy import deepcopy from openram import debug from openram.gdsMill import gdsMill from openram import tech @@ -460,21 +461,32 @@ class layout(): for pin in pin_list: pin.rect = [pin.ll() - offset, pin.ur() - offset] - def add_inst(self, name, mod, offset=[0, 0], mirror="R0", rotate=0): + def add_inst(self, name, mod, offset=[0, 0], mirror="R0", rotate=0, is_bitcell=False): """ Adds an instance of a mod to this module """ # Contacts are not really instances, so skip them if "contact" not in mod.name: # Check that the instance name is unique debug.check(name not in self.inst_names, "Duplicate named instance in {0}: {1}".format(self.cell_name, name)) - self.mods.add(mod) self.inst_names.add(name) - self.insts.append(geometry.instance(name, mod, offset, mirror, rotate)) + self.insts.append(geometry.instance(name, mod, offset, mirror, rotate, is_bitcell)) debug.info(3, "adding instance {}".format(self.insts[-1])) # This is commented out for runtime reasons # debug.info(4, "instance list: " + ",".join(x.name for x in self.insts)) return self.insts[-1] + def add_existing_inst(self, inst, name): + #new_inst = deepcopy(inst) + new_inst = self.add_inst(name, inst.mod, offset=inst.offset, mirror=inst.mirror, rotate=inst.rotate, is_bitcell=inst.is_bitcell) + #new_inst.mod = inst.mod + self.mods.add(new_inst.mod) + #if name: + # new_inst.name = name + self.inst_names.add(new_inst.name) + #self.insts.append(new_inst) + debug.info(3, "adding existing instance{}".format(self.insts[-1])) + return self.insts[-1] + def get_inst(self, name): """ Retrieve an instance by name """ for inst in self.insts: @@ -2149,6 +2161,13 @@ class layout(): # Add the gnd ring self.add_ring([ll, ur]) + def reset_coordinates(self): + ll=vector(min([x.lx() for x in self.insts]),min([y.by() for y in self.insts])) + + self.translate_all(ll) + self.width = max([x.rx() for x in self.insts]) - min([x.lx() for x in self.insts]) + self.height = max([x.uy() for x in self.insts]) - min([y.by() for y in self.insts]) + def add_ring(self, bbox=None, width_mult=8, offset=0): """ Add a ring around the bbox @@ -2201,7 +2220,7 @@ class layout(): size=(supply_vias, supply_vias)) - def add_power_ring(self): + def add_power_ring(self, h_layer="m2", v_layer="m1", top=True, bottom=True, left=True, right=True): """ Create vdd and gnd power rings around an area of the bounding box argument. Must have a supply_rail_width and supply_rail_pitch @@ -2209,118 +2228,120 @@ class layout(): left/right/top/bottom vdd/gnd center offsets for use in other modules.. """ - [ll, ur] = self.bbox - supply_rail_spacing = self.supply_rail_pitch - self.supply_rail_width + supply_rail_spacing = self.supply_rail_pitch height = (ur.y - ll.y) + 3 * self.supply_rail_pitch - supply_rail_spacing width = (ur.x - ll.x) + 3 * self.supply_rail_pitch - supply_rail_spacing # LEFT vertical rails - offset = ll + vector(-2 * self.supply_rail_pitch, - -2 * self.supply_rail_pitch) - left_gnd_pin = self.add_layout_pin(text="gnd", - layer="m2", - offset=offset, - width=self.supply_rail_width, - height=height) + if left: + offset = ll + vector(-2*self.supply_rail_pitch, + -2*self.supply_rail_pitch) + self.left_gnd_pin = self.add_layout_pin(text="gnd", + layer=v_layer, + offset=offset, + width=self.supply_rail_width, + height=height + 2 * supply_rail_spacing) - offset = ll + vector(-1 * self.supply_rail_pitch, - -1 * self.supply_rail_pitch) - left_vdd_pin = self.add_layout_pin(text="vdd", - layer="m2", - offset=offset, - width=self.supply_rail_width, - height=height) + offset = ll + vector(-1 * self.supply_rail_pitch, + -1 * self.supply_rail_pitch) + self.left_vdd_pin = self.add_layout_pin(text="vdd", + layer=v_layer, + offset=offset, + width=self.supply_rail_width, + height=height) - # RIGHT vertical rails - offset = vector(ur.x, ll.y) + vector(0, -2 * self.supply_rail_pitch) - right_gnd_pin = self.add_layout_pin(text="gnd", - layer="m2", - offset=offset, - width=self.supply_rail_width, - height=height) + if right: + # RIGHT vertical railsteac a 460 + offset = vector(ur.x, ll.y) + vector(2 * self.supply_rail_pitch - self.supply_rail_width, + -2 * self.supply_rail_pitch) + self.right_gnd_pin = self.add_layout_pin(text="gnd", + layer=v_layer, + offset=offset, + width=self.supply_rail_width, + height=height + 2* supply_rail_spacing) - offset = vector(ur.x, ll.y) + vector(self.supply_rail_pitch, - -1 * self.supply_rail_pitch) - right_vdd_pin = self.add_layout_pin(text="vdd", - layer="m2", - offset=offset, - width=self.supply_rail_width, - height=height) + offset = vector(ur.x, ll.y) + vector(1 * self.supply_rail_pitch - self.supply_rail_width, + -1 * self.supply_rail_pitch) + self.right_vdd_pin = self.add_layout_pin(text="vdd", + layer=v_layer, + offset=offset, + width=self.supply_rail_width, + height=height) - # BOTTOM horizontal rails - offset = ll + vector(-2 * self.supply_rail_pitch, - -2 * self.supply_rail_pitch) - bottom_gnd_pin = self.add_layout_pin(text="gnd", - layer="m1", - offset=offset, - width=width, - height=self.supply_rail_width) + if bottom: + # BOTTOM horizontal rails + offset = ll + vector(-2 * self.supply_rail_pitch, + -2 * self.supply_rail_pitch) + self.bottom_gnd_pin = self.add_layout_pin(text="gnd", + layer=h_layer, + offset=offset, + width=width + 2 * supply_rail_spacing, + height=self.supply_rail_width) - offset = ll + vector(-1 * self.supply_rail_pitch, - -1 * self.supply_rail_pitch) - bottom_vdd_pin = self.add_layout_pin(text="vdd", - layer="m1", - offset=offset, - width=width, - height=self.supply_rail_width) + offset = ll + vector(-1 * self.supply_rail_pitch, + -1 * self.supply_rail_pitch) + self.bottom_vdd_pin = self.add_layout_pin(text="vdd", + layer=h_layer, + offset=offset, + width=width, + height=self.supply_rail_width) + if top: + # TOP horizontal rails + offset = vector(ll.x, ur.y) + vector(-2 * self.supply_rail_pitch, + 2 * self.supply_rail_pitch - self.supply_rail_width) + self.top_gnd_pin = self.add_layout_pin(text="gnd", + layer=h_layer, + offset=offset, + width=width + 2 * supply_rail_spacing, + height=self.supply_rail_width) - # TOP horizontal rails - offset = vector(ll.x, ur.y) + vector(-2 * self.supply_rail_pitch, - 0) - top_gnd_pin = self.add_layout_pin(text="gnd", - layer="m1", - offset=offset, - width=width, - height=self.supply_rail_width) - - offset = vector(ll.x, ur.y) + vector(-1 * self.supply_rail_pitch, - self.supply_rail_pitch) - top_vdd_pin = self.add_layout_pin(text="vdd", - layer="m1", - offset=offset, - width=width, - height=self.supply_rail_width) + offset = vector(ll.x, ur.y) + vector(-1 * self.supply_rail_pitch, + 1 * self.supply_rail_pitch - self.supply_rail_width) + self.top_vdd_pin = self.add_layout_pin(text="vdd", + layer=h_layer, + offset=offset, + width=width, + height=self.supply_rail_width) # Remember these for connecting things in the design - self.left_gnd_x_center = left_gnd_pin.cx() - self.left_vdd_x_center = left_vdd_pin.cx() - self.right_gnd_x_center = right_gnd_pin.cx() - self.right_vdd_x_center = right_vdd_pin.cx() - - self.bottom_gnd_y_center = bottom_gnd_pin.cy() - self.bottom_vdd_y_center = bottom_vdd_pin.cy() - self.top_gnd_y_center = top_gnd_pin.cy() - self.top_vdd_y_center = top_vdd_pin.cy() - - # Find the number of vias for this pitch - self.supply_vias = 1 - while True: - c = factory.create(module_type="contact", - layer_stack=self.m1_stack, - dimensions=(self.supply_vias, self.supply_vias)) - if c.second_layer_width < self.supply_rail_width and c.second_layer_height < self.supply_rail_width: - self.supply_vias += 1 - else: - self.supply_vias -= 1 - break - - via_points = [vector(self.left_gnd_x_center, self.bottom_gnd_y_center), - vector(self.left_gnd_x_center, self.top_gnd_y_center), - vector(self.right_gnd_x_center, self.bottom_gnd_y_center), - vector(self.right_gnd_x_center, self.top_gnd_y_center), - vector(self.left_vdd_x_center, self.bottom_vdd_y_center), - vector(self.left_vdd_x_center, self.top_vdd_y_center), - vector(self.right_vdd_x_center, self.bottom_vdd_y_center), - vector(self.right_vdd_x_center, self.top_vdd_y_center)] + if left: + self.left_gnd_x_center = self.left_gnd_pin.cx() + self.left_vdd_x_center = self.left_vdd_pin.cx() + if right: + self.right_gnd_x_center = self.right_gnd_pin.cx() + self.right_vdd_x_center = self.right_vdd_pin.cx() + if bottom: + self.bottom_gnd_y_center = self.bottom_gnd_pin.cy() + self.bottom_vdd_y_center = self.bottom_vdd_pin.cy() + if top: + self.top_gnd_y_center = self.top_gnd_pin.cy() + self.top_vdd_y_center = self.top_vdd_pin.cy() + via_points = [] + if left and bottom: + via_points.append((self.left_gnd_x_center, self.bottom_gnd_y_center)) + if left and top: + via_points.append(vector(self.left_gnd_x_center, self.top_gnd_y_center)) + if right and bottom: + via_points.append(vector(self.right_gnd_x_center, self.bottom_gnd_y_center)) + if right and top: + via_points.append(vector(self.right_gnd_x_center, self.top_gnd_y_center)) + if left and bottom: + via_points.append(vector(self.left_vdd_x_center, self.bottom_vdd_y_center)) + if left and top: + via_points.append(vector(self.left_vdd_x_center, self.top_vdd_y_center)) + if right and bottom: + via_points.append(vector(self.right_vdd_x_center, self.bottom_vdd_y_center)) + if right and top: + via_points.append((self.right_vdd_x_center, self.top_vdd_y_center)) + for pt in via_points: - self.add_via_center(layers=self.m1_stack, - offset=pt, - size=(self.supply_vias, - self.supply_vias)) - + self.add_via_stack_center(offset=pt, + from_layer=h_layer, + to_layer=v_layer, + min_area=True) def pdf_write(self, pdf_name): """ Display the layout to a PDF file. diff --git a/compiler/base/hierarchy_spice.py b/compiler/base/hierarchy_spice.py index eb2b81d5..cef46f30 100644 --- a/compiler/base/hierarchy_spice.py +++ b/compiler/base/hierarchy_spice.py @@ -50,6 +50,7 @@ class spice(): if not os.path.exists(self.lvs_file): self.lvs_file = self.sp_file + self.valid_signal_types = ["INOUT", "INPUT", "OUTPUT", "BIAS", "POWER", "GROUND"] # Holds subckts/mods for this module self.mods = set() # Holds the pins for this module (in order) @@ -699,7 +700,8 @@ class spice(): def get_instance_connections(self): conns = [] for inst in self.insts: - conns.append(inst.get_connections()) + if "contact" not in inst.name: + conns.append(inst.get_connections()) return conns def is_net_alias(self, known_net, net_alias, mod, exclusion_set): diff --git a/compiler/modules/__init__.py b/compiler/modules/__init__.py index d9b470a6..b3e141f7 100755 --- a/compiler/modules/__init__.py +++ b/compiler/modules/__init__.py @@ -41,7 +41,7 @@ from .local_bitcell_array import * from .nand2_dec import * from .nand3_dec import * from .nand4_dec import * -from .orig_bitcell_array import * +#from .orig_bitcell_array import * from .pand2 import * from .pand3 import * from .pand4 import * diff --git a/compiler/modules/bitcell_array.py b/compiler/modules/bitcell_array.py index 6e3369ab..db349b8a 100644 --- a/compiler/modules/bitcell_array.py +++ b/compiler/modules/bitcell_array.py @@ -10,7 +10,9 @@ from openram.tech import drc, spice from openram.sram_factory import factory from openram import OPTS from .bitcell_base_array import bitcell_base_array - +from .pattern import pattern +from openram.base import geometry, instance +from math import ceil class bitcell_array(bitcell_base_array): """ @@ -42,7 +44,7 @@ class bitcell_array(bitcell_base_array): def create_layout(self): - self.place_array("bit_r{0}_c{1}") + self.place_array() self.add_layout_pins() @@ -57,18 +59,26 @@ class bitcell_array(bitcell_base_array): self.cell = factory.create(module_type=OPTS.bitcell) def create_instances(self): - """ Create the module instances used in this design """ - self.cell_inst = {} - for col in range(self.column_size): - for row in range(self.row_size): - name = "bit_r{0}_c{1}".format(row, col) - self.cell_inst[row, col]=self.add_inst(name=name, - mod=self.cell) - self.connect_inst(self.get_bitcell_pins(row, col)) + self.cell_inst={} + if self.cell.mirror.y: + core_block = [[0 for x in range(2)] for y in range(2)] + core_block[0][0] = geometry.instance("core_0_0", mod=self.cell, is_bitcell=True) + core_block[1][0] = geometry.instance("core_1_0", mod=self.cell, is_bitcell=True, mirror='MX') + core_block[0][1] = geometry.instance("core_0_1", mod=self.cell, is_bitcell=True, mirror='MY') + core_block[1][1] = geometry.instance("core_1_1", mod=self.cell, is_bitcell=True, mirror='XY') + else: + core_block = [[0 for x in range(1)] for y in range(2)] + core_block[0][0] = geometry.instance("core_0_0", mod=self.cell, is_bitcell=True) + core_block[1][0] = geometry.instance("core_1_0", mod=self.cell, is_bitcell=True, mirror='MX') - # If it is a "core" cell, it could be trimmed for sim time - if col>0 and col0 and row0 and col0 and row List[int]: + for row in block: + row_height = row[0].height + for inst in row: + debug.check(row_height == inst.height, "intrablock instances within the same row are different heights") + + for y in range(len(block[0])): + debug.check(all([row[y].width for row in block]), "intrablock instances within the same column are different widths") + + block_width = sum([instance.width for instance in block[0]]) + block_height = sum([row[0].height for row in block]) + + return [block_width, block_height] + + def verify_interblock_dimensions(self) -> None: + """ + Ensure the individual blocks are valid and interblock dimensions are valid + """ + debug.check(len(self.core_block) >= 1, "invalid core_block dimension: core_block rows must be >=1") + debug.check(len(self.core_block[0]) >= 1, "invalid core_block dimension: core_block cols must be >=1") + if self.x_block and self.y_block: + debug.check(self.xy_block is not None, "must have xy_block if both x_block and y_block are provided") + + + (self.core_block_width, self.core_block_height) = self.compute_and_verify_intrablock_dimensions(self.core_block) + if self.x_block: + (self.x_block_width, self.x_block_height) = self.compute_and_verify_intrablock_dimensions(self.x_block) + if self.y_block: + (self.y_block_width, self.y_block_height) = self.compute_and_verify_intrablock_dimensions(self.y_block) + if self.xy_block: + (self.xy_block_width, self.xy_block_height) = self.compute_and_verify_intrablock_dimensions(self.xy_block) + if(self.x_block): + debug.check(self.core_block_width * self.cores_per_x_block == self.x_block_width, "core_block does not align with x_block") + if(self.y_block): + debug.check(self.core_block_height * self.cores_per_y_block == self.y_block_height, "core_block does not aligns with y_block") + if(self.xy_block): + debug.check(self.xy_block_height == self.x_block_height, "xy_block does not align with x_block") + debug.check(self.xy_block_width == self.y_block_width, "xy_block does not align with y_block") + + + def connect_block(self, block: block, col: int, row: int): + for dr in range(len(block)): + row_done = False + for dc in range(len(block[0])): + if(self.bit_rows.count(self.num_rows) <= self.num_cols and self.bit_cols.count(self.num_cols) <= self.num_rows): + inst = block[dr][dc] + if(len(self.bit_rows) <= col + dc): + self.bit_rows.append(0) + if(len(self.bit_cols) <= row + dr): + self.bit_cols.append(0) + if(row_done or self.bit_cols[row+dr] >= self.num_cols): + row_done = True + continue + if((self.bit_rows[col+dc] < self.num_rows) and (self.bit_cols[row+dr] < self.num_cols)): + if(inst.is_bitcell): + self.parent_design.cell_inst[self.bit_rows[col+dc], self.bit_cols[row+dr]] = self.parent_design.add_existing_inst(inst,self.name_template.format(row +dr, col+dc)) + self.parent_design.all_inst[row + dr, col + dc] = self.parent_design.cell_inst[self.bit_rows[col+dc], self.bit_cols[row+dr]] + self.parent_design.connect_inst(self.parent_design.get_bitcell_pins(self.bit_rows[col+dc], self.bit_cols[row+dr])) + self.bit_rows[col+dc] += 1 + self.bit_cols[row+dr] += 1 + + else: + self.parent_design.all_inst[row + dr, col + dc] = self.parent_design.add_existing_inst(inst,self.name_template.format(row +dr, col+dc)) + self.parent_design.connect_inst(self.parent_design.get_strap_pins(self.bit_rows[col+dc], self.bit_cols[row+dr])) + else: + row_done = True + + def connect_array(self) -> None: + + #debug_array = [[None]*12 for _ in range(6)] + row = 0 + col = 0 + for i in range(self.num_cores_y): + for j in range (self.num_cores_x): + self.connect_block(self.core_block, col, row) + col += len(self.core_block[0]) + col = 0 + row += len(self.core_block) + + def connect_array_raw(self) -> None: + for row in range(self.num_rows): + for col in range(self.num_cols): + inst = self.core_block[row][col] + if(len(self.bit_rows) <= col): + self.bit_rows.append(0) + if(len(self.bit_cols) <= row): + self.bit_cols.append(0) + if(inst.is_bitcell): + self.parent_design.cell_inst[self.bit_rows[col], self.bit_cols[row]] = self.parent_design.add_existing_inst(inst,self.name_template.format(row, col)) + self.parent_design.all_inst[row, col] = self.parent_design.cell_inst[self.bit_rows[col], self.bit_cols[row]] + self.parent_design.connect_inst(self.parent_design.get_bitcell_pins(self.bit_rows[col], self.bit_cols[row])) + self.bit_rows[col] += 1 + self.bit_cols[row] += 1 + + else: + self.parent_design.all_inst[row, col] = self.parent_design.add_existing_inst(inst,self.name_template.format(row, col)) + self.parent_design.connect_inst(self.parent_design.get_strap_pins(self.bit_rows[col], self.bit_cols[row])) + + def print_bit_blocck(): + return + + def place_inst(self, inst, offset) -> None: + x = offset[0] + y = offset[1] + if "X" in inst.mirror: + y += inst.height + if "Y" in inst.mirror: + x += inst.width + #print('placing inst {} at {}'.format(inst, offset)) + inst.place((x, y), inst.mirror, inst.rotate) + + def place_array(self): + + (self.row_max, self.col_max) = list(self.parent_design.all_inst.keys())[-1] + y = 0 + for row in range(self.row_max+1): + x = 0 + for col in range(self.col_max+1): + inst = self.parent_design.all_inst[self.row_max - row, col] + self.place_inst(inst, (x, y)) + x += inst.width + y += inst.height + + self.parent_design.width = max([x.rx() for x in self.parent_design.insts]) + self.parent_design.height = max([x.uy() for x in self.parent_design.insts]) + + def append_row_to_block(block, row): + block.append(row) + + def append_block_under_block(base_block, under_block): + base_block = base_block + under_block + + def append_block_right_block(base_block, right_block): + for row in base_block: + row = row + right_block + + def rotate_list(lst, n): + # Loop through the range of n positions to rotate + for i in range(n): + # Remove the last element of the list and insert it at the beginning + lst.insert(0, lst.pop()) + # Return the rotated list + return lst diff --git a/compiler/modules/pgate.py b/compiler/modules/pgate.py index 211584b6..d0afd12e 100644 --- a/compiler/modules/pgate.py +++ b/compiler/modules/pgate.py @@ -156,13 +156,15 @@ class pgate(design): return via - def extend_wells(self): + def extend_wells(self, width=None): """ Extend the n/p wells to cover whole cell """ # This should match the cells in the cell library self.nwell_yoffset = 0.48 * self.height full_height = self.height + 0.5 * self.m1_width + if width == None: + width = self.width + 2 * self.well_extend_active # FIXME: float rounding problem if "nwell" in layer: @@ -173,12 +175,12 @@ class pgate(design): nwell_height = nwell_max_offset - self.nwell_yoffset self.add_rect(layer="nwell", offset=nwell_position, - width=self.width + 2 * self.well_extend_active, + width=width, height=nwell_height) if "vtg" in layer: self.add_rect(layer="vtg", offset=nwell_position, - width=self.width + 2 * self.well_extend_active, + width=width, height=nwell_height) # Start this half a rail width below the cell @@ -189,12 +191,12 @@ class pgate(design): pwell_height = self.nwell_yoffset - pwell_position.y self.add_rect(layer="pwell", offset=pwell_position, - width=self.width + 2 * self.well_extend_active, + width=width, height=pwell_height) if "vtg" in layer: self.add_rect(layer="vtg", offset=pwell_position, - width=self.width + 2 * self.well_extend_active, + width=width, height=pwell_height) if cell_props.pgate.add_implants: diff --git a/compiler/modules/replica_bitcell_array.py b/compiler/modules/replica_bitcell_array.py index 4434ad9b..79704022 100644 --- a/compiler/modules/replica_bitcell_array.py +++ b/compiler/modules/replica_bitcell_array.py @@ -4,6 +4,8 @@ # All rights reserved. # from openram import debug +from openram.base import round_to_grid +from openram.tech import drc from openram.base import vector from openram.base import contact from openram.sram_factory import factory @@ -74,7 +76,7 @@ class replica_bitcell_array(bitcell_base_array): """ Array and dummy/replica columns """ # Bitcell array self.bitcell_array = factory.create(module_type="bitcell_array", - column_offset=1 + len(self.left_rbl), + column_offset=len(self.left_rbl), cols=self.column_size, rows=self.row_size) @@ -87,11 +89,11 @@ class replica_bitcell_array(bitcell_base_array): if port in self.left_rbl: # These go top down starting from the bottom of the bitcell array. replica_bit = self.rbl[0] - port - 1 - column_offset = len(self.left_rbl) + column_offset = 0 elif port in self.right_rbl: # These go bottom up starting from the top of the bitcell array. replica_bit = self.rbl[0] + self.row_size + port - 1 - column_offset = len(self.left_rbl) + self.column_size + 1 + column_offset = len(self.left_rbl) + self.column_size else: continue @@ -102,14 +104,24 @@ class replica_bitcell_array(bitcell_base_array): replica_bit=replica_bit) # Dummy row (for replica wordlines) - self.dummy_row = factory.create(module_type="dummy_array", + self.dummy_rows = {} + + for port in self.all_ports: + if port in self.left_rbl: + row_offset = self.row_size + mirror = row_offset % 2 + 1 + elif port in self.right_rbl: + row_offset = 0 + mirror = 0 + else: + continue + + self.dummy_rows[port] = factory.create(module_type="dummy_array", cols=self.column_size, rows=1, - # cap column + left replica column - # FIXME: these col offsets should really start at 0 because - # this is the left edge of the array... but changing them all is work - column_offset=1 + len(self.left_rbl), - mirror=0) + row_offset=row_offset, + column_offset=len(self.left_rbl), + mirror=mirror) def add_pins(self): @@ -213,7 +225,7 @@ class replica_bitcell_array(bitcell_base_array): for port in self.all_ports: # TODO: tie to self.rbl or whatever if self.rbl[port] != 0: self.dummy_row_replica_insts.append(self.add_inst(name="dummy_row_{}".format(port), - mod=self.dummy_row)) + mod=self.dummy_rows[port])) self.connect_inst(self.all_bitline_names + self.rbl_wordline_names[port] + self.supplies) else: self.dummy_row_replica_insts.append(None) @@ -225,28 +237,28 @@ class replica_bitcell_array(bitcell_base_array): 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])) - # 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 main array - self.bitcell_array_inst.place(offset=0) + self.bitcell_array_inst.place(offset=(0,0)) self.add_replica_columns() # 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(-len(self.left_rbl), -self.rbl[0]) - self.translate_all(array_offset) + + #rbc_width = (self.replica_col_insts[0].width, 0) + #dummy_height = max(x for x in map(lambda x: x if x != None else 0, self.)) + #array_offset = self.bitcell_offset.scale(-len(self.left_rbl), -self.rbl[0]) + ll=vector(min([x.lx() for x in self.insts]),min([y.by() for y in self.insts])) + + self.translate_all(ll) self.add_layout_pins() self.route_supplies() - - self.height = (sum(self.rbl) + self.row_size) * self.cell.height - self.width = (len(self.rbls) + self.column_size) * self.cell.width + + self.width = max([x.rx() for x in self.insts]) - min([x.lx() for x in self.insts]) + self.height = max([x.uy() for x in self.insts]) - min([y.by() for y in self.insts]) self.add_boundary() @@ -280,25 +292,24 @@ 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]) + rbc_dimension = vector(self.replica_col_insts[port].width, self.cell.height) + offset = rbc_dimension.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]) + offset = self.bitcell_array_inst.lr() + rbc_dimension.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.dummy_row_replica_insts[bit].place(offset=dummy_offset, - mirror="MX" if (-self.rbl[0] + bit) % 2 else "R0") + dummy_offset = self.bitcell_array_inst.ll() - vector(0, self.dummy_row_replica_insts[bit].height) + self.dummy_row_replica_insts[bit].place(offset=dummy_offset) # These grow up, away from the array for bit in range(self.rbl[1]): - dummy_offset = self.bitcell_offset.scale(0, bit + bit % 2) + self.bitcell_array_inst.ul() - self.dummy_row_replica_insts[self.rbl[0] + bit].place(offset=dummy_offset, - mirror="MX" if (self.row_size + bit) % 2 else "R0") + dummy_offset = self.bitcell_array_inst.ul() + self.dummy_row_replica_insts[self.rbl[0] + bit].place(offset=dummy_offset) def add_layout_pins(self): """ Add the layout pins """ @@ -316,14 +327,15 @@ class replica_bitcell_array(bitcell_base_array): # Replica wordlines (go by the row instead of replica column because we may have to add a pin # 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()): - pin = inst.get_pin(pin_name) - self.add_layout_pin(text=wl_name, - layer=pin.layer, - offset=pin.ll().scale(0, 1), - width=self.width, - height=pin.height()) + if self.rbl != [0,0]: + for (names, inst) in zip(self.rbl_wordline_names, self.dummy_row_replica_insts): + for (wl_name, pin_name) in zip(names, self.dummy_rows[0].get_wordline_names()): + pin = inst.get_pin(pin_name) + self.add_layout_pin(text=wl_name, + layer=pin.layer, + offset=pin.ll().scale(0, 1), + width=self.width, + height=pin.height()) # Main array bl/br for pin_name in self.all_bitline_names: @@ -347,12 +359,6 @@ class replica_bitcell_array(bitcell_base_array): width=pin.width(), height=self.height) - def route_supplies(self): - """ just copy supply pins from all instances """ - 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.""" # Dynamic Power from Bitline diff --git a/compiler/modules/replica_column.py b/compiler/modules/replica_column.py index d551a9cc..6521fe5b 100644 --- a/compiler/modules/replica_column.py +++ b/compiler/modules/replica_column.py @@ -9,7 +9,8 @@ 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 - +from openram.base import geometry +from openram.modules import pattern class replica_column(bitcell_base_array): """ @@ -44,10 +45,10 @@ class replica_column(bitcell_base_array): 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" - + " (left_rbl must be odd) for LVS.") + #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" + # + " (left_rbl must be odd) for LVS.") self.create_netlist() if not OPTS.netlist_only: @@ -59,10 +60,7 @@ class replica_column(bitcell_base_array): self.create_instances() def create_layout(self): - self.place_instances() - - self.height = self.cell_inst[-1].uy() - self.width = self.cell_inst[0].rx() + self.place_array() self.add_layout_pins() @@ -88,103 +86,35 @@ class replica_column(bitcell_base_array): self.dummy_cell = factory.create(module_type=OPTS.dummy_bitcell) def create_instances(self): - self.cell_inst = [] + self.cell_inst = {} + core_block = [[0 for x in range(1)] for y in range(self.total_size)] + current_row = self.row_start for row in range(self.total_size): - 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. # 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(row, 0)) + if current_row % 2 == 0: + core_block[row][0] = geometry.instance("rbc_{}".format(row), mod=self.replica_cell, is_bitcell=True) + else: + core_block[row][0] = geometry.instance("rbc_{}".format(row), mod=self.replica_cell, is_bitcell=True, mirror='MX') else: - self.cell_inst.append(self.add_inst(name=name, - mod=self.dummy_cell)) - self.connect_inst(self.get_bitcell_pins(row, 0)) + if current_row % 2 == 0: + core_block[row][0] = geometry.instance("rbc_{}".format(row), mod=self.dummy_cell, is_bitcell=True) + else: + core_block[row][0] = geometry.instance("rbc_{}".format(row), mod=self.dummy_cell, is_bitcell=True, mirror='MX') - 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) % 2 - - # if our bitcells are mirrored on the y axis, check if we are in global - # column that needs to be flipped. - dir_y = False - xoffset = 0 - if self.cell.mirror.y and self.column_offset % 2: - dir_y = True - xoffset = self.replica_cell.width - - for row in range(self.total_size): - # name = "bit_r{0}_{1}".format(row, "rbl") - dir_x = self.cell.mirror.x and (row + rbl_offset) % 2 - - offset = vector(xoffset, self.cell.height * (row + (row + rbl_offset) % 2)) - - if dir_x and dir_y: - dir_key = "XY" - elif dir_x: - dir_key = "MX" - elif dir_y: - dir_key = "MY" - else: - dir_key = "" - - self.cell_inst[row].place(offset=offset, - mirror=dir_key) - - def add_layout_pins(self): - 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), - layer=bl_pin.layer, - offset=bl_pin.ll().scale(1, 0), - width=bl_pin.width(), - height=self.height) - bl_pin = self.cell_inst[0].get_pin(self.cell.get_br_name(port)) - self.add_layout_pin(text="br_{0}_{1}".format(port, 0), - layer=bl_pin.layer, - offset=bl_pin.ll().scale(1, 0), - width=bl_pin.width(), - height=self.height) - - for port in self.all_ports: + current_row += 1 + if self.cell.mirror.y: 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), - layer=wl_pin.layer, - offset=wl_pin.ll().scale(0, 1), - width=self.width, - height=wl_pin.height()) - - def route_supplies(self): - - for inst in self.cell_inst: - for pin_name in ["vdd", "gnd"]: - self.copy_layout_pin(inst, pin_name) - - def get_bitline_names(self, port=None): - if port == None: - return self.all_bitline_names - else: - return self.bitline_names[port] - - def get_bitcell_pins(self, row, col): - """ - Creates a list of connections in the bitcell, - indexed by column and row, for instance use in bitcell_array - """ - bitcell_pins = [] - for port in self.all_ports: - bitcell_pins.extend([x for x in self.get_bitline_names(port) if x.endswith("_{0}".format(col))]) - bitcell_pins.extend([x for x in self.all_wordline_names if x.endswith("_{0}".format(row))]) - bitcell_pins.append("vdd") - bitcell_pins.append("gnd") - - return bitcell_pins + if self.column_offset % 2 == 0: + if core_block[row][0].mirror=='MX': + core_block[row][0].mirror='XY' + else: + core_block[row][0].mirror='MY' + self.pattern = pattern(self, "bitcell_array", core_block, num_rows=self.total_size, num_cols=self.column_size, name_template="rbc_r{0}_c{1}") + self.pattern.connect_array() def get_bitcell_pins_col_cap(self, row, col): """ @@ -208,4 +138,4 @@ class replica_column(bitcell_base_array): for row, cell in enumerate(self.cell_inst): if row != self.replica_bit: - self.graph_inst_exclude.add(cell) + self.graph_inst_exclude.add(self.cell_inst[cell]) diff --git a/compiler/modules/row_cap_array.py b/compiler/modules/row_cap_array.py index 606c35ff..c68034fe 100644 --- a/compiler/modules/row_cap_array.py +++ b/compiler/modules/row_cap_array.py @@ -12,10 +12,11 @@ class row_cap_array(bitcell_base_array): """ Generate a dummy row/column for the replica array. """ - def __init__(self, rows, cols, column_offset=0, mirror=0, name=""): + def __init__(self, rows, cols, column_offset=0, mirror=0, location="", name=""): super().__init__(rows=rows, cols=cols, column_offset=column_offset, name=name) self.mirror = mirror - self.no_instances = True + self.location = location + #self.no_instances = True self.create_netlist() if not OPTS.netlist_only: self.create_layout() @@ -69,44 +70,21 @@ class row_cap_array(bitcell_base_array): return bitcell_pins - def place_array(self, name_template, row_offset=0): - xoffset = 0.0 - for col in range(self.column_size): - yoffset = self.cell.height - tempx, dir_y = self._adjust_x_offset(xoffset, col, self.column_offset) - - for row in range(self.row_size): - tempy, dir_x = self._adjust_y_offset(yoffset, row + 1, row_offset) - - if dir_x and dir_y: - dir_key = "XY" - elif dir_x: - dir_key = "MX" - elif dir_y: - dir_key = "MY" - else: - dir_key = "" - - self.cell_inst[row, col].place(offset=[tempx, tempy], - mirror=dir_key) - yoffset += self.cell.height - xoffset += self.cell.width - def add_layout_pins(self): """ Add the layout pins """ - row_list = self.cell.get_all_wl_names() - - for row in range(1, self.row_size - 1): - for cell_row in row_list: - wl_pin = self.cell_inst[row, 0].get_pin(cell_row) - self.add_layout_pin(text=cell_row + "_{0}".format(row), + wl_names = self.cell.get_all_wl_names() + max_row = self.row_size - 2 + for row in range(0, max_row): + for port in self.all_ports: + wl_pin = self.cell_inst[max_row - 1 - row, 0].get_pin(wl_names[port]) + 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, height=wl_pin.height()) - for row in range(1, self.row_size - 1): + for row in range(0, max_row): for col in range(self.column_size): inst = self.cell_inst[row, col] for pin_name in ["vdd", "gnd"]: diff --git a/compiler/modules/sram_1bank.py b/compiler/modules/sram_1bank.py index 7d1aec3f..cfa0b99b 100644 --- a/compiler/modules/sram_1bank.py +++ b/compiler/modules/sram_1bank.py @@ -662,7 +662,10 @@ class sram_1bank(design, verilog, lef): inputs = [] outputs = [] for bit in range(self.num_spare_cols): - inputs.append("spare_wen{}[{}]".format(port, bit)) + if self.num_spare_cols == 1: + inputs.append("spare_wen{}".format(port)) + else: + inputs.append("spare_wen{}[{}]".format(port, bit)) outputs.append("bank_spare_wen{}_{}".format(port, bit)) self.connect_inst(inputs + outputs + ["clk_buf{}".format(port)] + self.ext_supplies) diff --git a/compiler/modules/write_mask_and_array.py b/compiler/modules/write_mask_and_array.py index 522baa06..3d0f680b 100644 --- a/compiler/modules/write_mask_and_array.py +++ b/compiler/modules/write_mask_and_array.py @@ -11,6 +11,7 @@ from openram.base import design from openram.base import vector from openram.sram_factory import factory from openram import OPTS +from openram.tech import drc class write_mask_and_array(design): @@ -104,6 +105,10 @@ class write_mask_and_array(design): base = vector(self.offsets[int(i * write_bits)], 0) self.and2_insts[i].place(base) + # decide whethre to connect nwell to avoid drc errors + if self.bitcell.width < drc("nwell_to_nwell"): + self.and2.extend_wells(width=self.and2.width + drc("nwell_to_nwell")) + def add_layout_pins(self): # Create the enable pin that connects all write mask AND array's B pins diff --git a/compiler/tests/22_sram_1bank_nomux_func_test.py b/compiler/tests/22_sram_1bank_nomux_func_test.py index 364605ac..011af010 100755 --- a/compiler/tests/22_sram_1bank_nomux_func_test.py +++ b/compiler/tests/22_sram_1bank_nomux_func_test.py @@ -25,7 +25,7 @@ class sram_1bank_nomux_func_test(openram_test): OPTS.analytical_delay = False OPTS.netlist_only = True OPTS.trim_netlist = False - + OPTS.num_sim_threads = 1 # This is a hack to reload the characterizer __init__ with the spice version from importlib import reload from openram import characterizer diff --git a/compiler/tests/Makefile b/compiler/tests/Makefile index ccca67b1..fc27a521 100644 --- a/compiler/tests/Makefile +++ b/compiler/tests/Makefile @@ -1,3 +1,4 @@ +SHELL := /bin/bash TOP_DIR := $(realpath $(dir $(lastword $(MAKEFILE_LIST)))../..) include $(TOP_DIR)/openram.mk diff --git a/install_conda.sh b/install_conda.sh index 6b37c720..6aadd393 100755 --- a/install_conda.sh +++ b/install_conda.sh @@ -7,8 +7,8 @@ CONDA_HOME="${CONDA_HOME:-miniconda}" # If you want to use the latest version, just use "". TOOLS="" TOOLS+="klayout=0.28.3 " -TOOLS+="magic=8.3.363 " -TOOLS+="netgen=1.5.253 " +TOOLS+="magic=8.3.497 " +TOOLS+="netgen=1.5.286 " TOOLS+="ngspice=26 " TOOLS+="trilinos=12.12.1=1 " TOOLS+="xyce=7.4=3" diff --git a/macros/Makefile b/macros/Makefile index 6d6429e4..04035916 100644 --- a/macros/Makefile +++ b/macros/Makefile @@ -65,6 +65,9 @@ all: | configs example: $(EXAMPLE_STAMPS) .PHONY: example +sp: sky130_sram_1kbyte_1rw_32x256_8 sky130_sram_2kbyte_1rw_32x512_8 sky130_sram_4kbyte_1rw_32x1024_8 sky130_sram_4kbyte_1rw_64x512_8 +.PHONY: sp + sky130: $(SKY130_STAMPS) .PHONY: sky130 diff --git a/technology/sky130/custom/replica_bitcell_array.py b/technology/sky130/custom/replica_bitcell_array.py deleted file mode 100644 index 2b1ab54a..00000000 --- a/technology/sky130/custom/replica_bitcell_array.py +++ /dev/null @@ -1,731 +0,0 @@ -# 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 openram.modules import bitcell_base_array - - -class replica_bitcell_array(bitcell_base_array): - """ - 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). - """ - 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(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 - 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 - # This could be an empty list - if left_rbl != None: - self.left_rbl = left_rbl - else: - self.left_rbl = [0] - # This could be an empty list - if right_rbl != None: - self.right_rbl = right_rbl - else: - self.right_rbl=[1] if len(self.all_ports) > 1 else [] - 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() - 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) - """ - # Bitcell array - self.bitcell_array = factory.create(module_type="bitcell_array", - column_offset=1 + len(self.left_rbl), - cols=self.column_size, - rows=self.row_size) - - # Replica bitlines - self.replica_columns = {} - - for port in self.all_ports: - if port in self.left_rbl: - # 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] - - 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 - else: - continue - - self.replica_columns[port] = factory.create(module_type="replica_column", - rows=self.row_size, - rbl=self.rbl, - column_offset=column_offset, - replica_bit=replica_bit) - - # Dummy row - self.dummy_row = factory.create(module_type="dummy_array", - cols=self.column_size, - rows=1, - # dummy column + left replica column - 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: - # 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): - # The bit is which port the RBL is for - for bit in self.rbls: - for port in self.all_ports: - self.rbl_bitline_names[bit].append("rbl_bl_{0}_{1}".format(port, bit)) - for port in self.all_ports: - self.rbl_bitline_names[bit].append("rbl_br_{0}_{1}".format(port, bit)) - # Make a flat list too - self.all_rbl_bitline_names = [x for sl in self.rbl_bitline_names for x in sl] - - self.bitline_names = self.bitcell_array.bitline_names - # Make a flat list too - self.all_bitline_names = [x for sl in zip(*self.bitline_names) for x in sl] - - 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") - - def add_wordline_pins(self): - - # Wordlines to ground - self.gnd_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.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())) - 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) - 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())) - - 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") - for port in range(self.rbl[0], self.rbl[0] + self.rbl[1]): - self.add_pin(self.rbl_wordline_names[port][port], "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) - self.connect_inst(self.all_bitline_names + self.all_wordline_names + self.supplies) - - # Replica columns - self.replica_col_insts = [] - for port in self.all_ports: - 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) - else: - self.replica_col_insts.append(None) - - # Dummy rows under the bitcell array (connected with 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: - 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) - - 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 main array - self.bitcell_array_inst.place(offset=self.unused_offset) - - 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]) - 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.add_boundary() - - self.DRC_LVS() - - def get_main_array_top(self): - """ Return the top of the main bitcell array. """ - return self.bitcell_array_inst.uy() - - def get_main_array_bottom(self): - """ Return the bottom of the main bitcell array. """ - return self.bitcell_array_inst.by() - - def get_main_array_left(self): - """ Return the left of the main bitcell array. """ - return self.bitcell_array_inst.lx() - - def get_main_array_right(self): - """ 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 - """ - offsets = [x + self.bitcell_array_inst.lx() for x in self.bitcell_array.get_column_offsets()] - return offsets - - def add_replica_columns(self): - """ Add replica columns on left and right of 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 - 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) - 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 - 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 - for bit in range(self.rbl[1]): - dummy_offset = self.bitcell_offset.scale(0, bit + bit % 2) + self.bitcell_array_inst.ul() - 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 - for pin_name in self.all_wordline_names: - pin_list = self.bitcell_array_inst.get_pins(pin_name) - for pin in pin_list: - self.add_layout_pin(text=pin_name, - layer=pin.layer, - offset=pin.ll().scale(0, 1), - width=self.width, - height=pin.height()) - - # Replica wordlines (go by the row instead of replica column because we may have to add a pin - # 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, - offset=pin.ll().scale(0, 1), - width=self.width, - height=pin.height()) - - for pin_name in self.all_bitline_names: - pin_list = self.bitcell_array_inst.get_pins(pin_name) - for pin in pin_list: - self.add_layout_pin(text=pin_name, - layer=pin.layer, - offset=pin.ll().scale(1, 0), - width=pin.width(), - height=self.height) - - # Replica bitlines - if len(self.rbls) > 0: - for (names, inst) in zip(self.rbl_bitline_names, self.replica_col_insts): - pin_names = self.replica_columns[self.rbls[0]].all_bitline_names - for (bl_name, pin_name) in zip(names, pin_names): - pin = inst.get_pin(pin_name) - self.add_layout_pin(text=bl_name, - layer=pin.layer, - offset=pin.ll().scale(1, 0), - width=pin.width(), - height=self.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 (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]) - - 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.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. - """ - - for port in self.left_rbl + self.right_rbl: - self.replica_columns[port].exclude_all_but_replica() - - 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.init_graph_params() diff --git a/technology/sky130/custom/sky130_bitcell.py b/technology/sky130/custom/sky130_bitcell.py index 21fc7264..f99c37c8 100644 --- a/technology/sky130/custom/sky130_bitcell.py +++ b/technology/sky130/custom/sky130_bitcell.py @@ -6,7 +6,7 @@ # from openram import debug -from openram.modules import bitcell_base +from openram.modules.bitcell_base import bitcell_base from openram.tech import cell_properties as props diff --git a/technology/sky130/custom/sky130_bitcell_array.py b/technology/sky130/custom/sky130_bitcell_array.py index e5a1704b..daf6f899 100644 --- a/technology/sky130/custom/sky130_bitcell_array.py +++ b/technology/sky130/custom/sky130_bitcell_array.py @@ -6,11 +6,13 @@ # from openram import debug -from openram.modules import bitcell_array +from openram.modules.bitcell_array import bitcell_array +from openram.modules import pattern from openram.sram_factory import factory +from openram.base import geometry from openram import OPTS from .sky130_bitcell_base_array import sky130_bitcell_base_array - +from math import ceil class sky130_bitcell_array(bitcell_array, sky130_bitcell_base_array): """ @@ -18,76 +20,37 @@ class sky130_bitcell_array(bitcell_array, sky130_bitcell_base_array): Assumes bit-lines and word lines are connected by abutment. """ def __init__(self, rows, cols, column_offset=0, name=""): - # Don't call the regular bitcell_array constructor since we don't want its constructor, just - # some of it's useful member functions - sky130_bitcell_base_array.__init__(self, rows=rows, cols=cols, column_offset=column_offset, name=name) - if self.row_size % 2 == 0: - debug.error("Invalid number of rows {}. number of rows (excluding dummy rows) must be odd to connect to col ends".format(self.row_size), -1) - debug.info(1, "Creating {0} {1} x {2}".format(self.name, self.row_size, self.column_size)) - self.add_comment("rows: {0} cols: {1}".format(self.row_size, self.column_size)) - - # This will create a default set of bitline/wordline names - self.create_all_bitline_names() - self.create_all_wordline_names() - self.create_netlist() - if not OPTS.netlist_only: - self.create_layout() - self.add_supply_pins() + super().__init__(rows=rows, cols=cols, column_offset=column_offset, name=name) def add_modules(self): """ Add the modules used in this design """ # Bitcell for port names only self.cell = factory.create(module_type=OPTS.bitcell, version="opt1") - self.cell2 = factory.create(module_type=OPTS.bitcell, version="opt1a") + self.cella = factory.create(module_type=OPTS.bitcell, version="opt1a") self.strap = factory.create(module_type="internal", version="wlstrap") - self.strap2 = factory.create(module_type="internal", version="wlstrap_p") - self.strap3 = factory.create(module_type="internal", version="wlstrapa") - self.strap4 = factory.create(module_type="internal", version="wlstrapa_p") + self.strap_p = factory.create(module_type="internal", version="wlstrap_p") + self.strapa = factory.create(module_type="internal", version="wlstrapa") + self.strapa_p = factory.create(module_type="internal", version="wlstrapa_p") def create_instances(self): """ Create the module instances used in this design """ - self.cell_inst = {} - self.array_layout = [] - alternate_bitcell = (self.row_size) % 2 - for row in range(0, self.row_size): + self.all_inst={} + self.cell_inst={} - row_layout = [] + bit_row_opt1 = [geometry.instance("00_opt1", mod=self.cell, is_bitcell=True, mirror='XY')] \ + + [geometry.instance("01_strap_p", mod=self.strap_p, is_bitcell=False, mirror='MX')]\ + + [geometry.instance("02_opt1", mod=self.cell, is_bitcell=True, mirror='MX')] \ + + [geometry.instance("03_strap", mod=self.strap, is_bitcell=False, mirror='MX')] + + bit_row_opt1a = [geometry.instance("10_opt1a", mod=self.cella, is_bitcell=True, mirror='MY')] \ + + [geometry.instance("11_strap_p", mod=self.strap_p, is_bitcell=False)] \ + + [geometry.instance("12_opt1a", mod=self.cella, is_bitcell=True)] \ + + [geometry.instance("13_strapa", mod=self.strapa, is_bitcell=False)] - alternate_strap = (self.row_size+1) % 2 - for col in range(0, self.column_size): - if alternate_bitcell == 1: - row_layout.append(self.cell) - self.cell_inst[row, col]=self.add_inst(name="row_{}_col_{}_bitcell".format(row, col), - mod=self.cell) - else: - row_layout.append(self.cell2) - self.cell_inst[row, col]=self.add_inst(name="row_{}_col_{}_bitcell".format(row, col), - mod=self.cell2) - self.connect_inst(self.get_bitcell_pins(row, col)) - if col != self.column_size - 1: - if alternate_strap: - if row % 2: - name="row_{}_col_{}_wlstrapa_p".format(row, col) - row_layout.append(self.strap4) - self.add_inst(name=name, mod=self.strap4) - else: - name="row_{}_col_{}_wlstrap_p".format(row, col) - row_layout.append(self.strap2) - self.add_inst(name=name, mod=self.strap2) - alternate_strap = 0 - else: - if row % 2: - name="row_{}_col_{}_wlstrapa".format(row, col) - row_layout.append(self.strap3) - self.add_inst(name=name.format(row, col), mod=self.strap3) - else: - name="row_{}_col_{}_wlstrap".format(row, col) - row_layout.append(self.strap) - self.add_inst(name=name.format(row, col), mod=self.strap) - alternate_strap = 1 - self.connect_inst(self.get_strap_pins(row, col, name)) - if alternate_bitcell == 0: - alternate_bitcell = 1 - else: - alternate_bitcell = 0 - self.array_layout.append(row_layout) + bit_block = [] + pattern.append_row_to_block(bit_block, bit_row_opt1) + pattern.append_row_to_block(bit_block, bit_row_opt1a) + for row in bit_block: + row = pattern.rotate_list(row, self.column_offset * 2) + self.pattern = pattern(self, "bitcell_array", bit_block, num_rows=self.row_size, num_cols=self.column_size, num_cores_x=ceil(self.column_size/2), num_cores_y=ceil(self.row_size/2), name_template="bit_r{0}_c{1}") + self.pattern.connect_array() diff --git a/technology/sky130/custom/sky130_bitcell_base_array.py b/technology/sky130/custom/sky130_bitcell_base_array.py index 9263f8dd..292ff39d 100644 --- a/technology/sky130/custom/sky130_bitcell_base_array.py +++ b/technology/sky130/custom/sky130_bitcell_base_array.py @@ -11,7 +11,7 @@ from openram.modules import bitcell_base_array from openram.sram_factory import factory from openram.tech import layer from openram import OPTS - +from openram.modules import pattern class sky130_bitcell_base_array(bitcell_base_array): """ @@ -23,44 +23,6 @@ class sky130_bitcell_base_array(bitcell_base_array): self.cell = factory.create(module_type=OPTS.bitcell, version="opt1") - def place_array(self, name_template, row_offset=0, col_offset=0): - yoffset = 0.0 - - for row in range(0, len(self.array_layout)): - xoffset = 0.0 - for col in range(0, len(self.array_layout[row])): - self.place_inst = self.insts[(col) + (row) * len(self.array_layout[row])] - - if row % 2 == 0: - if col == 0: - self.place_inst.place(offset=[xoffset, yoffset + self.cell.height], mirror="MX") - elif col % 4 == 0: - self.place_inst.place(offset=[xoffset, yoffset + self.cell.height], mirror="MX") - elif col % 4 == 3 : - self.place_inst.place(offset=[xoffset, yoffset + self.cell.height], mirror="MX") - elif col % 4 == 2: - self.place_inst.place(offset=[xoffset + self.cell.width, yoffset + self.cell.height], mirror="XY") - else: - self.place_inst.place(offset=[xoffset, yoffset + self.cell.height], mirror="MX") - else: - if col == 0: - self.place_inst.place(offset=[xoffset, yoffset]) - elif col % 4 == 0: - self.place_inst.place(offset=[xoffset, yoffset]) - elif col % 4 == 3 : - self.place_inst.place(offset=[xoffset, yoffset]) - elif col % 4 == 2: - self.place_inst.place(offset=[xoffset + self.cell.width, yoffset], mirror="MY") - # self.place_inst.place(offset=[xoffset, yoffset]) - else: - self.place_inst.place(offset=[xoffset, yoffset]) - - xoffset += self.place_inst.width - yoffset += self.place_inst.height - - self.width = max([x.rx() for x in self.insts]) - self.height = max([x.uy() for x in self.insts]) - def get_bitcell_pins(self, row, col, swap = False): """ Creates a list of connections in the bitcell, @@ -122,66 +84,36 @@ class sky130_bitcell_base_array(bitcell_base_array): strap_pins = ["vdd", "gnd", "vdd"] return strap_pins - def add_supply_pins(self): - """ Add the layout pins """ + def route_supplies(self): # Copy a vdd/gnd layout pin from every cell - + super().route_supplies() for inst in self.insts: - if "wlstrap" in inst.name: - if "VPWR" in inst.mod.pins: - self.copy_layout_pin(inst, "VPWR", "vdd") - if "VGND" in inst.mod.pins: - self.copy_layout_pin(inst, "VGND", "gnd") + if "VPWR" in inst.mod.pins: + self.copy_layout_pin(inst, "VPWR", "vdd") + if "VGND" in inst.mod.pins: + self.copy_layout_pin(inst, "VGND", "gnd") - for row in range(self.row_size): - for col in range(self.column_size): - inst = self.cell_inst[row, col] - for pin_name in ["vdd", "gnd"]: - self.copy_layout_pin(inst, pin_name) + if hasattr(self, 'cell_inst'): + for col in range(self.column_size): + inst = self.cell_inst[0,col] + pin = inst.get_pin("vpb") + self.objs.append(geometry.rectangle(layer["nwell"], + pin.ll(), + pin.width(), + pin.height())) + self.objs.append(geometry.label("vdd", layer["nwell"], pin.center())) + + try: + from openram.tech import layer_override + if layer_override['VNB']: + pin = inst.get_pin("vnb") + self.objs.append(geometry.label("gnd", layer["pwellp"], pin.center())) + self.objs.append(geometry.rectangle(layer["pwellp"], + pin.ll(), + pin.width(), + pin.height())) + except: + pin = inst.get_pin("vnb") + self.add_label("vdd", pin.layer, pin.center()) - if row == 2: #add only 1 label per col - if 'VPB' or 'vpb' in self.cell_inst[row, col].mod.pins: - pin = inst.get_pin("vpb") - self.objs.append(geometry.rectangle(layer["nwell"], - pin.ll(), - pin.width(), - pin.height())) - self.objs.append(geometry.label("vdd", layer["nwell"], pin.center())) - - if 'VNB' or 'vnb'in self.cell_inst[row, col].mod.pins: - try: - from openram.tech import layer_override - if layer_override['VNB']: - pin = inst.get_pin("vnb") - self.objs.append(geometry.label("gnd", layer["pwellp"], pin.center())) - self.objs.append(geometry.rectangle(layer["pwellp"], - pin.ll(), - pin.width(), - pin.height())) - except: - pin = inst.get_pin("vnb") - self.add_label("vdd", pin.layer, pin.center()) - - def add_bitline_pins(self): - bitline_names = self.cell.get_all_bitline_names() - for col in range(self.column_size): - for port in self.all_ports: - bl_pin = self.cell_inst[0, col].get_pin(bitline_names[2 * port]) - text = "bl_{0}_{1}".format(port, col) - #if "Y" in self.cell_inst[0, col].mirror: - # text = text.replace("bl", "br") - self.add_layout_pin(text=text, - layer=bl_pin.layer, - offset=bl_pin.ll().scale(1, 0), - width=bl_pin.width(), - height=self.height) - br_pin = self.cell_inst[0, col].get_pin(bitline_names[2 * port + 1]) - text = "br_{0}_{1}".format(port, col) - #if "Y" in self.cell_inst[0, col].mirror: - # text = text.replace("br", "bl") - self.add_layout_pin(text=text, - layer=br_pin.layer, - offset=br_pin.ll().scale(1, 0), - width=br_pin.width(), - height=self.height) diff --git a/technology/sky130/custom/sky130_capped_replica_bitcell_array.py b/technology/sky130/custom/sky130_capped_replica_bitcell_array.py index ab79fce3..b19069cb 100644 --- a/technology/sky130/custom/sky130_capped_replica_bitcell_array.py +++ b/technology/sky130/custom/sky130_capped_replica_bitcell_array.py @@ -7,213 +7,194 @@ from openram import debug from openram.base import vector from openram.base import contact +from openram import debug +from openram.base import round_to_grid 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 openram.modules.capped_replica_bitcell_array import capped_replica_bitcell_array from .sky130_bitcell_base_array import sky130_bitcell_base_array +from math import sqrt -class sky130_capped_replica_bitcell_array(sky130_bitcell_base_array): +class sky130_capped_replica_bitcell_array(capped_replica_bitcell_array, sky130_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)) + super().__init__(rows, cols, rbl, left_rbl, right_rbl, name) + + def add_layout_pins(self): + """ Add the layout pins """ - # 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 = [] + for row_end in self.dummy_col_insts: + row_end = row_end.mod + for (rba_wl_name, wl_name) in zip(self.get_all_wordline_names(), row_end.get_wordline_names()): + pin = row_end.get_pin(wl_name) + self.add_layout_pin(text=rba_wl_name, + layer=pin.layer, + offset=vector(0,pin.ll().scale(0, 1)[1]), + #width=self.width, + width=pin.width(), + height=pin.height()) - self.create_netlist() - if not OPTS.netlist_only: - self.create_layout() + pin_height = (round_to_grid(drc["minarea_m3"] / round_to_grid(sqrt(drc["minarea_m3"]))) + drc["{0}_to_{0}".format('m3')]) + drc_width = drc["{0}_to_{0}".format('m3')] - def create_netlist(self): - """ Create and connect the netlist """ - self.add_modules() - self.add_pins() - self.create_instances() + # vdd/gnd are only connected in the perimeter cells + # replica column should only have a vdd/gnd in the dummy cell on top/bottom + supply_insts = self.dummy_row_insts - def add_modules(self): - 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) - - 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): - 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 - - self.bitline_pins = [] - - for port in self.left_rbl: - self.bitline_pins.extend(self.rbl_bitline_names[port]) - self.bitline_pins.extend(self.all_bitline_names) - for port in self.right_rbl: - self.bitline_pins.extend(self.rbl_bitline_names[port]) - - self.add_pin_list(self.bitline_pins, "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.wordline_pins = [] - - for port in range(self.rbl[0]): - self.wordline_pins.append(self.rbl_wordline_names[port][port]) - self.wordline_pins.extend(self.all_wordline_names) - for port in range(self.rbl[0], self.rbl[0] + self.rbl[1]): - self.wordline_pins.append(self.rbl_wordline_names[port][port]) - - self.add_pin_list(self.wordline_pins, "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_pins + self.wordline_pins + self.supplies) - - def create_layout(self): - - self.replica_bitcell_array_inst.place(offset=0) - - self.width = self.replica_bitcell_array.width - self.height = self.replica_bitcell_array.height - - for pin_name in self.bitline_pins + self.wordline_pins + self.supplies: - self.copy_layout_pin(self.replica_bitcell_array_inst, pin_name) - - self.add_boundary() - - self.DRC_LVS() - - def get_main_array_top(self): - return self.replica_bitcell_array.get_main_array_top() - - def get_main_array_bottom(self): - return self.replica_bitcell_array.get_main_array_bottom() - - def get_main_array_left(self): - return self.replica_bitcell_array.get_main_array_left() - - def get_main_array_right(self): - return self.replica_bitcell_array.get_main_array_right() - - def get_replica_top(self): - return self.replica_bitcell_array.get_replica_top() - - def get_replica_bottom(self): - return self.replica_bitcell_array.get_replica_bottom() - - def get_replica_left(self): - return self.replica_bitcell_array.get_replica_left() - - def get_replica_right(self): - return self.replica_bitcell_array.get_replica_right() + for pin_name in self.supplies: + for supply_inst in supply_insts: + vdd_alternate = 0 + gnd_alternate = 0 + for cell_inst in supply_inst.mod.insts: + inst = cell_inst.mod + for pin in inst.get_pins(pin_name): + if pin.name == 'vdd': + if vdd_alternate: + connection_offset = -0.02 + vdd_alternate = 0 + else: + connection_offset = 0.02 + vdd_alternate = 1 + connection_width = drc["minwidth_{}".format('m1')] + track_offset = 1 + elif pin.name == 'gnd': + if gnd_alternate: + connection_offset = 0.00 + gnd_alternate = 0 + else: + connection_offset = 0.00 + gnd_alternate = 1 + connection_width = drc["minwidth_{}".format('m1')] + track_offset = 4 + pin_width = round_to_grid(sqrt(drc["minarea_m3"])) + pin_height = round_to_grid(drc["minarea_m3"] / pin_width) + if inst.cell_name == 'sky130_fd_bd_sram__sram_sp_colend_p_cent' or inst.cell_name == 'sky130_fd_bd_sram__sram_sp_colenda_p_cent' or inst.cell_name == 'sky130_fd_bd_sram__sram_sp_colend_cent' or inst.cell_name == 'sky130_fd_bd_sram__sram_sp_colenda_cent' or 'corner' in inst.cell_name: + if 'dummy_row_bot' in supply_inst.name: + pin_center = vector(pin.center()[0], -1 * track_offset * (pin_height + drc_width*2)) + self.add_segment_center(pin.layer, pin_center+supply_inst.ll()+cell_inst.ll()+vector(connection_offset,0), vector((pin_center+supply_inst.ll()+cell_inst.ll())[0] + connection_offset, 0), connection_width) + elif 'dummy_row_top' in supply_inst.name: + pin_center = vector(pin.center()[0],inst.height + 1 * track_offset* (pin_height + drc_width*2)) + self.add_segment_center(pin.layer, pin_center+supply_inst.ll()+cell_inst.ll()+vector(connection_offset,0), vector((pin_center+supply_inst.ll()+cell_inst.ll())[0] + connection_offset, self.height), connection_width) + # elif 'replica_col' in supply_inst.name and cell_inst.mirror == 'MX': + # pin_center = vector(pin.center()[0], -1 * track_offset* (pin_height + drc_width*2)) + # self.add_segment_center(pin.layer, pin_center+supply_inst.ll()+cell_inst.ll()+vector(connection_offset,0), vector((pin_center+supply_inst.ll()+cell_inst.ll())[0] + connection_offset, 0), connection_width) + # elif 'replica_col' in supply_inst.name: + # pin_center = vector(pin.center()[0],inst.height + 1 * track_offset * (pin_height + drc_width*2)) + # self.add_segment_center(pin.layer, pin_center+supply_inst.ll()+cell_inst.ll()+vector(connection_offset,0), vector((pin_center+supply_inst.ll()+cell_inst.ll())[0] + connection_offset,self.height), connection_width) + self.add_via_stack_center(from_layer=pin.layer, + to_layer='m2', + offset=pin_center+supply_inst.ll()+cell_inst.ll() + vector(connection_offset,0)) - def get_column_offsets(self): - return self.replica_bitcell_array.get_column_offsets() + # add well contacts to perimeter cells + for pin_name in ['vpb', 'vnb']: + for supply_inst in supply_insts: + vnb_alternate = 0 + vpb_alternate = 0 + for cell_inst in supply_inst.mod.insts: - 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) + inst = cell_inst.mod + for pin in inst.get_pins(pin_name): + if pin.name == 'vpb': + if vpb_alternate: + connection_offset = 0.01 + vpb_alternate = 0 + else: + connection_offset = 0.02 + vpb_alternate = 1 + connection_width = drc["minwidth_{}".format('m1')] + track_offset = 2 + elif pin.name == 'vnb': + if vnb_alternate: + connection_offset = -0.01 + vnb_alternate = 0 + else: + connection_offset = -0.02 + vnb_alternate = 1 + connection_width = drc["minwidth_{}".format('m1')] + track_offset = 3 + if inst.cell_name == 'sky130_fd_bd_sram__sram_sp_colend_p_cent' or inst.cell_name == 'sky130_fd_bd_sram__sram_sp_colenda_p_cent' or inst.cell_name == 'sky130_fd_bd_sram__sram_sp_colend_cent' or inst.cell_name == 'sky130_fd_bd_sram__sram_sp_colenda_cent': + if 'dummy_row_bot' in supply_inst.name: + pin_center = vector(pin.center()[0], -1 * track_offset * (pin_height + drc_width*2)) + self.add_segment_center(pin.layer, pin_center+supply_inst.ll()+cell_inst.ll()+vector(connection_offset,0), vector((pin_center+supply_inst.ll()+cell_inst.ll())[0] + connection_offset, 0), connection_width) + elif 'dummy_row_top' in supply_inst.name: + pin_center = vector(pin.center()[0],inst.height + 1 * track_offset* (pin_height + drc_width*2)) + self.add_segment_center(pin.layer, pin_center+supply_inst.ll()+cell_inst.ll()+vector(connection_offset,0), vector((pin_center+supply_inst.ll()+cell_inst.ll())[0] + connection_offset, self.height), connection_width) + # elif 'replica_col' in supply_inst.name: + # pin_center = vector(pin.center()[0], -1 * track_offset* (pin_height + drc_width*2)) + # self.add_segment_center(pin.layer, pin_center+supply_inst.ll()+cell_inst.ll()+vector(connection_offset,0), vector((pin_center+supply_inst.ll()+cell_inst.ll())[0] + connection_offset, 0), connection_width) + # elif 'replica_col' in supply_inst.name: + # pin_center = vector(pin.center()[0],inst.height + 1 * track_offset * (pin_height + drc_width*2)) + # self.add_segment_center(pin.layer, pin_center+supply_inst.ll()+cell_inst.ll()+vector(connection_offset,0), vector((pin_center+supply_inst.ll()+cell_inst.ll())[0] + connection_offset,self.height), connection_width) + self.add_via_stack_center(from_layer=pin.layer, + to_layer='m2', + offset=pin_center+supply_inst.ll()+cell_inst.ll() + vector(connection_offset,0)) - # Calculate the bitcell power which currently only includes leakage - cell_power = self.cell.analytical_power(corner, load) + min_area = drc["minarea_{}".format('m3')] + for track,supply, offset in zip(range(1,5),['vdd','vdd','gnd','gnd'],[min_area * 6,min_area * 6, 0, 0]): + y_offset = track * (pin_height + drc_width*2) + self.add_segment_center('m2', vector(-0.4,-y_offset), vector(self.width+0.4, -y_offset), drc["minwidth_{}".format('m2')]) + self.add_segment_center('m2', vector(-0.4,self.height + y_offset), vector(self.width+0.4, self.height + y_offset), drc["minwidth_{}".format('m2')]) + self.add_power_pin(name=supply, + loc=vector(round_to_grid(sqrt(min_area))/2 + offset, -y_offset), + start_layer='m2') + self.add_power_pin(name=supply, + loc=vector(round_to_grid(sqrt(min_area))/2 + offset, self.height + y_offset), + start_layer='m2') + self.add_power_pin(name=supply, + loc=vector(self.width - round_to_grid(sqrt(min_area))/2 - offset, -y_offset), + start_layer='m2') + self.add_power_pin(name=supply, + loc=vector(self.width - round_to_grid(sqrt(min_area))/2 - offset, self.height + y_offset), + start_layer='m2') - # 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 + self.offset_all_coordinates() + self.height = self.height + self.dummy_col_insts[0].lr().y * 2 + - - 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() + for pin_name in self.bitline_pin_list: + pin_list = self.replica_bitcell_array_inst.get_pins(pin_name) + for pin in pin_list: + if 'bl' in pin.name: + self.add_layout_pin(text=pin_name, + layer=pin.layer, + offset=pin.ll().scale(1, 0), + width=pin.width(), + height=self.height) + elif 'br' in pin_name: + self.add_layout_pin(text=pin_name, + layer=pin.layer, + offset=pin.ll().scale(1, 0) + vector(0,pin_height + drc_width*2), + width=pin.width(), + height=self.height - 2 *(pin_height + drc_width*2)) + + # # Replica bitlines + # if len(self.rbls) > 0: + # for (names, inst) in zip(self.rbl_bitline_names, self.replica_col_insts): + # pin_names = self.replica_bitcell_array_inst.mod.replica_columns[self.rbls[0]].all_bitline_names + # mirror = self.replica_col_insts[0].mirror + # for (bl_name, pin_name) in zip(names, pin_names): + # pin = inst.get_pin(pin_name) + # if 'rbl_bl' in bl_name: + # # if mirror != "MY": + # # bl_name = bl_name.replace("rbl_bl","rbl_br") + # self.add_layout_pin(text=bl_name, + # layer=pin.layer, + # offset=pin.ll().scale(1, 0), + # width=pin.width(), + # height=self.height) + # elif 'rbl_br' in bl_name: + # # if mirror != "MY": + # # bl_name = bl_name.replace("rbl_br","rbl_bl") + # self.add_layout_pin(text=bl_name, + # layer=pin.layer, + # offset=pin.ll().scale(1, 0) + vector(0,(pin_height + drc_width*2)), + # width=pin.width(), + # height=self.height - 2 *(pin_height + drc_width*2)) + return diff --git a/technology/sky130/custom/sky130_col_cap_array.py b/technology/sky130/custom/sky130_col_cap_array.py index 445c918c..960a87be 100644 --- a/technology/sky130/custom/sky130_col_cap_array.py +++ b/technology/sky130/custom/sky130_col_cap_array.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 + #!/usr/bin/env python3 # See LICENSE for licensing information. # # Copyright (c) 2016-2023 Regents of the University of California @@ -9,42 +9,18 @@ from openram.base import geometry from openram.sram_factory import factory from openram.tech import layer from openram import OPTS +from openram.modules.col_cap_array import col_cap_array from .sky130_bitcell_base_array import sky130_bitcell_base_array +from openram.modules import pattern +from math import ceil -class sky130_col_cap_array(sky130_bitcell_base_array): +class sky130_col_cap_array(col_cap_array, sky130_bitcell_base_array): """ Generate a dummy row/column for the replica array. """ - def __init__(self, rows, cols, location, column_offset=0, mirror=0, name=""): - # Don't call the regular col-cap_array constructor since we don't want its constructor, just - # some of it's useful member functions - sky130_bitcell_base_array.__init__(self, rows=rows, cols=cols, column_offset=column_offset, name=name) - self.mirror = mirror - self.location = location - self.rows = rows - self.cols = cols - self.create_netlist() - if not OPTS.netlist_only: - self.create_layout() - - def create_netlist(self): - """ Create and connect the netlist """ - # This module has no wordlines - # self.create_all_wordline_names() - # This module has no bitlines - # self.create_all_bitline_names() - self.add_modules() - self.create_all_wordline_names() - self.add_pins() - self.create_instances() - - def create_layout(self): - - self.place_array("dummy_r{0}_c{1}", self.mirror) - self.add_layout_pins() - self.add_supply_pins() - self.add_boundary() - self.DRC_LVS() + def __init__(self, rows, cols, column_offset=0, mirror=0, location="", name=""): + super().__init__(rows, cols, column_offset=column_offset, mirror=mirror, location=location, name=name) + self.no_instances = False def add_modules(self): """ Add the modules used in this design """ @@ -60,196 +36,60 @@ class sky130_col_cap_array(sky130_bitcell_base_array): self.cell = factory.create(module_type=OPTS.bitcell, version="opt1") def create_instances(self): - """ Create the module instances used in this design """ - self.cell_inst = {} - self.array_layout = [] - bitline = 0 - for col in range((self.column_size * 2) - 1): - row_layout = [] - name="rca_{0}_{1}".format(self.location, col) - # Top/bottom cell are always dummy cells. - # Regular array cells are replica cells (>left_rbl and 1 else 0] - # This specifies which RBL to put on the left or right - # by port number - # This could be an empty list - if left_rbl != None: - self.left_rbl = left_rbl - else: - self.left_rbl = [0] - # This could be an empty list - if right_rbl != None: - self.right_rbl = right_rbl - else: - self.right_rbl=[1] if len(self.all_ports) > 1 else [] - self.rbls = self.left_rbl + self.right_rbl - - if ((self.column_size + self.rbl[0] + self.rbl[1]) % array_col_multiple != 0): - debug.error("Invalid number of cols including rbl(s): {}. Total cols must be divisible by {}".format(self.column_size + self.rbl[0] + self.rbl[1], array_col_multiple), -1) - - if ((self.row_size + self.rbl[0] + self.rbl[1]) % array_row_multiple != 0): - debug.error("invalid number of rows including dummy row(s): {}. Total cols must be divisible by {}".format(self.row_size + self.rbl[0] + self.rbl[1], array_row_multiple), -15) - - super().__init__(self.row_size, self.column_size, rbl, left_rbl, right_rbl, name) - - def create_layout(self): - # We will need unused wordlines grounded, so we need to know their layer - # and create a space on the left and right for the vias to connect to ground - pin = self.cell.get_pin(self.cell.get_all_wl_names()[0]) - pin_layer = pin.layer - self.unused_pitch = 1.5 * getattr(self, "{}_pitch".format(pin_layer)) - self.unused_offset = vector(self.unused_pitch, 0) - - # This is a bitcell x bitcell offset to scale - self.bitcell_offset = vector(self.cell.width, self.cell.height) - self.strap_offset = vector(self.replica_col_insts[0].mod.strap1.width, self.replica_col_insts[0].mod.strap1.height) - self.col_end_offset = vector(self.dummy_row_insts[0].mod.colend1.width, self.dummy_row_insts[0].mod.colend1.height) - self.row_end_offset = vector(self.dummy_col_insts[0].mod.rowend1.width, self.dummy_col_insts[0].mod.rowend1.height) - - # Everything is computed with the main array at (self.unused_pitch, 0) to start - self.bitcell_array_inst.place(offset=self.unused_offset) - - self.add_replica_columns() - - self.add_end_caps() - - # Array was at (0, 0) but move everything so it is at the lower left - self.offset_all_coordinates() - - # Add extra width on the left and right for the unused WLs - #self.width = self.dummy_col_insts[0].rx() + self.unused_offset[0] - self.width = self.dummy_col_insts[1].rx() - self.height = self.dummy_col_insts[0].uy() - - self.add_layout_pins() - - self.route_unused_wordlines() - - self.add_boundary() - - self.DRC_LVS() - - def add_pins(self): - super().add_pins() - - def add_replica_columns(self): - """ Add replica columns on left and right of array """ - - # Grow from left to right, toward the array - for bit, port in enumerate(self.left_rbl): - offset = self.bitcell_array_inst.ll() \ - - vector(0, self.col_cap_bottom.height) \ - - vector(0, self.dummy_row.height) \ - - vector(self.replica_columns[0].width, 0) - self.replica_col_insts[bit].place(offset + vector(0, self.replica_col_insts[bit].height), mirror="MX") - - # 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] - (self.col_end_offset.y / self.cell.height)) \ - + self.strap_offset.scale(bit, -self.rbl[0] - 1) - 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 - 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 - for bit in range(self.rbl[1]): - dummy_offset = self.bitcell_offset.scale(0, bit + bit % 2) + self.bitcell_array_inst.ul() - self.dummy_row_replica_insts[self.rbl[0] + bit].place(offset=dummy_offset, - mirror="MX" if bit % 2 else "R0") - - def add_end_caps(self): - """ Add dummy cells or end caps around the array """ - - dummy_row_offset = self.bitcell_offset.scale(0, self.rbl[1]) + self.bitcell_array_inst.ul() - self.dummy_row_insts[1].place(offset=dummy_row_offset) - - dummy_row_offset = self.bitcell_offset.scale(0, -self.rbl[0] - (self.col_end_offset.y / self.cell.height)) + self.unused_offset - self.dummy_row_insts[0].place(offset=dummy_row_offset + vector(0, self.dummy_row_insts[0].height), mirror="MX") - - # 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.right_rbl) * (1 + self.strap_offset.x / self.cell.width), -self.rbl[0] - (self.col_end_offset.y / self.cell.height)) - vector(self.replica_col_insts[0].width, 0) + self.unused_offset - self.dummy_col_insts[0].place(offset=dummy_col_offset, mirror="MY") - - # 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) * (1 + self.strap_offset.x / self.cell.width), -self.rbl[0] - (self.col_end_offset.y / self.cell.height)) + self.bitcell_array_inst.lr() - self.dummy_col_insts[1].place(offset=dummy_col_offset) - - def route_unused_wordlines(self): - """ Connect the unused RBL and dummy wordlines to gnd """ - return - # This grounds all the dummy row word lines - for inst in self.dummy_row_insts: - for wl_name in self.col_cap.get_wordline_names(): - self.ground_pin(inst, wl_name) - - # 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: - self.ground_pin(inst, pin_name) - - def add_layout_pins(self): - """ Add the layout pins """ - - for row_end in self.dummy_col_insts: - row_end = row_end.mod - for (rba_wl_name, wl_name) in zip(self.get_all_wordline_names(), row_end.get_wordline_names()): - pin = row_end.get_pin(wl_name) - self.add_layout_pin(text=rba_wl_name, - layer=pin.layer, - offset=vector(0,pin.ll().scale(0, 1)[1]), - #width=self.width, - width=pin.width(), - height=pin.height()) - - pin_height = (round_to_grid(drc["minarea_m3"] / round_to_grid(sqrt(drc["minarea_m3"]))) + drc["{0}_to_{0}".format('m3')]) - drc_width = drc["{0}_to_{0}".format('m3')] - - # vdd/gnd are only connected in the perimeter cells - # replica column should only have a vdd/gnd in the dummy cell on top/bottom - supply_insts = self.dummy_row_insts + self.replica_col_insts - - for pin_name in self.supplies: - for supply_inst in supply_insts: - vdd_alternate = 0 - gnd_alternate = 0 - for cell_inst in supply_inst.mod.insts: - inst = cell_inst.mod - for pin in inst.get_pins(pin_name): - if pin.name == 'vdd': - if vdd_alternate: - connection_offset = 0.035 - vdd_alternate = 0 - else: - connection_offset = -0.035 - vdd_alternate = 1 - connection_width = drc["minwidth_{}".format('m1')] - track_offset = 1 - elif pin.name == 'gnd': - if gnd_alternate: - connection_offset = 0.035 - gnd_alternate = 0 - else: - connection_offset = -0.035 - gnd_alternate = 1 - connection_width = drc["minwidth_{}".format('m1')] - track_offset = 4 - pin_width = round_to_grid(sqrt(drc["minarea_m3"])) - pin_height = round_to_grid(drc["minarea_m3"] / pin_width) - if inst.cell_name == 'sky130_fd_bd_sram__sram_sp_colend_p_cent' or inst.cell_name == 'sky130_fd_bd_sram__sram_sp_colenda_p_cent' or inst.cell_name == 'sky130_fd_bd_sram__sram_sp_colend_cent' or inst.cell_name == 'sky130_fd_bd_sram__sram_sp_colenda_cent' or 'corner' in inst.cell_name: - if 'dummy_row' in supply_inst.name and supply_inst.mirror == 'MX': - pin_center = vector(pin.center()[0], -1 * track_offset * (pin_height + drc_width*2)) - self.add_segment_center(pin.layer, pin_center+supply_inst.ll()+cell_inst.ll()+vector(connection_offset,0), vector((pin_center+supply_inst.ll()+cell_inst.ll())[0] + connection_offset, 0), connection_width) - elif 'dummy_row' in supply_inst.name: - pin_center = vector(pin.center()[0],inst.height + 1 * track_offset* (pin_height + drc_width*2)) - self.add_segment_center(pin.layer, pin_center+supply_inst.ll()+cell_inst.ll()+vector(connection_offset,0), vector((pin_center+supply_inst.ll()+cell_inst.ll())[0] + connection_offset, self.height), connection_width) - elif 'replica_col' in supply_inst.name and cell_inst.mirror == 'MX': - pin_center = vector(pin.center()[0], -1 * track_offset* (pin_height + drc_width*2)) - self.add_segment_center(pin.layer, pin_center+supply_inst.ll()+cell_inst.ll()+vector(connection_offset,0), vector((pin_center+supply_inst.ll()+cell_inst.ll())[0] + connection_offset, 0), connection_width) - elif 'replica_col' in supply_inst.name: - pin_center = vector(pin.center()[0],inst.height + 1 * track_offset * (pin_height + drc_width*2)) - self.add_segment_center(pin.layer, pin_center+supply_inst.ll()+cell_inst.ll()+vector(connection_offset,0), vector((pin_center+supply_inst.ll()+cell_inst.ll())[0] + connection_offset,self.height), connection_width) - self.add_via_stack_center(from_layer=pin.layer, - to_layer='m2', - offset=pin_center+supply_inst.ll()+cell_inst.ll() + vector(connection_offset,0)) - - - # add well contacts to perimeter cells - for pin_name in ['vpb', 'vnb']: - for supply_inst in supply_insts: - vnb_alternate = 0 - vpb_alternate = 0 - for cell_inst in supply_inst.mod.insts: - - inst = cell_inst.mod - for pin in inst.get_pins(pin_name): - if pin.name == 'vpb': - if vpb_alternate: - connection_offset = 0.01 - vpb_alternate = 0 - else: - connection_offset = 0.02 - vpb_alternate = 1 - connection_width = drc["minwidth_{}".format('m1')] - track_offset = 2 - elif pin.name == 'vnb': - if vnb_alternate: - connection_offset = -0.01 - vnb_alternate = 0 - else: - connection_offset = -0.02 - vnb_alternate = 1 - connection_width = drc["minwidth_{}".format('m1')] - track_offset = 3 - if inst.cell_name == 'sky130_fd_bd_sram__sram_sp_colend_p_cent' or inst.cell_name == 'sky130_fd_bd_sram__sram_sp_colenda_p_cent' or inst.cell_name == 'sky130_fd_bd_sram__sram_sp_colend_cent' or inst.cell_name == 'sky130_fd_bd_sram__sram_sp_colenda_cent': - if 'dummy_row' in supply_inst.name and supply_inst.mirror == 'MX': - pin_center = vector(pin.center()[0], -1 * track_offset * (pin_height + drc_width*2)) - self.add_segment_center(pin.layer, pin_center+supply_inst.ll()+cell_inst.ll()+vector(connection_offset,0), vector((pin_center+supply_inst.ll()+cell_inst.ll())[0] + connection_offset, 0), connection_width) - elif 'dummy_row' in supply_inst.name: - pin_center = vector(pin.center()[0],inst.height + 1 * track_offset* (pin_height + drc_width*2)) - self.add_segment_center(pin.layer, pin_center+supply_inst.ll()+cell_inst.ll()+vector(connection_offset,0), vector((pin_center+supply_inst.ll()+cell_inst.ll())[0] + connection_offset, self.height), connection_width) - elif 'replica_col' in supply_inst.name and cell_inst.mirror == 'MX': - pin_center = vector(pin.center()[0], -1 * track_offset* (pin_height + drc_width*2)) - self.add_segment_center(pin.layer, pin_center+supply_inst.ll()+cell_inst.ll()+vector(connection_offset,0), vector((pin_center+supply_inst.ll()+cell_inst.ll())[0] + connection_offset, 0), connection_width) - elif 'replica_col' in supply_inst.name: - pin_center = vector(pin.center()[0],inst.height + 1 * track_offset * (pin_height + drc_width*2)) - self.add_segment_center(pin.layer, pin_center+supply_inst.ll()+cell_inst.ll()+vector(connection_offset,0), vector((pin_center+supply_inst.ll()+cell_inst.ll())[0] + connection_offset,self.height), connection_width) - self.add_via_stack_center(from_layer=pin.layer, - to_layer='m2', - offset=pin_center+supply_inst.ll()+cell_inst.ll() + vector(connection_offset,0)) - - min_area = drc["minarea_{}".format('m3')] - for track,supply, offset in zip(range(1,5),['vdd','vdd','gnd','gnd'],[min_area * 6,min_area * 6, 0, 0]): - y_offset = track * (pin_height + drc_width*2) - self.add_segment_center('m2', vector(0,-y_offset), vector(self.width, -y_offset), drc["minwidth_{}".format('m2')]) - self.add_segment_center('m2', vector(0,self.height + y_offset), vector(self.width, self.height + y_offset), drc["minwidth_{}".format('m2')]) - self.add_power_pin(name=supply, - loc=vector(round_to_grid(sqrt(min_area))/2 + offset, -y_offset), - start_layer='m2') - self.add_power_pin(name=supply, - loc=vector(round_to_grid(sqrt(min_area))/2 + offset, self.height + y_offset), - start_layer='m2') - self.add_power_pin(name=supply, - loc=vector(self.width - round_to_grid(sqrt(min_area))/2 - offset, -y_offset), - start_layer='m2') - self.add_power_pin(name=supply, - loc=vector(self.width - round_to_grid(sqrt(min_area))/2 - offset, self.height + y_offset), - start_layer='m2') - - self.offset_all_coordinates() - self.height = self.height + self.dummy_col_insts[0].lr().y * 2 - - for pin_name in self.all_bitline_names: - pin_list = self.bitcell_array_inst.get_pins(pin_name) - for pin in pin_list: - if 'bl' in pin.name: - self.add_layout_pin(text=pin_name, - layer=pin.layer, - offset=pin.ll().scale(1, 0), - width=pin.width(), - height=self.height) - elif 'br' in pin_name: - self.add_layout_pin(text=pin_name, - layer=pin.layer, - offset=pin.ll().scale(1, 0) + vector(0,pin_height + drc_width*2), - width=pin.width(), - height=self.height - 2 *(pin_height + drc_width*2)) - # Replica bitlines - if len(self.rbls) > 0: - for (names, inst) in zip(self.rbl_bitline_names, self.replica_col_insts): - pin_names = self.replica_columns[self.rbls[0]].all_bitline_names - mirror = self.replica_col_insts[0].mirror - for (bl_name, pin_name) in zip(names, pin_names): - pin = inst.get_pin(pin_name) - if 'rbl_bl' in bl_name: - # if mirror != "MY": - # bl_name = bl_name.replace("rbl_bl","rbl_br") - self.add_layout_pin(text=bl_name, - layer=pin.layer, - offset=pin.ll().scale(1, 0), - width=pin.width(), - height=self.height) - elif 'rbl_br' in bl_name: - # if mirror != "MY": - # bl_name = bl_name.replace("rbl_br","rbl_bl") - self.add_layout_pin(text=bl_name, - layer=pin.layer, - offset=pin.ll().scale(1, 0) + vector(0,(pin_height + drc_width*2)), - width=pin.width(), - height=self.height - 2 *(pin_height + drc_width*2)) - return - - def add_wordline_pins(self): - - # Wordlines to ground - self.gnd_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.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())) - 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) - 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())) - - 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") - for port in range(self.rbl[0], self.rbl[0] + self.rbl[1]): - self.add_pin(self.rbl_wordline_names[port][port], "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) - self.connect_inst(self.all_bitline_names + self.all_wordline_names + self.supplies) - # Replica columns - self.replica_col_insts = [] - for port in self.all_ports: - 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 + ["gnd"] + ["gnd"]) - else: - self.replica_col_insts.append(None) - - # Dummy rows under the bitcell array (connected with 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: - 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 + self.supplies + ["gnd"]) - self.dummy_row_insts.append(self.add_inst(name="dummy_row_top", - mod=self.col_cap_top)) - self.connect_inst(self.all_bitline_names + self.supplies + ["gnd"]) - - # 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] + ["gnd"] + self.replica_array_wordline_names + ["gnd"] + 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] + ["gnd"] + self.replica_array_wordline_names + ["gnd"] + self.supplies) diff --git a/technology/sky130/custom/sky130_replica_column.py b/technology/sky130/custom/sky130_replica_column.py index 4d95a3a4..a40e23fa 100644 --- a/technology/sky130/custom/sky130_replica_column.py +++ b/technology/sky130/custom/sky130_replica_column.py @@ -11,9 +11,9 @@ from openram.sram_factory import factory from openram.tech import layer from openram import OPTS from .sky130_bitcell_base_array import sky130_bitcell_base_array - - -class sky130_replica_column(sky130_bitcell_base_array): +from openram.modules import pattern +from openram.modules import replica_column +class sky130_replica_column(replica_column, sky130_bitcell_base_array): """ Generate a replica bitline column for the replica array. Rows is the total number of rows i the main array. @@ -23,75 +23,7 @@ class sky130_replica_column(sky130_bitcell_base_array): """ def __init__(self, name, rows, rbl, replica_bit, column_offset=0): - # Used for pin names and properties - self.cell = factory.create(module_type=OPTS.bitcell) - # 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 - # 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 - self.left_rbl = rbl[0] - self.right_rbl = rbl[1] - self.replica_bit = replica_bit - # left, right, regular rows plus top/bottom dummy cells - - self.total_size = self.left_rbl + rows + self.right_rbl + 2 - self.column_offset = column_offset - - if self.rows % 2 == 0: - debug.error("Invalid number of rows {}. Number of rows must be even to connect to col ends".format(self.rows), -1) - if self.column_offset % 2 == 0: - debug.error("Invalid column_offset {}. Column offset must be odd to connect to col ends".format(self.rows), -1) - debug.check(replica_bit != 0 and replica_bit != rows, - "Replica bit cannot be the dummy row.") - debug.check(replica_bit <= self.left_rbl or replica_bit >= self.total_size - self.right_rbl - 1, - "Replica bit cannot be in the regular array.") - # if OPTS.tech_name == "sky130": - # 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" - # + " (left_rbl must be even) for LVS.") - # commented out to support odd row counts while testing opc - - self.create_netlist() - if not OPTS.netlist_only: - self.create_layout() - - def create_netlist(self): - self.add_modules() - self.add_pins() - self.create_instances() - - def create_layout(self): - self.place_instances() - - self.width = max([x.rx() for x in self.insts]) - self.height = max([x.uy() for x in self.insts]) - - self.add_layout_pins() - - self.add_boundary() - self.DRC_LVS() - - def add_pins(self): - - self.create_all_bitline_names() - #self.create_all_wordline_names(self.row_size+2) - # +2 to add fake wl pins for colends - self.create_all_wordline_names(self.row_size+1, 1) - self.add_pin_list(self.all_bitline_names, "OUTPUT") - self.add_pin_list(self.all_wordline_names, "INPUT") - - self.add_pin("vdd", "POWER") - self.add_pin("gnd", "GROUND") - - self.add_pin("top_gate", "INPUT") - self.add_pin("bot_gate", "INPUT") + super().__init__(name=name, rows=rows, rbl=rbl, replica_bit=replica_bit, column_offset=column_offset) def add_modules(self): self.replica_cell = factory.create(module_type="replica_bitcell_1port", version="opt1") @@ -99,172 +31,65 @@ class sky130_replica_column(sky130_bitcell_base_array): self.replica_cell2 = factory.create(module_type="replica_bitcell_1port", version="opt1a") self.dummy_cell = factory.create(module_type="dummy_bitcell_1port", version="opt1") - self.dummy_cell2 = factory.create(module_type="dummy_bitcell_1port", version="opt1") - - self.strap1 = factory.create(module_type="internal", version="wlstrap") - self.strap2 = factory.create(module_type="internal", version="wlstrap_p") - self.strap3 = factory.create(module_type="internal", version="wlstrapa_p") - - self.colend = factory.create(module_type="col_cap", version="colend") - self.edge_cell = self.colend - self.colenda = factory.create(module_type="col_cap", version="colenda") - self.colend_p_cent = factory.create(module_type="col_cap", version="colend_p_cent") - self.colenda_p_cent = factory.create(module_type="col_cap", version="colenda_p_cent") + self.dummy_cell2 = factory.create(module_type="dummy_bitcell_1port", version="opt1a") + self.strap = factory.create(module_type="internal", version="wlstrap") + self.strap_p = factory.create(module_type="internal", version="wlstrap_p") + self.strapa = factory.create(module_type="internal", version="wlstrapa") + self.strapa_p = factory.create(module_type="internal", version="wlstrapa_p") + def create_instances(self): - self.cell_inst = {} - self.array_layout = [] - alternate_bitcell = (self.rows + 1) % 2 + """ Create the module instances used in this design """ + self.all_inst={} + self.cell_inst={} + + replica_row_opt1 = [geometry.instance("rep_00_opt1", mod=self.replica_cell, is_bitcell=True, mirror='XY')] \ + + [geometry.instance("rep_01_strap_p", mod=self.strap_p, is_bitcell=False, mirror='MX')]\ + + [geometry.instance("rep_02_opt1", mod=self.replica_cell, is_bitcell=True, mirror='MX')] \ + + [geometry.instance("rep_03_strap", mod=self.strap, is_bitcell=False, mirror='MX')] + + replica_row_opt1a = [geometry.instance("rep_10_opt1a", mod=self.replica_cell2, is_bitcell=True, mirror='MY')] \ + + [geometry.instance("rep_11_strap_p", mod=self.strap_p, is_bitcell=False)] \ + + [geometry.instance("rep_12_opt1a", mod=self.replica_cell2, is_bitcell=True)] \ + + [geometry.instance("rep_13_strapaa", mod=self.strapa, is_bitcell=False)] + + dummy_row_opt1 = [geometry.instance("dummy_00_opt1", mod=self.dummy_cell, is_bitcell=True, mirror='XY')] \ + + [geometry.instance("dummy_01_strap_p", mod=self.strap_p, is_bitcell=False, mirror='MX')]\ + + [geometry.instance("dummy_02_opt1", mod=self.dummy_cell, is_bitcell=True, mirror='MX')] \ + + [geometry.instance("dummy_03_strap", mod=self.strap, is_bitcell=False, mirror='MX')] + + dummy_row_opt1a = [geometry.instance("dummy_10_opt1a", mod=self.dummy_cell2, is_bitcell=True, mirror='MY')] \ + + [geometry.instance("dummy_11_strap_p", mod=self.strap_p, is_bitcell=False)] \ + + [geometry.instance("dummy_12_opt1a", mod=self.dummy_cell2, is_bitcell=True)] \ + + [geometry.instance("dummy_13_strapa", mod=self.strapa, is_bitcell=False)] + + bit_block = [] + if self.column_offset % 2 == 0: + replica_row_opt1 = replica_row_opt1[0:2] + replica_row_opt1a = replica_row_opt1a[0:2] + dummy_row_opt1 = dummy_row_opt1[0:2] + dummy_row_opt1a = dummy_row_opt1a[0:2] + else: + replica_row_opt1 = replica_row_opt1[2:4] + replica_row_opt1a = replica_row_opt1a[2:4] + dummy_row_opt1 = dummy_row_opt1[2:4] + dummy_row_opt1a = dummy_row_opt1a[2:4] + current_row = self.row_start for row in range(self.total_size): - row_layout = [] - name="rbc_{0}".format(row) - # Top/bottom cell are always dummy cells. - # Regular array cells are replica cells (>left_rbl and self.left_rbl and row < self.total_size - 1 or row == self.replica_bit): - - if alternate_bitcell == 0: - row_layout.append(self.replica_cell) - self.cell_inst[row]=self.add_inst(name=name, mod=self.replica_cell) - self.connect_inst(self.get_bitcell_pins(row, 0)) - row_layout.append(self.strap2) - self.add_inst(name=name + "_strap_p", mod=self.strap2) - self.connect_inst(self.get_strap_pins(row, 0, name + "_strap_p")) - alternate_bitcell = 1 - + # Regular array cells are replica cells + # Replic bit specifies which other bit (in the full range (0,total_size) to make a replica cell. + # All other cells are dummies + if (row == self.replica_bit) or (row >= self.row_start and row < self.row_end): + if current_row % 2 == 1: + pattern.append_row_to_block(bit_block, replica_row_opt1) else: - row_layout.append(self.replica_cell2) - self.cell_inst[row]=self.add_inst(name=name, mod=self.replica_cell2) - self.connect_inst(self.get_bitcell_pins(row, 0)) - row_layout.append(self.strap3) - self.add_inst(name=name + "_strap", mod=self.strap3) - self.connect_inst(self.get_strap_pins(row, 0)) - alternate_bitcell = 0 - - elif (row == 0): - row_layout.append(self.colend) - self.cell_inst[row]=self.add_inst(name=name, mod=self.colend) - self.connect_inst(self.get_col_cap_pins(row, 0)) - row_layout.append(self.colend_p_cent) - self.add_inst(name=name + "_cap", mod=self.colend_p_cent) - self.connect_inst(self.get_col_cap_p_pins(row, 0)) - elif (row == self.total_size - 1): - row_layout.append(self.colenda) - self.cell_inst[row]=self.add_inst(name=name, mod=self.colenda) - self.connect_inst(self.get_col_cap_pins(row, 0)) - row_layout.append(self.colenda_p_cent) - self.add_inst(name=name + "_cap", mod=self.colenda_p_cent) - self.connect_inst(self.get_col_cap_p_pins(row, 0)) - - self.array_layout.append(row_layout) - - def place_instances(self, name_template="", row_offset=0): - col_offset = self.column_offset - yoffset = 0.0 - - for row in range(row_offset, len(self.array_layout) + row_offset): - xoffset = 0.0 - for col in range(col_offset, len(self.array_layout[row]) + col_offset): - self.place_inst = self.insts[(col - col_offset) + (row - row_offset) * len(self.array_layout[row - row_offset])] - if row == row_offset or row == (len(self.array_layout) + row_offset -1): - if row == row_offset: - self.place_inst.place(offset=[xoffset, yoffset + self.colend.height], mirror="MX") - else: - self.place_inst.place(offset=[xoffset, yoffset]) - - elif col % 2 == 0: - if row % 2 == 0: - self.place_inst.place(offset=[xoffset, yoffset + self.place_inst.height], mirror="MX") - else: - self.place_inst.place(offset=[xoffset, yoffset]) - else: - if row % 2 == 0: - self.place_inst.place(offset=[xoffset + self.place_inst.width, yoffset + self.place_inst.height], mirror="XY") - else: - self.place_inst.place(offset=[xoffset + self.place_inst.width, yoffset], mirror="MY") - - xoffset += self.place_inst.width - if row == row_offset: - yoffset += self.colend.height + pattern.append_row_to_block(bit_block, replica_row_opt1a) else: - yoffset += self.place_inst.height + if current_row % 2 == 1: + pattern.append_row_to_block(bit_block, dummy_row_opt1) + else: + pattern.append_row_to_block(bit_block, dummy_row_opt1a) + current_row += 1 + self.pattern = pattern(self, "replica_column", bit_block, num_rows=self.total_size, num_cols=len(replica_row_opt1), name_template="rbc_r{0}_c{1}") + self.pattern.connect_array_raw() - self.width = max([x.rx() for x in self.insts]) - self.height = max([x.uy() for x in self.insts]) - - def add_layout_pins(self): - """ Add the layout pins """ - for port in self.all_ports: - bl_pin = self.cell_inst[2].get_pin(self.cell.get_bl_name(port)) - self.add_layout_pin(text="bl_{0}_{1}".format(port, 0), - layer=bl_pin.layer, - offset=bl_pin.ll().scale(1, 0), - width=bl_pin.width(), - height=self.height) - bl_pin = self.cell_inst[2].get_pin(self.cell.get_br_name(port)) - self.add_layout_pin(text="br_{0}_{1}".format(port, 0), - layer=bl_pin.layer, - offset=bl_pin.ll().scale(1, 0), - width=bl_pin.width(), - height=self.height) - - row_range_max = self.total_size - 1 - row_range_min = 1 - - for port in self.all_ports: - for row in range(row_range_min, row_range_max): - 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_range_max-row), - layer=wl_pin.layer, - offset=wl_pin.ll().scale(0, 1), - width=self.width, - height=wl_pin.height()) - - # for colend in [self.cell_inst[0], self.cell_inst[self.row_size]]: - # inst = self.cell_inst[row] - # for pin_name in ["top_gate", "bot_gate"]: - # pin = inst.get_pin("gate") - # self.add_layout_pin(text=pin_name, - # layer=pin.layer, - # offset=pin.ll(), - # width=pin.width(), - # height=pin.height()) - - for row in range(self.row_size + 2): - inst = self.cell_inst[row] - # add only 1 label per col - for pin_name in ["vdd", "gnd"]: - self.copy_layout_pin(inst, pin_name) - #if row == 2: - if 'VPB' or 'vpb' in self.cell_inst[row].mod.pins: - pin = inst.get_pin("vpb") - self.objs.append(geometry.rectangle(layer["nwell"], - pin.ll(), - pin.width(), - pin.height())) - self.objs.append(geometry.label("vdd", layer["nwell"], pin.center())) - - if 'VNB' or 'vnb' in self.cell_inst[row].mod.pins: - try: - from openram.tech import layer_override - if layer_override['VNB']: - pin = inst.get_pin("vnb") - self.add_label("gnd", pin.layer, pin.center()) - self.objs.append(geometry.rectangle(layer["pwellp"], - pin.ll(), - pin.width(), - pin.height())) - self.objs.append(geometry.label("gnd", layer["pwellp"], pin.center())) - - - except: - pin = inst.get_pin("vnb") - self.add_label("gnd", pin.layer, pin.center()) - - def exclude_all_but_replica(self): - """ - Excludes all bits except the replica cell (self.replica_bit). - """ - for row, cell in self.cell_inst.items(): - if row != self.replica_bit: - self.graph_inst_exclude.add(cell) diff --git a/technology/sky130/custom/sky130_row_cap_array.py b/technology/sky130/custom/sky130_row_cap_array.py index f8d9d91c..7031e388 100644 --- a/technology/sky130/custom/sky130_row_cap_array.py +++ b/technology/sky130/custom/sky130_row_cap_array.py @@ -5,140 +5,107 @@ # All rights reserved. # +from openram.base import geometry from openram.sram_factory import factory from openram import OPTS from .sky130_bitcell_base_array import sky130_bitcell_base_array +from openram.modules.row_cap_array import row_cap_array +from openram.modules.pattern import pattern +from math import ceil - -class sky130_row_cap_array(sky130_bitcell_base_array): +class sky130_row_cap_array(row_cap_array, sky130_bitcell_base_array): """ Generate a dummy row/column for the replica array. """ - def __init__(self, rows, cols, column_offset=0, mirror=0, name=""): - # Don't call the regular col-cap_array constructor since we don't want its constructor, just - # some of it's useful member functions - sky130_bitcell_base_array.__init__(self, rows=rows, cols=cols, column_offset=column_offset, name=name) - self.rows = rows - self.cols = cols - self.column_offset = column_offset + def __init__(self, rows, cols, column_offset=0, mirror=0, location="", name=""): + super().__init__(rows, cols, column_offset=column_offset, location=location, name=name) self.mirror = mirror - self.create_netlist() - if not OPTS.netlist_only: - self.create_layout() - - def create_netlist(self): - """ Create and connect the netlist """ - self.create_all_wordline_names() - # This module has no bitlines - # self.create_all_bitline_names() - - self.add_modules() - self.add_pins() - self.create_instances() - - def create_layout(self): - - self.place_array("dummy_r{0}_c{1}", self.mirror) - self.add_layout_pins() - - self.width = max([x.rx() for x in self.insts]) - self.height = max([x.uy() for x in self.insts]) - - self.add_boundary() - self.DRC_LVS() - + self.location = location def add_modules(self): """ Add the modules used in this design """ - if self.column_offset == 0: + if self.location == "left": self.top_corner = factory.create(module_type="corner", location="ul") self.bottom_corner =factory.create(module_type="corner", location="ll") - self.rowend1 = factory.create(module_type="row_cap", version="rowend_replica") - self.rowend2 = factory.create(module_type="row_cap", version="rowenda_replica") + #self.rowend1 = factory.create(module_type="row_cap", version="rowend_replica") + #self.rowend2 = factory.create(module_type="row_cap", version="rowenda_replica") else: self.top_corner = factory.create(module_type="corner", location="ur") self.bottom_corner = factory.create(module_type="corner", location="lr") - self.rowend1 = factory.create(module_type="row_cap", version="rowend") - self.rowend2 = factory.create(module_type="row_cap", version="rowenda") - + #self.rowend1 = factory.create(module_type="row_cap", version="rowend") + #self.rowend2 = factory.create(module_type="row_cap", version="rowenda") + self.rowend = factory.create(module_type="row_cap", version="rowend") + self.rowenda = factory.create(module_type="row_cap", version="rowenda") self.cell = factory.create(module_type=OPTS.bitcell, version="opt1") def create_instances(self): - """ Create the module instances used in this design """ - self.cell_inst = {} - self.array_layout = [] - alternate_bitcell = (self.rows + 1) % 2 - for row in range(self.rows + 2): - row_layout = [] - name="rca_{0}".format(row) - # Top/bottom cell are always dummy cells. - # Regular array cells are replica cells (>left_rbl and 0): - - if alternate_bitcell == 0: - row_layout.append(self.rowend1) - self.cell_inst[row]=self.add_inst(name=name, mod=self.rowend1) - self.connect_inst(["wl_0_{}".format(row - 1), "vdd"]) - alternate_bitcell = 1 - - else: - row_layout.append(self.rowend2) - self.cell_inst[row] = self.add_inst(name=name, mod=self.rowend2) - self.connect_inst(["wl_0_{}".format(row - 1), "vdd"]) - alternate_bitcell = 0 - - elif (row == 0): - row_layout.append(self.bottom_corner) - self.cell_inst[row]=self.add_inst(name=name, mod=self.bottom_corner) - self.connect_inst(self.get_corner_pins()) - - elif (row == self.rows + 1): - row_layout.append(self.top_corner) - self.cell_inst[row]=self.add_inst(name=name, mod=self.top_corner) - self.connect_inst(self.get_corner_pins()) - - self.array_layout.append(row_layout) - - def place_array(self, name_template, row_offset=0): - xoffset = 0.0 - yoffset = 0.0 - for row in range(len(self.insts)): - inst = self.insts[row] - if row == 0: - inst.place(offset=[xoffset, yoffset + inst.height], mirror="MX") - elif row == len(self.insts)-1: - inst.place(offset=[xoffset, yoffset]) + pattern.append_row_to_block(bit_block, [top_corner]) + for row in range(1, self.row_size-1): + if row % 2 == 1: + pattern.append_row_to_block(bit_block, [rowend]) else: - if row % 2 ==0: - inst.place(offset=[xoffset, yoffset + inst.height], mirror="MX") - else: - inst.place(offset=[xoffset, yoffset]) - yoffset += inst.height + pattern.append_row_to_block(bit_block, [rowenda]) + pattern.append_row_to_block(bit_block, [bottom_corner]) + self.pattern = pattern(self, "row_cap_array_" + self.location, bit_block, num_rows=self.row_size, num_cols=self.column_size, num_cores_x=ceil(self.column_size/2), num_cores_y=ceil(self.row_size/2), name_template="row_cap_array" + self.location + "_r{0}_c{1}") + self.pattern.connect_array_raw() + + + def get_bitcell_pins(self, row, col): + """ + Creates a list of connections in the bitcell, + indexed by column and row, for instance use in bitcell_array + """ - def add_pins(self): - for row in range(self.rows + 2): + bitcell_pins = [] + bitcell_pins.append("vdd") # vdd + bitcell_pins.extend([x for x in self.all_wordline_names if x.endswith("_{0}".format(row))]) + + #bitcell_pins.extend([x for x in self.all_wordline_names if x.endswith("_{0}".format(row))]) + + return bitcell_pins + + def get_strap_pins(self, row, col): + + strap_pins = [] + + strap_pins.append("vdd") # vdd + strap_pins.append("vdd") # vpb + strap_pins.append("gnd") # vnb + + return strap_pins + + def create_all_wordline_names(self, row_size=None, start_row=0): + if row_size == None: + row_size = self.row_size + row_size = row_size - 2 + for row in range(start_row, row_size): for port in self.all_ports: - self.add_pin("wl_{}_{}".format(port, row), "OUTPUT") - self.add_pin("vdd", "POWER") - self.add_pin("gnd", "GROUND") + self.wordline_names[port].append("wl_{0}_{1}".format(port, row)) - def add_layout_pins(self): - """ Add the layout pins """ - for row in range(0, self.rows + 1): - if row > 0 and row < self.rows + 1: - wl_pin = self.cell_inst[row].get_pin("wl") - self.add_layout_pin(text="wl_0_{0}".format(row -1), - layer=wl_pin.layer, - offset=wl_pin.ll().scale(0, 1), - width=self.width, - height=wl_pin.height()) + self.all_wordline_names = [x for sl in zip(*self.wordline_names) for x in sl] + + def create_layout(self): - # Add vdd/gnd via stacks - for row in range(1, self.rows): - inst = self.cell_inst[row] - for pin_name in ["vdd", "gnd"]: - for pin in inst.get_pins(pin_name): - self.copy_layout_pin(inst, pin_name) + self.place_array() + self.add_layout_pins() + + self.add_boundary() + self.DRC_LVS() + diff --git a/technology/sky130/tech/tech.py b/technology/sky130/tech/tech.py index 8ee49f40..0d23da8a 100755 --- a/technology/sky130/tech/tech.py +++ b/technology/sky130/tech/tech.py @@ -817,6 +817,7 @@ blackbox_cells = ["sky130_fd_bd_sram__openram_dp_cell", "sky130_fd_bd_sram__sram_sp_cell_opt1a", "sky130_fd_bd_sram__openram_sp_cell_opt1a_dummy", + "sky130_fd_bd_sram__openram_sp_cell_opt1_dummy", "sky130_fd_bd_sram__sram_sp_cell_opt1_ce", "sky130_fd_bd_sram__sram_sp_cell_opt1", "sky130_fd_bd_sram__openram_sp_cell_opt1_replica",