diff --git a/.github/workflows/regress.yml b/.github/workflows/regress.yml index eb07071d..f9b46bb8 100644 --- a/.github/workflows/regress.yml +++ b/.github/workflows/regress.yml @@ -19,31 +19,30 @@ jobs: run: | rm -rf ~/.local/lib/python3.8/site-packages/openram* make library - - name: Build conda - run: | - ./install_conda.sh - name: PDK Install run: | - export OPENRAM_HOME="${{ github.workspace }}/compiler" - export OPENRAM_TECH="${{ github.workspace }}/technology" - export PDK_ROOT="${{ github.workspace }}/pdk" + nix --extra-experimental-features 'nix-command flakes' develop --command bash -lc ' + export OPENRAM_HOME="${{ github.workspace }}/compiler"; + export OPENRAM_TECH="${{ github.workspace }}/technology"; + export PDK_ROOT="${{ github.workspace }}/pdk"; # Add make targets to install PDKs of all technologies that need it - make sky130-pdk + make sky130-pdk; make sky130-install - name: Regress run: | - export OPENRAM_HOME="${{ github.workspace }}/compiler" - export OPENRAM_TECH="${{ github.workspace }}/technology" - export PDK_ROOT="${{ github.workspace }}/pdk" - export FREEPDK45="~/FreePDK45" + nix --extra-experimental-features 'nix-command flakes' develop --command bash -lc ' + export OPENRAM_HOME="${{ github.workspace }}/compiler"; + export OPENRAM_TECH="${{ github.workspace }}/technology"; + export PDK_ROOT="${{ github.workspace }}/pdk"; + export FREEPDK45="~/FreePDK45"; # KLAYOUT_PATH breaks klayout installation. Unset it for now... - unset KLAYOUT_PATH + unset KLAYOUT_PATH; #cd $OPENRAM_HOME/.. && make pdk && make install #export OPENRAM_TMP="${{ github.workspace }}/scn4me_subm_temp" #python3-coverage run -p $OPENRAM_HOME/tests/regress.py -j 12 -t scn4m_subm #$OPENRAM_HOME/tests/regress.py -j 24 -t scn4m_subm - cd $OPENRAM_HOME/tests - make clean + cd $OPENRAM_HOME/tests; + make clean; make -k -j 48 - name: Archive if: ${{ failure() }} diff --git a/.gitignore b/.gitignore index a46b5394..7b0c7889 100644 --- a/.gitignore +++ b/.gitignore @@ -19,7 +19,6 @@ compiler/tests/results/ open_pdks/ dist/ openram.egg-info/ -miniconda/ sky130A sky130B gf180mcuA diff --git a/MANIFEST.in b/MANIFEST.in index cb0967e3..790cc3e3 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,7 +2,6 @@ include Makefile include openram.mk include setpaths.sh include requirements.txt -include install_conda.sh include docker/* recursive-include compiler * recursive-include technology * diff --git a/Makefile b/Makefile index 802ac68d..f2c65a15 100644 --- a/Makefile +++ b/Makefile @@ -13,8 +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 ?= fc63b12883b4bf458ee8c756ba64c37063e1ffb9 SKY130_PDK ?= $(PDK_ROOT)/sky130A GF180_PDK ?= $(PDK_ROOT)/gf180mcuD @@ -58,8 +57,8 @@ INSTALL_BASE_DIRS := gds_lib mag_lib sp_lib lvs_lib calibre_lvs_lib klayout_lvs_ INSTALL_BASE := $(OPENRAM_HOME)/../technology/sky130 INSTALL_DIRS := $(addprefix $(INSTALL_BASE)/,$(INSTALL_BASE_DIRS)) -# If conda is installed, we will use ciel from there -CONDA_DIR := $(wildcard $(TOP_DIR)/miniconda) +# If nix is available, run ciel via nix develop +NIX_BIN := $(shell command -v nix 2>/dev/null) check-pdk-root: ifndef PDK_ROOT @@ -103,23 +102,21 @@ sky130-install: $(SRAM_LIB_DIR) sky130-pdk: $(SKY130_PDKS_DIR) @echo "Installing SKY130 via ciel..." -ifeq ($(CONDA_DIR),) +ifeq ($(NIX_BIN),) ciel enable --pdk sky130 $(SKY130_CIEL) else - source $(TOP_DIR)/miniconda/bin/activate && \ - ciel enable --pdk sky130 $(SKY130_CIEL) && \ - conda deactivate + nix --extra-experimental-features 'nix-command flakes' develop --command \ + ciel enable --pdk sky130 $(SKY130_CIEL) endif .PHONY: sky130-pdk gf180mcu-pdk: @echo "Installing GF180 via ciel..." -ifeq ($(CONDA_DIR),) +ifeq ($(NIX_BIN),) ciel enable --pdk gf180mcu $(GF180_CIEL) else - source $(TOP_DIR)/miniconda/bin/activate && \ - ciel enable --pdk gf180mcu $(GF180_CIEL) && \ - conda deactivate + nix --extra-experimental-features 'nix-command flakes' develop --command \ + ciel enable --pdk gf180mcu $(GF180_CIEL) endif .PHONY: gf180mcu-pdk diff --git a/__init__.py b/__init__.py index 758f50c4..48558a8a 100644 --- a/__init__.py +++ b/__init__.py @@ -23,22 +23,11 @@ if "OPENRAM_HOME" not in os.environ.keys(): __path__.insert(0, OPENRAM_HOME) -# Find the conda installer script -if os.path.exists(OPENRAM_HOME + "/install_conda.sh"): - CONDA_INSTALLER = OPENRAM_HOME + "/install_conda.sh" - CONDA_HOME = OPENRAM_HOME + "/miniconda" -elif os.path.exists(OPENRAM_HOME + "/../install_conda.sh"): - CONDA_INSTALLER = OPENRAM_HOME + "/../install_conda.sh" - CONDA_HOME = os.path.abspath(OPENRAM_HOME + "/../miniconda") -# Override CONDA_HOME if it's set as an environment variable -if "CONDA_HOME" in os.environ.keys(): - CONDA_HOME = os.environ["CONDA_HOME"] -# Add CONDA_HOME to environment variables just in case -try: - os.environ["CONDA_HOME"] = CONDA_HOME -except: - from openram import debug - debug.warning("Couldn't find conda setup directory.") +# Nix toolchain root (flake location) +NIX_HOME = os.path.abspath(OPENRAM_HOME + "/..") +if "NIX_HOME" in os.environ.keys(): + NIX_HOME = os.environ["NIX_HOME"] +os.environ["NIX_HOME"] = NIX_HOME # Import everything in globals.py 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 c56fa392..7d512fdc 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/characterizer/simulation.py b/compiler/characterizer/simulation.py index 1e6a34ab..18a42684 100644 --- a/compiler/characterizer/simulation.py +++ b/compiler/characterizer/simulation.py @@ -619,10 +619,10 @@ class simulation(): Gets the signal name associated with the bitlines in the bank. """ # FIXME: change to a solution that does not depend on the technology - if OPTS.tech_name == "sky130" and len(self.all_ports) == 1: - cell_mod = factory.create(module_type=OPTS.bitcell, version="opt1") - else: - cell_mod = factory.create(module_type=OPTS.bitcell) + #if OPTS.tech_name == "sky130" and len(self.all_ports) == 1: + # cell_mod = factory.create(module_type=OPTS.bitcell, version="opt1") + #else: + cell_mod = factory.create(module_type=OPTS.bitcell) cell_bl = cell_mod.get_bl_name(port) cell_br = cell_mod.get_br_name(port) diff --git a/compiler/characterizer/stimuli.py b/compiler/characterizer/stimuli.py index e5f44a2c..c184ed66 100644 --- a/compiler/characterizer/stimuli.py +++ b/compiler/characterizer/stimuli.py @@ -12,6 +12,7 @@ simulations as well. """ import os +import shlex import subprocess import numpy as np from openram import debug @@ -406,11 +407,15 @@ class stimuli(): spice_stdout = open("{0}spice_stdout.log".format(OPTS.openram_temp), 'w') spice_stderr = open("{0}spice_stderr.log".format(OPTS.openram_temp), 'w') - # Wrap the command with conda activate & conda deactivate + # Run spice in the Nix devShell when Nix-managed tools are enabled. # FIXME: Should use verify/run_script.py here but run_script doesn't return # the return code of the subprocess. File names might also mismatch. - from openram import CONDA_HOME - cmd = "/bin/bash -c 'source {0}/bin/activate && {1} && conda deactivate'".format(CONDA_HOME, cmd) + if OPTS.use_nix: + cmd = ( + "nix --extra-experimental-features 'nix-command flakes' " + "develop --command /bin/bash -lc {0}" + .format(shlex.quote(cmd)) + ) debug.info(2, cmd) proc = subprocess.run(cmd, stdout=spice_stdout, stderr=spice_stderr, shell=True) diff --git a/compiler/drc/custom_cell_properties.py b/compiler/drc/custom_cell_properties.py index d7972be1..6c5a7240 100644 --- a/compiler/drc/custom_cell_properties.py +++ b/compiler/drc/custom_cell_properties.py @@ -144,11 +144,11 @@ class _pgate: class bitcell(cell): - def __init__(self, port_order, port_types, port_map=None, storage_nets=["Q", "Q_bar"], mirror=None, end_caps=False): + def __init__(self, port_order, port_types, port_map=None, storage_nets=["Q", "Q_bar"], mirror=None, end_caps=False, has_corners=True): super().__init__(port_order, port_types, port_map) self.end_caps = end_caps - + self.has_corners = has_corners if not mirror: self.mirror = _mirror_axis(True, False) else: diff --git a/compiler/globals.py b/compiler/globals.py index 65bf8567..ea017aa6 100644 --- a/compiler/globals.py +++ b/compiler/globals.py @@ -67,11 +67,14 @@ def parse_args(): dest="num_sim_threads"), optparse.make_option("-v", "--verbose", action="count", - dest="verbose_level", + dest="5", help="Increase the verbosity level"), optparse.make_option("-t", "--tech", dest="tech_name", help="Technology name"), + optparse.make_option("-f", "--tech_file", + dest="tech_file", + help="Technology name"), optparse.make_option("-s", "--spice", dest="spice_name", help="Spice simulator executable name"), @@ -185,7 +188,7 @@ def init_openram(config_file, is_unit_test=False): read_config(config_file, is_unit_test) - install_conda() + install_nix() import_tech() @@ -206,17 +209,40 @@ def init_openram(config_file, is_unit_test=False): from openram import verify -def install_conda(): - """ Setup conda for default tools. """ +def install_nix(): + """Initialize Nix-based toolchain dependencies.""" - # Don't setup conda if not used - if not OPTS.use_conda or OPTS.is_unit_test: + # Don't setup tools during unit tests. + if OPTS.is_unit_test: return - debug.info(1, "Creating conda setup..."); + if not OPTS.use_nix or OPTS.is_unit_test: + return - from openram import CONDA_INSTALLER - subprocess.call(CONDA_INSTALLER) + debug.info(1, "Bootstrapping toolchain with Nix...") + + nix_exe = shutil.which("nix") + if nix_exe is None: + debug.error("Nix is required for automatic tool setup, but 'nix' was not found in PATH.", -1) + + repo_root = os.path.abspath(os.path.join(OPENRAM_HOME, "..")) + flake_file = os.path.join(repo_root, "flake.nix") + if not os.path.exists(flake_file): + debug.error("Expected Nix flake at {} for tool setup.".format(flake_file), -1) + + # Trigger materialization/build of the devShell dependencies once. + # Environment activation still happens outside OpenRAM via `nix develop`. + cmd = [ + nix_exe, + "--extra-experimental-features", "nix-command flakes", + "develop", + "--command", "true", + ] + result = subprocess.call(cmd, cwd=repo_root) + if result != 0: + debug.error("Failed to initialize Nix toolchain (nix develop returned {}).".format(result), -1) + + return def setup_bitcell(): @@ -443,14 +469,7 @@ def find_exe(check_exe): Check if the binary exists in any path dir and return the full path. """ - # Search for conda setup if used - if OPTS.use_conda: - from openram import CONDA_HOME - search_path = "{0}/bin{1}{2}".format(CONDA_HOME, - os.pathsep, - os.environ["PATH"]) - else: - search_path = os.environ["PATH"] + search_path = os.environ["PATH"] # Check if the preferred spice option exists in the path for path in search_path.split(os.pathsep): 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..c2948059 100644 --- a/compiler/modules/bitcell_array.py +++ b/compiler/modules/bitcell_array.py @@ -10,15 +10,17 @@ 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): """ Creates a rows x cols array of memory cells. Assumes bit-lines and word lines are connected by abutment. """ - def __init__(self, rows, cols, column_offset=0, name=""): - super().__init__(rows=rows, cols=cols, column_offset=column_offset, name=name) + def __init__(self, rows, cols, column_offset=0, row_offset=0, name="", left_rbl=None, right_rbl=None): + super().__init__(rows=rows, cols=cols, column_offset=column_offset, row_offset=row_offset, name=name) debug.info(1, "Creating {0} {1} x {2}".format(self.name, rows, cols)) self.add_comment("rows: {0} cols: {1}".format(rows, cols)) @@ -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,29 @@ 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)) + r = self.row_offset + c = self.column_offset + self.cell_inst={} + if self.cell.mirror.y: + core_block = [[0 for x in range(2)] for y in range(2)] + core_block[(0 + r) % 2][(0+c) %2] = geometry.instance(f"core_{(0 + r)%2}_{(0+c)%2}", mod=self.cell, is_bitcell=True, mirror='') + core_block[(0 + r) % 2][(1+c) %2] = geometry.instance(f"core_{(0 + r)%2}_{(1+c)%2}", mod=self.cell, is_bitcell=True, mirror='MY') + core_block[(1 + r) % 2][(0+c) %2] = geometry.instance(f"core_{(1 + r)%2}_{(0+c)%2}", mod=self.cell, is_bitcell=True, mirror='MX') + core_block[(1 + r) % 2][(1+c) %2] = geometry.instance(f"core_{(1 + r)%2}_{(1+c)%2}", 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 + self.row_offset) % 2][(0+self.column_offset) %2] = geometry.instance("core_0_0", mod=self.cell, is_bitcell=True) + core_block[(1 + self.row_offset) % 2][(0+self.column_offset) %2] = geometry.instance("core_1_0", mod=self.cell, is_bitcell=True, mirror='MX') + #print(r, c) + #print(core_block) - # If it is a "core" cell, it could be trimmed for sim time - if col>0 and col0 and row0 and col0 and row 0: + temp.extend(self.get_rbl_wordline_names(0)) if port == None: temp.extend(self.all_wordline_names) else: temp.extend(self.wordline_names[port]) if len(self.all_ports) > 1: temp.extend(self.get_rbl_wordline_names(1)) + + return temp def add_bitline_pins(self): @@ -157,14 +161,54 @@ class bitcell_base_array(design): height=wl_pin.height()) def route_supplies(self): - for inst in self.cell_inst.values(): - for pin_name in ["vdd", "gnd"]: - self.copy_layout_pin(inst, pin_name) + + vdd_found = False + gnd_found = False + + for inst in self.insts: + if 'vdd' in inst.mod.get_pin_names(): + vdd_found = True + if 'gnd' in inst.mod.get_pin_names(): + gnd_found = True + + power_name = 'vdd' + ground_name = 'gnd' + + if vdd_found == False or gnd_found == False: + for inst in self.insts: + if 'VDD' in inst.mod.get_pin_names(): + vdd_found = True + power_name = 'VDD' + + if 'GND' in inst.mod.get_pin_names(): + gnd_found = True + ground_name = 'GND' + + + if vdd_found is False or gnd_found is False: + from openram.tech import cell_properties + try: + power_name = cell_properties.power_name + except: + pass + try: + ground_name = cell_properties.ground_name + except: + pass + + + for inst in self.insts: + if power_name in inst.mod.get_pin_names(): + self.copy_layout_pin(inst, power_name, new_name='vdd') + if ground_name in inst.mod.get_pin_names(): + self.copy_layout_pin(inst, ground_name , new_name='gnd') def add_layout_pins(self): """ Add the layout pins """ - self.add_bitline_pins() - self.add_wl_pins() + if self.get_bitline_names(): + self.add_bitline_pins() + if self.get_wordline_names(): + self.add_wl_pins() def _adjust_x_offset(self, xoffset, col, col_offset): tempx = xoffset @@ -184,32 +228,8 @@ class bitcell_base_array(design): dir_x = True return (tempy, dir_x) - def place_array(self, name_template, row_offset=0): - # We increase it by a well enclosure so the precharges don't overlap our wells - self.height = self.row_size * self.cell.height - self.width = self.column_size * self.cell.width - - xoffset = 0.0 - for col in range(self.column_size): - yoffset = 0.0 - 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, 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 place_array(self): + self.pattern.place_array() def get_column_offsets(self): """ diff --git a/compiler/modules/capped_replica_bitcell_array.py b/compiler/modules/capped_replica_bitcell_array.py index c68ad824..d87ad247 100644 --- a/compiler/modules/capped_replica_bitcell_array.py +++ b/compiler/modules/capped_replica_bitcell_array.py @@ -20,7 +20,7 @@ class capped_replica_bitcell_array(bitcell_base_array): 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) + super().__init__(name, rows, cols, column_offset=0, row_offset=0) debug.info(1, "Creating {0} {1} x {2} rbls: {3} left_rbl: {4} right_rbl: {5}".format(self.name, rows, cols, @@ -51,10 +51,10 @@ class capped_replica_bitcell_array(bitcell_base_array): self.rbls = self.left_rbl + self.right_rbl # Two dummy rows plus replica even if we don't add the column - self.extra_rows = sum(self.rbl) + self.extra_rows = sum(self.rbl) + 2 # 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 + #if not self.cell.end_caps: + # self.extra_rows += 2 self.create_netlist() if not OPTS.netlist_only: @@ -73,6 +73,8 @@ class capped_replica_bitcell_array(bitcell_base_array): cols=self.column_size, rows=self.row_size, rbl=self.rbl, + column_offset=1, + row_offset=1, left_rbl=self.left_rbl, right_rbl=self.right_rbl) @@ -86,35 +88,39 @@ class capped_replica_bitcell_array(bitcell_base_array): rows=1, # dummy column + left replica column(s) column_offset=1, + row_offset=self.row_size+ self.extra_rows + 1, #add 1 to account for bottom col_cap mirror=0, - location="top") + location="top", + left_rbl=self.left_rbl, + right_rbl=self.right_rbl) self.col_cap_bottom = factory.create(module_type=col_cap_module_type, cols=self.column_size + len(self.rbls), rows=1, # dummy column + left replica column(s) column_offset=1, - mirror=0, - location="bottom") + row_offset=0, + mirror=(1+self.row_size+self.extra_rows) % 2, + location="bottom", + left_rbl=self.left_rbl, + right_rbl=self.right_rbl) # 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) + column_offset=0, + row_offset=0, + location="left") 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) + column_offset=1 + len(self.left_rbl) + self.column_size + len(self.right_rbl), + row_offset=0, + location="right") def add_pins(self): @@ -158,10 +164,21 @@ class capped_replica_bitcell_array(bitcell_base_array): self.unused_wordline_names = self.replica_bitcell_array.unused_wordline_names self.replica_array_wordline_names_with_grounded_wls = ["gnd" if x in self.unused_wordline_names else x for x in self.replica_bitcell_array.wordline_pin_list] + # Left/right row caps cover the full array height. Pad with gnd so the + # netlist list length matches the row cap (replica in the center); do + # not use col cap wordline heuristics. + + n_rowcap_wl = len(self.row_cap_left.get_wordline_names()) + n_rba_wl = len(self.replica_array_wordline_names_with_grounded_wls) + + self.wordline_pin_list = [] - self.wordline_pin_list.extend(["gnd"] * len(self.col_cap_top.get_wordline_names())) + + if self.rbls: + self.wordline_pin_list.extend(["gnd"] * len(self.rbls)) self.wordline_pin_list.extend(self.replica_array_wordline_names_with_grounded_wls) - self.wordline_pin_list.extend(["gnd"] * len(self.col_cap_bottom.get_wordline_names())) + if self.rbls: + self.wordline_pin_list.extend(["gnd"] * len(self.rbls)) self.add_pin_list(self.used_wordline_names, "INPUT") @@ -177,7 +194,7 @@ class capped_replica_bitcell_array(bitcell_base_array): # 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)) + mod=self.col_cap_bottom,)) self.connect_inst(self.bitline_pin_list + ["gnd"] * len(self.col_cap_bottom.get_wordline_names()) + self.supplies) self.dummy_row_insts.append(self.add_inst(name="dummy_row_top", mod=self.col_cap_top)) @@ -188,6 +205,10 @@ class capped_replica_bitcell_array(bitcell_base_array): self.dummy_col_insts.append(self.add_inst(name="dummy_col_left", mod=self.row_cap_left)) self.connect_inst(["dummy_left_" + bl for bl in self.row_cap_left.all_bitline_names] + self.wordline_pin_list + self.supplies) + + #print(self.dummy_col_insts[0].mod.pins) + #print(["dummy_left_" + bl for bl in self.row_cap_left.all_bitline_names] + self.wordline_pin_list + self.supplies) + self.dummy_col_insts.append(self.add_inst(name="dummy_col_right", mod=self.row_cap_right)) self.connect_inst(["dummy_right_" + bl for bl in self.row_cap_right.all_bitline_names] + self.wordline_pin_list + self.supplies) @@ -201,40 +222,34 @@ class capped_replica_bitcell_array(bitcell_base_array): # row-based or column based power and ground lines. self.vertical_pitch = 1.1 * getattr(self, "{}_pitch".format(self.supply_stack[0])) self.horizontal_pitch = 1.1 * getattr(self, "{}_pitch".format(self.supply_stack[2])) - # FIXME: custom sky130 replica module has a better version of this offset - self.unused_offset = vector(0.25, 0.25) - - # This is a bitcell x bitcell offset to scale - self.bitcell_offset = vector(self.cell.width, self.cell.height) - self.col_end_offset = vector(self.cell.width, self.cell.height) - self.row_end_offset = vector(self.cell.width, self.cell.height) # Everything is computed with the replica array - self.replica_bitcell_array_inst.place(offset=self.unused_offset) + self.replica_bitcell_array_inst.place(offset=0) self.add_end_caps() - # shift everything up and right to account for cap cells - self.translate_all(self.bitcell_offset.scale(-1, -1)) - - self.width = self.dummy_col_insts[1].rx() + self.unused_offset.x - self.height = self.dummy_row_insts[1].uy() - - self.add_layout_pins() + ll = vector(-1 * self.dummy_col_insts[0].width, -1 * self.dummy_row_insts[0].height) + self.translate_all(ll) + self.capped_rba_width = self.dummy_col_insts[0].width + self.dummy_row_insts[0].width + self.dummy_col_insts[1].width + self.capped_rba_height = self.dummy_col_insts[0].height + + self.route_power_ring(self.supply_stack[2], self.supply_stack[0]) 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.reset_coordinates() + self.add_layout_pins() self.add_boundary() - self.DRC_LVS() + + + def route_power_ring(self, v_layer, h_layer): + self.bbox = (vector(0,0), vector(self.capped_rba_width, self.capped_rba_height)) + self.supply_rail_width = drc["minwidth_m3"] + self.supply_rail_pitch = 6 * self.supply_rail_width + self.add_power_ring(v_layer=v_layer, h_layer=h_layer) def get_main_array_top(self): return self.replica_bitcell_array_inst.by() + self.replica_bitcell_array.get_main_array_top() @@ -248,7 +263,7 @@ class capped_replica_bitcell_array(bitcell_base_array): def get_main_array_right(self): return self.replica_bitcell_array_inst.lx() + self.replica_bitcell_array.get_main_array_right() - # FIXME: these names need to be changed to reflect what they're actually returning + #FIXME: these names need to be changed to reflect what they're actually returning def get_replica_top(self): return self.dummy_row_insts[1].by() @@ -261,7 +276,6 @@ class capped_replica_bitcell_array(bitcell_base_array): def get_replica_right(self): return self.dummy_col_insts[1].rx() - def get_column_offsets(self): """ Return an array of the x offsets of all the regular bits @@ -273,47 +287,50 @@ class capped_replica_bitcell_array(bitcell_base_array): def add_end_caps(self): """ Add dummy cells or end caps around the array """ - # Far top dummy row (first row above array is NOT flipped if even number of rows) - flip_dummy = (self.row_size + self.rbl[1]) % 2 - dummy_row_offset = self.bitcell_offset.scale(0, flip_dummy) + self.replica_bitcell_array_inst.ul() - self.dummy_row_insts[1].place(offset=dummy_row_offset, - mirror="MX" if flip_dummy else "R0") + # Far top dummy row + offset = self.replica_bitcell_array_inst.ul() + self.dummy_row_insts[1].place(offset=offset) - # Far bottom dummy row (first row below array IS flipped) - flip_dummy = (self.rbl[0] + 1) % 2 - dummy_row_offset = self.bitcell_offset.scale(0, flip_dummy - 1) + self.unused_offset - self.dummy_row_insts[0].place(offset=dummy_row_offset, - mirror="MX" if flip_dummy else "R0") + # Far bottom dummy row + dummy_row_height = vector(0, self.dummy_row_insts[0].height) + offset = self.replica_bitcell_array_inst.ll() - dummy_row_height + self.dummy_row_insts[0].place(offset=offset) + # Far left dummy col - # Shifted down by the number of left RBLs even if we aren't adding replica column to this bitcell array - dummy_col_offset = self.bitcell_offset.scale(-1, -1) + self.unused_offset - self.dummy_col_insts[0].place(offset=dummy_col_offset) + dummy_col_width = vector(self.dummy_col_insts[0].width, 0) + offset = self.dummy_row_insts[0].ll() - dummy_col_width + if self.dummy_col_insts[0].mod.cell.has_corners is False: + offset += vector(0, dummy_row_height.y) + self.dummy_col_insts[0].place(offset=offset) # Far right dummy col - # Shifted down by the number of left RBLs even if we aren't adding replica column to this bitcell array - dummy_col_offset = self.bitcell_offset.scale(0, -1) + self.replica_bitcell_array_inst.lr() - self.dummy_col_insts[1].place(offset=dummy_col_offset) + offset = self.dummy_row_insts[0].lr() + if self.dummy_col_insts[0].mod.cell.has_corners is False: + offset += vector(0, dummy_row_height.y) + self.dummy_col_insts[1].place(offset=offset) def add_layout_pins(self): + for pin_name in self.used_wordline_names + self.bitline_pin_list: pin = self.replica_bitcell_array_inst.get_pin(pin_name) if "wl" in pin_name: # wordlines pin_offset = pin.ll().scale(0, 1) - pin_width = self.width + pin_width = self.capped_rba_width pin_height = pin.height() else: # bitlines pin_offset = pin.ll().scale(1, 0) pin_width = pin.width() - pin_height = self.height + pin_height = self.capped_rba_height self.add_layout_pin(text=pin_name, layer=pin.layer, offset=pin_offset, width=pin_width, height=pin_height) + def route_supplies(self): @@ -321,72 +338,100 @@ class capped_replica_bitcell_array(bitcell_base_array): bitcell = factory.create(module_type="pbitcell") else: bitcell = getattr(props, "bitcell_{}port".format(OPTS.num_ports)) + top = True + bottom = True + left = False + right = False + + if top: + inst = self.dummy_row_insts[1] + if 'vdd' in inst.mod.pins: + array_pins = inst.get_pins('vdd') + for array_pin in array_pins: + supply_pin = self.top_vdd_pin + self.add_path(array_pin.layer, [array_pin.center(), vector(array_pin.center()[0], supply_pin.center()[1])]) + self.add_via_stack_center(from_layer = array_pin.layer, + to_layer = supply_pin.layer, + offset = vector(array_pin.center()[0], supply_pin.center()[1])) - vdd_dir = bitcell.vdd_dir - gnd_dir = bitcell.gnd_dir + array_pins = inst.get_pins('gnd') + for array_pin in array_pins: + supply_pin = self.top_gnd_pin + self.add_path(array_pin.layer, [array_pin.center(), vector(array_pin.center()[0], supply_pin.center()[1])]) + self.add_via_stack_center(from_layer = array_pin.layer, + to_layer = supply_pin.layer, + offset = vector(array_pin.center()[0], supply_pin.center()[1])) + if bottom: + inst = self.dummy_row_insts[0] + if 'vdd' in inst.mod.pins: + array_pins = inst.get_pins('vdd') + for array_pin in array_pins: + supply_pin = self.bottom_vdd_pin + self.add_path(array_pin.layer, [array_pin.center(), vector(array_pin.center()[0], supply_pin.center()[1])]) + self.add_via_stack_center(from_layer = array_pin.layer, + to_layer = supply_pin.layer, + offset = vector(array_pin.center()[0], supply_pin.center()[1])) - # vdd/gnd are only connected in the perimeter cells - supply_insts = self.dummy_col_insts + self.dummy_row_insts + array_pins = inst.get_pins('gnd') + for array_pin in array_pins: + supply_pin = self.bottom_gnd_pin + self.add_path(array_pin.layer, [array_pin.center(), vector(array_pin.center()[0], supply_pin.center()[1])]) + self.add_via_stack_center(from_layer = array_pin.layer, + to_layer = supply_pin.layer, + offset = vector(array_pin.center()[0], supply_pin.center()[1])) + if left: + inst = self.dummy_col_insts[0] + if 'vnd' in inst.mod.pins: + array_pins = inst.get_pins('vnd') + for array_pin in array_pins: + supply_pin = self.left_vdd_pin + self.add_path(array_pin.layer, [array_pin.center(), vector(supply_pin.center()[0], array_pin.center()[1])]) + self.add_via_stack_center(from_layer = array_pin.layer, + to_layer = supply_pin.layer, + offset = vector(supply_pin.center()[0], array_pin.center()[1])) - # 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) + array_pins = inst.get_pins('gnd') + for array_pin in array_pins: + supply_pin = self.left_gnd_pin + self.add_path(array_pin.layer, [array_pin.center(), vector(supply_pin.center()[0], array_pin.center()[1])]) + self.add_via_stack_center(from_layer = array_pin.layer, + to_layer = supply_pin.layer, + offset = vector(supply_pin.center()[0], array_pin.center()[1])) + if right: + inst = self.dummy_col_insts[1] + if 'vdd' in inst.mod.pins: + array_pins = inst.get_pins('vdd') + for array_pin in array_pins: + supply_pin = self.right_vdd_pin + self.add_path(array_pin.layer, [array_pin.center(), vector(supply_pin.center()[0], array_pin.center()[1])]) + self.add_via_stack_center(from_layer = array_pin.layer, + to_layer = supply_pin.layer, + offset = vector(supply_pin.center()[0], array_pin.center()[1])) + array_pins = inst.get_pins('gnd') + for array_pin in array_pins: + supply_pin = self.right_gnd_pin + self.add_path(array_pin.layer, [array_pin.center(), vector(supply_pin.center()[0], array_pin.center()[1])]) + self.add_via_stack_center(from_layer = array_pin.layer, + to_layer = supply_pin.layer, + offset = vector(supply_pin.center()[0], array_pin.center()[1])) + 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(): + for wl_name in inst.mod.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) + self.connect_side_pin(pin, "left", self.left_gnd_pin.cx()) + self.connect_side_pin(pin, "right", self.right_gnd_pin.cx()) # Ground the unused replica wordlines for wl_name in self.unused_wordline_names: pin = self.replica_bitcell_array_inst.get_pin(wl_name) - self.connect_side_pin(pin, "left", self.left_gnd_locs[0].x) - self.connect_side_pin(pin, "right", self.right_gnd_locs[0].x) + self.connect_side_pin(pin, "left", self.left_gnd_pin.cx()) + self.connect_side_pin(pin, "right", self.right_gnd_pin.cx()) def route_side_pin(self, name, side, offset_multiple=1): """ diff --git a/compiler/modules/col_cap_array.py b/compiler/modules/col_cap_array.py index 3dabb640..0d60dc57 100644 --- a/compiler/modules/col_cap_array.py +++ b/compiler/modules/col_cap_array.py @@ -6,14 +6,16 @@ from openram.sram_factory import factory from openram import OPTS from .bitcell_base_array import bitcell_base_array - +from openram.base import geometry +from .pattern import pattern +from math import ceil class col_cap_array(bitcell_base_array): """ Generate a dummy row/column for the replica array. """ - def __init__(self, rows, cols, column_offset=0, mirror=0, location="", name=""): - super().__init__(rows=rows, cols=cols, column_offset=column_offset, name=name) + def __init__(self, rows, cols, column_offset=0, row_offset=0, mirror=0, location="", name="",left_rbl=[],right_rbl=[]): + super().__init__(rows=rows, cols=cols, column_offset=column_offset, row_offset=row_offset, name=name) self.mirror = mirror self.location = location @@ -37,28 +39,44 @@ class col_cap_array(bitcell_base_array): def create_layout(self): - self.place_array("dummy_r{0}_c{1}", self.mirror) + self.place_array() self.add_layout_pins() - self.height = self.dummy_cell.height - self.width = self.column_size * self.cell.width + #self.height = self.dummy_cell.height + #self.width = self.column_size * self.cell.width self.add_boundary() self.DRC_LVS() def add_modules(self): """ Add the modules used in this design """ - self.dummy_cell = factory.create(module_type="col_cap_{}".format(OPTS.bitcell)) + self.colend = factory.create(module_type="col_cap_{}".format(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.dummy_cell) + # self.connect_inst(self.get_bitcell_pins(row, col)) 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.dummy_cell) - self.connect_inst(self.get_bitcell_pins(row, col)) + self.cell_inst={} + if self.row_offset % 2 == 0: + bit_row = [geometry.instance("00_colend", mod=self.colend, is_bitcell=True, mirror="MY")]\ + + [geometry.instance("01_colend", mod=self.colend, is_bitcell=True)] + else: + bit_row = [geometry.instance("00_colend", mod=self.colend, is_bitcell=True, mirror="XY")]\ + + [geometry.instance("01_colend", mod=self.colend, is_bitcell=True, mirror="MX")] + + bit_row = pattern.rotate_list(bit_row, self.column_offset * 2) + bit_block = [] + pattern.append_row_to_block(bit_block, bit_row) + self.pattern = pattern(self, "col_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="col_cap_array" + self.location + "_r{0}_c{1}") + self.pattern.connect_array() def get_bitcell_pins(self, row, col): """ @@ -79,24 +97,24 @@ class col_cap_array(bitcell_base_array): return bitcell_pins - def add_layout_pins(self): - """ Add the layout pins """ + # def add_layout_pins(self): + # """ Add the layout pins """ - column_list = self.cell.get_all_bitline_names() + # column_list = self.cell.get_all_bitline_names() - for col in range(self.column_size): - for cell_column in column_list: - bl_pin = self.cell_inst[0, col].get_pin(cell_column) - self.add_layout_pin(text=cell_column + "_{0}".format(col), - layer=bl_pin.layer, - offset=bl_pin.ll().scale(1, 0), - width=bl_pin.width(), - height=self.height) + # for col in range(self.column_size): + # for cell_column in column_list: + # bl_pin = self.cell_inst[0, col].get_pin(cell_column) + # self.add_layout_pin(text=cell_column + "_{0}".format(col), + # layer=bl_pin.layer, + # offset=bl_pin.ll().scale(1, 0), + # width=bl_pin.width(), + # height=self.height) - # Add vdd/gnd via stacks - 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"]: - for pin in inst.get_pins(pin_name): - self.copy_layout_pin(inst, pin_name) + # # Add vdd/gnd via stacks + # 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"]: + # for pin in inst.get_pins(pin_name): + # self.copy_layout_pin(inst, pin_name) diff --git a/compiler/modules/dummy_array.py b/compiler/modules/dummy_array.py index b190d50b..b6b72913 100644 --- a/compiler/modules/dummy_array.py +++ b/compiler/modules/dummy_array.py @@ -6,16 +6,20 @@ from openram.sram_factory import factory from openram import OPTS from .bitcell_base_array import bitcell_base_array - +from openram.base import geometry +from .pattern import pattern class dummy_array(bitcell_base_array): """ Generate a dummy row/column for the replica array. """ - 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 + def __init__(self, rows, cols, column_offset=0, row_offset=0 ,mirror=0, location="", name="", left_rbl=0, right_rbl=0): + super().__init__(rows=rows, cols=cols, column_offset=column_offset, row_offset=row_offset, name=name) + self.location = location + self.row_offset = row_offset + self.mirror = mirror + self.create_netlist() if not OPTS.netlist_only: self.create_layout() @@ -32,7 +36,7 @@ class dummy_array(bitcell_base_array): def create_layout(self): - self.place_array("dummy_r{0}_c{1}", self.mirror) + self.place_array() self.add_layout_pins() @@ -50,13 +54,24 @@ class dummy_array(bitcell_base_array): 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.dummy_cell) - self.connect_inst(self.get_bitcell_pins(row, col)) + self.cell_inst={} + r = self.row_offset + c = self.column_offset + if self.cell.mirror.y: + core_block = [[0 for x in range(2)] for y in range(2)] + core_block[(0 + r) % 2][(0+c) %2] = geometry.instance(f"core_{(0 + r)%2}_{(0+c)%2}", mod=self.dummy_cell, is_bitcell=True, mirror='') + core_block[(0 + r) % 2][(1+c) %2] = geometry.instance(f"core_{(0 + r)%2}_{(1+c)%2}", mod=self.dummy_cell, is_bitcell=True, mirror='MY') + core_block[(1 + r) % 2][(0+c) %2] = geometry.instance(f"core_{(1 + r)%2}_{(0+c)%2}", mod=self.dummy_cell, is_bitcell=True, mirror='MX') + core_block[(1 + r) % 2][(1+c) %2] = geometry.instance(f"core_{(1 + r)%2}_{(1+c)%2}", mod=self.dummy_cell, is_bitcell=True, mirror='XY') + else: + core_block = [[0 for x in range(1)] for y in range(2)] + core_block[(0 + self.row_offset) % 2][(0+self.column_offset) %2] = geometry.instance("core_0_0", mod=self.dummy_cell, is_bitcell=True) + core_block[(1 + self.row_offset) % 2][(0+self.column_offset) %2] = geometry.instance("core_1_0", mod=self.dummy_cell, is_bitcell=True, mirror='MX') + #print(r, c) + #print(core_block) + + self.pattern = pattern(self, "dummy_array", core_block, num_rows=self.row_size, num_cols=self.column_size, name_template="bit_r{0}_c{1}") + self.pattern.connect_array() def add_pins(self): # bitline pins are not added because they are floating @@ -68,47 +83,6 @@ class dummy_array(bitcell_base_array): self.add_pin("vdd", "POWER") self.add_pin("gnd", "GROUND") - def add_layout_pins(self): - """ Add the layout pins """ - - # Add the bitline metal, but not as pins since they are going to just be floating - # For some reason, LVS has an issue if we don't add this metal - 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]) - self.add_layout_pin(text="bl_{0}_{1}".format(port, col), - 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]) - self.add_layout_pin(text="br_{0}_{1}".format(port, col), - layer=br_pin.layer, - offset=br_pin.ll().scale(1, 0), - width=br_pin.width(), - height=self.height) - - wl_names = self.cell.get_all_wl_names() - for row in range(self.row_size): - for port in self.all_ports: - wl_pins = self.cell_inst[row, 0].get_pins(wl_names[port]) - for wl_pin in wl_pins: - 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): - - # Copy a vdd/gnd layout pin from every cell - 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) - def input_load(self): # FIXME: This appears to be old code from previous characterization. Needs to be updated. wl_wire = self.gen_wl_wire() diff --git a/compiler/modules/orig_bitcell_array.py b/compiler/modules/orig_bitcell_array.py deleted file mode 100644 index ad48b19e..00000000 --- a/compiler/modules/orig_bitcell_array.py +++ /dev/null @@ -1,116 +0,0 @@ -# See LICENSE for licensing information. -# -# Copyright (c) 2016-2024 Regents of the University of California and The Board -# of Regents for the Oklahoma Agricultural and Mechanical College -# (acting for and on behalf of Oklahoma State University) -# All rights reserved. -# -from openram.sram_factory import factory -from openram.tech import drc, spice -from openram import OPTS -from .bitcell_base_array import bitcell_base_array - - -class bitcell_array(bitcell_base_array): - """ - Creates a rows x cols array of memory cells. Assumes bit-lines - and word line is connected by abutment. - Connects the word lines and bit lines. - """ - def __init__(self, cols, rows, name, column_offset=0): - super().__init__(cols, rows, name, column_offset) - - self.create_netlist() - if not OPTS.netlist_only: - self.create_layout() - - # We don't offset this because we need to align - # the replica bitcell in the control logic - # self.offset_all_coordinates() - - def create_netlist(self): - """ Create and connect the netlist """ - self.add_modules() - self.add_pins() - self.create_instances() - - def create_layout(self): - - self.place_array("bit_r{0}_c{1}") - - self.add_layout_pins() - - self.add_boundary() - - self.DRC_LVS() - - def add_modules(self): - """ Add the modules used in this design """ - 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(col, row)) - - 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_wl_wire(self): - if OPTS.netlist_only: - width = 0 - else: - width = self.width - wl_wire = self.generate_rc_net(int(self.column_size), width, drc("minwidth_m1")) - wl_wire.wire_c = 2 * spice["min_tx_gate_c"] + wl_wire.wire_c # 2 access tx gate per cell - return wl_wire - - 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 get_wordline_cin(self): - """Get the relative input capacitance from the wordline connections in all the bitcell""" - # A single wordline is connected to all the bitcells in a single row meaning the capacitance depends on the # of columns - bitcell_wl_cin = self.cell.get_wl_cin() - total_cin = bitcell_wl_cin * self.column_size - return total_cin - - def graph_exclude_bits(self, targ_row, targ_col): - """Excludes bits in column from being added to graph except target""" - # Function is not robust with column mux configurations - for row in range(self.row_size): - for col in range(self.column_size): - if row == targ_row and col == targ_col: - continue - self.graph_inst_exclude.add(self.cell_inst[row, col]) - - def get_cell_name(self, inst_name, row, col): - """Gets the spice name of the target bitcell.""" - return inst_name + "{}x".format(OPTS.hier_seperator) + self.cell_inst[row, col].name, self.cell_inst[row, col] diff --git a/compiler/modules/pattern.py b/compiler/modules/pattern.py new file mode 100644 index 00000000..9c52436d --- /dev/null +++ b/compiler/modules/pattern.py @@ -0,0 +1,219 @@ +# 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.geometry import instance,geometry +from typing import List +from typing import Optional +from openram.base import design +from openram.globals import OPTS +from math import ceil, floor +from copy import deepcopy + +class pattern(): + """ + This class is used to desribe the internals of a bitcell array. It describes + instance modules, rotation, and ordering + + """ + block = List[List[instance]] + def __init__(self, + parent_design: design, + name:str, + core_block:block, + num_rows:int, + num_cols:int, + name_template, + num_cores_x: Optional[int] = 0, + num_cores_y: Optional[int] = 0, + cores_per_x_block: int = 1, + cores_per_y_block: int = 1, + x_block: Optional[block] = None, + y_block: Optional[block] = None, + xy_block: Optional[block] = None, + initial_x_block:bool = False, + initial_y_block:bool = False, + final_x_block:bool = False, + final_y_block:bool = False, + ): + """ + a "block" is a 2d list of instances + core_block defines the main block that is tiled + num_core defines the number of times the core block is to be tiled + (i.e. bitcells in dimension / bitcells in core_block) + x_block defines a block that is inserted to the right of the core_block + y_block defines a block that is inserted below the core block + xy_block defines a block that is a inserted where the x_block and y_block intercept + the initial and final booleans determine whether the pattern begins and/or ends with x/y blocks + """ + self.parent_design = parent_design + self.name = name + self.core_block = core_block + self.num_rows = num_rows + self.num_cols = num_cols + self.name_template = name_template + self.num_cores_x = num_cores_x + self.num_cores_y = num_cores_y + if num_cores_x == 0: + self.num_cores_x = ceil(num_cols/len(core_block[0])) + if num_cores_y == 0: + self.num_cores_y = ceil(num_rows/len(core_block)) + + self.cores_per_x_block = cores_per_x_block + self.cores_per_y_block = cores_per_y_block + self.x_block = x_block + self.y_block = y_block + self.xy_block = xy_block + self.initial_x_block = initial_x_block + self.initial_y_block = initial_y_block + self.final_x_block = final_x_block + self.final_y_block = final_y_block + self.bits_per_row = ceil(self.num_rows/self.num_cores_x) + self.bits_per_col = ceil(self.num_cols/self.num_cores_y) + self.bit_rows = [] + self.bit_cols = [] + self.parent_design.all_inst = {} + if not OPTS.netlist_only: + self.verify_interblock_dimensions() + + def compute_and_verify_intrablock_dimensions(self, block: block) -> 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: + + 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 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[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..433f03f5 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 @@ -22,8 +24,8 @@ class replica_bitcell_array(bitcell_base_array): Requires a regular bitcell array and (if using replica topology) 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) + def __init__(self, rows, cols, rbl=None, left_rbl=None, right_rbl=None, column_offset=0, row_offset=0, name=""): + super().__init__(name=name, rows=rows, cols=cols, column_offset=column_offset, row_offset=row_offset) debug.info(1, "Creating {0} {1} x {2} rbls: {3} left_rbl: {4} right_rbl: {5}".format(self.name, rows, cols, @@ -33,6 +35,9 @@ class replica_bitcell_array(bitcell_base_array): 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_offset=column_offset + self.row_offset=row_offset + self.column_size = cols self.row_size = rows # This is how many RBLs are in all the arrays @@ -74,9 +79,12 @@ 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)+ self.column_offset, + row_offset=len(self.left_rbl)+ self.row_offset, cols=self.column_size, - rows=self.row_size) + rows=self.row_size, + left_rbl=self.left_rbl, + right_rbl=self.right_rbl) # Replica bitlines self.replica_columns = {} @@ -87,29 +95,36 @@ 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) + rbc_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 + rbc_offset = len(self.left_rbl) + self.column_size else: continue self.replica_columns[port] = factory.create(module_type="replica_column", rows=self.row_size, rbl=self.rbl, - column_offset=column_offset, + column_offset=rbc_offset + self.column_offset, 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: + dummy_offset = 0 + elif port in self.right_rbl: + dummy_offset = self.row_size + len(self.left_rbl) + else: + dummy_offset = 0 + + 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=dummy_offset + self.row_offset, + column_offset=len(self.left_rbl) + self.row_offset) def add_pins(self): @@ -213,7 +228,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 +240,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 +295,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 +330,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 +362,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..d3790050 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): """ @@ -29,7 +30,7 @@ class replica_column(bitcell_base_array): self.row_start = rbl[0] # End of regular word line rows self.row_end = self.row_start + rows - super().__init__(rows=self.row_size, cols=1, column_offset=column_offset, name=name) + super().__init__(rows=self.row_size, cols=1, column_offset=column_offset, row_offset=0, name=name) self.rows = rows self.left_rbl = rbl[0] @@ -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,38 @@ 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, mirror='MY') + else: + core_block[row][0] = geometry.instance("rbc_{}".format(row), mod=self.replica_cell, is_bitcell=True, mirror='XY') 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, mirror='MY') + else: + core_block[row][0] = geometry.instance("rbc_{}".format(row), mod=self.dummy_cell, is_bitcell=True, mirror='XY') - 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()) + if self.column_offset % 2 == 0: + if core_block[row][0].mirror=='MY': + core_block[row][0].mirror='' + else: + core_block[row][0].mirror='MX' - 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 + 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 +141,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..c421fb42 100644 --- a/compiler/modules/row_cap_array.py +++ b/compiler/modules/row_cap_array.py @@ -6,16 +6,21 @@ 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 +from math import ceil 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=""): - super().__init__(rows=rows, cols=cols, column_offset=column_offset, name=name) + def __init__(self, rows, cols, column_offset=0, row_offset=0, mirror=0, location="", name=""): + super().__init__(rows=rows, cols=cols, column_offset=column_offset, row_offset=row_offset, name=name) self.mirror = mirror - self.no_instances = True + self.location = location + self.row_offset = row_offset + self.column_offset = column_offset + #self.no_instances = True self.create_netlist() if not OPTS.netlist_only: self.create_layout() @@ -32,7 +37,7 @@ class row_cap_array(bitcell_base_array): def create_layout(self): - self.place_array("dummy_r{0}_c{1}", self.mirror) + self.place_array() self.add_layout_pins() self.width = max([x.rx() for x in self.insts]) @@ -43,19 +48,41 @@ class row_cap_array(bitcell_base_array): def add_modules(self): """ Add the modules used in this design """ - self.dummy_cell = factory.create(module_type="row_cap_{}".format(OPTS.bitcell)) + self.row_cap = factory.create(module_type="row_cap_{}".format(OPTS.bitcell)) 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(0, self.row_size): - name = "bit_r{0}_c{1}".format(row, col) - self.cell_inst[row, col]=self.add_inst(name=name, - mod=self.dummy_cell) - self.connect_inst(self.get_bitcell_pins(row, col)) + self.all_inst={} + self.cell_inst={} + + bit_block = [] + + if self.column_offset % 2 == 0: + #top_corner = geometry.instance("row_cap_top_corner", mod=self.top_corner, is_bitcell=False, mirror="MY") + #bottom_corner = geometry.instance("row_cap_bottom_corner", mod=self.bottom_corner, is_bitcell=False, mirror="XY") + rowend = geometry.instance("row_cap_rowend", mod=self.row_cap, is_bitcell=True, mirror="MX") + rowend_m = geometry.instance("row_cap_rowend_m", mod=self.row_cap, is_bitcell=True, mirror="") + else: + #top_corner = geometry.instance("row_cap_top_corner", mod=self.top_corner, is_bitcell=False) + #bottom_corner = geometry.instance("row_cap_bottom_corner", mod=self.bottom_corner, is_bitcell=False, mirror="MX") + rowend = geometry.instance("row_cap_rowend", mod=self.row_cap, is_bitcell=True, mirror="XY") + rowend_m = geometry.instance("row_cap_rowend_m", mod=self.row_cap, is_bitcell=True, mirror="MY") + #pattern.append_row_to_block(bit_block, [top_corner]) + for row in range(0, self.row_size): + if row % 2 == 0: + pattern.append_row_to_block(bit_block, [rowend]) + else: + pattern.append_row_to_block(bit_block, [rowend_m]) + + #pattern.append_row_to_block(bit_block, [bottom_corner]) + if self.cell.has_corners is False: + num_rows = self.row_size - 2 + else: + num_rows = self.row_size + self.pattern = pattern(self, "row_cap_array_" + self.location, bit_block, num_rows=num_rows, 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): """ @@ -69,44 +96,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[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/options.py b/compiler/options.py index d97ee70e..229f7c3a 100644 --- a/compiler/options.py +++ b/compiler/options.py @@ -151,9 +151,9 @@ class options(optparse.Values): ################### # Top process that was ran (openram, memchar, memfunc) top_process = None - # Use conda to install the default tools - # (existing tools will be used if disabled) - use_conda = True + # Use Nix to initialize the default open-source toolchain. + # If disabled, OpenRAM uses whatever tools are already in PATH. + use_nix = True # Variable to select the variant of spice spice_name = None # The spice executable being used which is derived from the user PATH. diff --git a/compiler/sram_config.py b/compiler/sram_config.py index 369ae3d0..bdd6b245 100644 --- a/compiler/sram_config.py +++ b/compiler/sram_config.py @@ -141,12 +141,12 @@ class sram_config: + " Bank addr size: {}".format(self.bank_addr_size)) num_ports = OPTS.num_rw_ports + OPTS.num_r_ports + OPTS.num_w_ports - if num_ports == 1: - if ((self.num_cols + num_ports + self.num_spare_cols) % self.array_col_multiple != 0): - debug.error("Invalid number of cols including rbl(s): {}. Total cols must be divisible by {}".format(self.num_cols + num_ports + self.num_spare_cols, self.array_col_multiple), -1) - - if ((self.num_rows + num_ports) % self.array_row_multiple != 0): - debug.error("invalid number of rows including dummy row(s): {}. Total cols must be divisible by {}".format(self.num_rows + num_ports, self.array_row_multiple), -1) + #if num_ports == 1: + # if ((self.num_cols + num_ports + self.num_spare_cols) % self.array_col_multiple != 0): + # debug.error("Invalid number of cols including rbl(s): {}. Total cols must be divisible by {}".format(self.num_cols + num_ports + self.num_spare_cols, self.array_col_multiple), -1)# + # + # if ((self.num_rows + num_ports) % self.array_row_multiple != 0): + # debug.error("invalid number of rows including dummy row(s): {}. Total cols must be divisible by {}".format(self.num_rows + num_ports, self.array_row_multiple), -1) def estimate_words_per_row(self, tentative_num_cols, word_size): """ diff --git a/compiler/tests/05_bitcell_array_test.py b/compiler/tests/05_bitcell_array_test.py index bd5d5f01..7a6d3aad 100755 --- a/compiler/tests/05_bitcell_array_test.py +++ b/compiler/tests/05_bitcell_array_test.py @@ -31,7 +31,7 @@ class array_test(openram_test): num_spare_rows = 0 num_spare_cols = 0 - a = factory.create(module_type="bitcell_array", cols=8 + num_spare_cols, rows=8 + num_spare_rows) + a = factory.create(module_type="bitcell_array", cols=2 + num_spare_cols, rows=2 + num_spare_rows) self.local_check(a) openram.end_openram() diff --git a/compiler/tests/14_capped_replica_bitcell_array_norbl_1rw_test.py b/compiler/tests/14_capped_replica_bitcell_array_norbl_1rw_test.py index 2a648b89..dad854ed 100755 --- a/compiler/tests/14_capped_replica_bitcell_array_norbl_1rw_test.py +++ b/compiler/tests/14_capped_replica_bitcell_array_norbl_1rw_test.py @@ -26,7 +26,7 @@ class capped_replica_bitcell_array_norbl_1rw_test(openram_test): openram.setup_bitcell() debug.info(2, "Testing 7x5 capped replica array for 1rw cell without replica column or dummy row") - a = factory.create(module_type="capped_replica_bitcell_array", cols=7, rows=5, rbl=[0, 0]) + a = factory.create(module_type="capped_replica_bitcell_array", cols=8, rows=6, rbl=[0, 0]) self.local_check(a) openram.end_openram() diff --git a/compiler/tests/14_replica_bitcell_array_leftrbl_1rw_test.py b/compiler/tests/14_replica_bitcell_array_leftrbl_1rw_test.py index f910c50f..b8abb4d1 100755 --- a/compiler/tests/14_replica_bitcell_array_leftrbl_1rw_test.py +++ b/compiler/tests/14_replica_bitcell_array_leftrbl_1rw_test.py @@ -23,6 +23,7 @@ class replica_bitcell_array_leftrbl_1rw_test(openram_test): OPTS.num_rw_ports = 1 OPTS.num_r_ports = 0 OPTS.num_w_ports = 0 + openram.setup_bitcell() debug.info(2, "Testing 7x5 replica array for 1rw cell with left replica column") diff --git a/compiler/tests/19_single_bank_2mux_test.py b/compiler/tests/19_single_bank_2mux_test.py index 451a253e..6683c9e8 100755 --- a/compiler/tests/19_single_bank_2mux_test.py +++ b/compiler/tests/19_single_bank_2mux_test.py @@ -24,7 +24,9 @@ class single_bank_test(openram_test): from openram import sram_config c = sram_config(word_size=4, - num_words=16) + num_words=16, + num_spare_cols=1, + num_spare_rows=1) c.num_words=32 c.words_per_row=2 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..c9ea9454 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 @@ -175,7 +176,11 @@ $(TEST_BASES): @rm -rf results/$* @rm -rf results/$*.* @mkdir -p results/$*/tmp +ifdef TECH_FILE + @sh -c "python3 -u $(OPENRAM_DIR)/$(getfile).py -t $(gettech) -k -f $(TECH_FILE) -v $(ARGS) -p $(OPENRAM_DIR)/results/$* > $(OPENRAM_DIR)/results/$*.out 2>&1 && touch $(OPENRAM_DIR)/results/$*.ok || touch $(OPENRAM_DIR)/results/$*.bad" +else @sh -c "python3 -u $(OPENRAM_DIR)/$(getfile).py -t $(gettech) -k -v $(ARGS) -p $(OPENRAM_DIR)/results/$* > $(OPENRAM_DIR)/results/$*.out 2>&1 && touch $(OPENRAM_DIR)/results/$*.ok || touch $(OPENRAM_DIR)/results/$*.bad" +endif ifdef KEEP @test -f $(TOP_DIR)/compiler/tests/results/$*.ok && echo "$* ... PASS!" || echo "$* ... FAIL!" else diff --git a/compiler/tests/golden/sram_2_16_1_sky130.sp b/compiler/tests/golden/sram_2_16_1_sky130.sp index d68dde96..0f8dc497 100644 --- a/compiler/tests/golden/sram_2_16_1_sky130.sp +++ b/compiler/tests/golden/sram_2_16_1_sky130.sp @@ -833,8 +833,8 @@ Xrca_top_4 X0 ll WL BR VNB sky130_fd_pr__special_nfet_pass w=0.14 l=0.15 X1 ul Q_bar_float VGND VNB sky130_fd_pr__special_nfet_latch w=0.21 l=0.15 X2 BL WL ul VNB sky130_fd_pr__special_nfet_pass w=0.14 l=0.15 -*X3 ur WL ur VPB sky130_fd_pr__special_pfet_pass w=0.07 l=0.095 -*X4 lr WL lr VPB sky130_fd_pr__special_pfet_pass w=0.07 l=0.095 +*X3 ur WL ur VPB sky130_fd_pr__special_pfet_pass w=0.14u l=25n +*X4 lr WL lr VPB sky130_fd_pr__special_pfet_pass w=0.14u l=25n X5 VPWR Q_float lr VPB sky130_fd_pr__special_pfet_pass w=0.14 l=0.15 X6 ur Q_bar_float VPWR VPB sky130_fd_pr__special_pfet_pass w=0.14 l=0.15 X7 VGND Q_float ll VNB sky130_fd_pr__special_nfet_latch w=0.21 l=0.15 @@ -977,8 +977,8 @@ Xrca_19 X0 Q_bar WL BR VNB sky130_fd_pr__special_nfet_pass w=0.14 l=0.15 X1 Q Q_bar VGND VNB sky130_fd_pr__special_nfet_latch w=0.21 l=0.15 X2 BL WL Q VNB sky130_fd_pr__special_nfet_pass w=0.14 l=0.15 -*X3 Q WL Q VPB sky130_fd_pr__special_pfet_pass w=0.07 l=0.095 -*X4 Q_bar WL Q_bar VPB sky130_fd_pr__special_pfet_pass w=0.07 l=0.095 +*X3 Q WL Q VPB sky130_fd_pr__special_pfet_pass w=0.14u l=25n +*X4 Q_bar WL Q_bar VPB sky130_fd_pr__special_pfet_pass w=0.14u l=25n X5 VPWR Q Q_bar VPB sky130_fd_pr__special_pfet_pass w=0.14 l=0.15 X6 Q Q_bar VPWR VPB sky130_fd_pr__special_pfet_pass w=0.14 l=0.15 X7 VGND Q Q_bar VNB sky130_fd_pr__special_nfet_latch w=0.21 l=0.15 @@ -989,8 +989,8 @@ X7 VGND Q Q_bar VNB sky130_fd_pr__special_nfet_latch w=0.21 l=0.15 X0 Q_bar WL BR VNB sky130_fd_pr__special_nfet_pass w=0.14 l=0.15 X1 Q Q_bar VGND VNB sky130_fd_pr__special_nfet_latch w=0.21 l=0.15 X2 BL WL Q VNB sky130_fd_pr__special_nfet_pass w=0.14 l=0.15 -*X3 Q WL Q VPB sky130_fd_pr__special_pfet_pass w=0.07 l=0.095 -*X4 Q_bar WL Q_bar VPB sky130_fd_pr__special_pfet_pass w=0.07 l=0.095 +*X3 Q WL Q VPB sky130_fd_pr__special_pfet_pass w=0.14u l=25n +*X4 Q_bar WL Q_bar VPB sky130_fd_pr__special_pfet_pass w=0.14u l=25n X5 VPWR Q Q_bar VPB sky130_fd_pr__special_pfet_pass w=0.14 l=0.15 X6 Q Q_bar VPWR VPB sky130_fd_pr__special_pfet_pass w=0.14 l=0.15 X7 VGND Q Q_bar VNB sky130_fd_pr__special_nfet_latch w=0.21 l=0.15 diff --git a/compiler/tests/sp_files/sram_2_16_1_sky130.sp b/compiler/tests/sp_files/sram_2_16_1_sky130.sp index d68dde96..0f8dc497 100644 --- a/compiler/tests/sp_files/sram_2_16_1_sky130.sp +++ b/compiler/tests/sp_files/sram_2_16_1_sky130.sp @@ -833,8 +833,8 @@ Xrca_top_4 X0 ll WL BR VNB sky130_fd_pr__special_nfet_pass w=0.14 l=0.15 X1 ul Q_bar_float VGND VNB sky130_fd_pr__special_nfet_latch w=0.21 l=0.15 X2 BL WL ul VNB sky130_fd_pr__special_nfet_pass w=0.14 l=0.15 -*X3 ur WL ur VPB sky130_fd_pr__special_pfet_pass w=0.07 l=0.095 -*X4 lr WL lr VPB sky130_fd_pr__special_pfet_pass w=0.07 l=0.095 +*X3 ur WL ur VPB sky130_fd_pr__special_pfet_pass w=0.14u l=25n +*X4 lr WL lr VPB sky130_fd_pr__special_pfet_pass w=0.14u l=25n X5 VPWR Q_float lr VPB sky130_fd_pr__special_pfet_pass w=0.14 l=0.15 X6 ur Q_bar_float VPWR VPB sky130_fd_pr__special_pfet_pass w=0.14 l=0.15 X7 VGND Q_float ll VNB sky130_fd_pr__special_nfet_latch w=0.21 l=0.15 @@ -977,8 +977,8 @@ Xrca_19 X0 Q_bar WL BR VNB sky130_fd_pr__special_nfet_pass w=0.14 l=0.15 X1 Q Q_bar VGND VNB sky130_fd_pr__special_nfet_latch w=0.21 l=0.15 X2 BL WL Q VNB sky130_fd_pr__special_nfet_pass w=0.14 l=0.15 -*X3 Q WL Q VPB sky130_fd_pr__special_pfet_pass w=0.07 l=0.095 -*X4 Q_bar WL Q_bar VPB sky130_fd_pr__special_pfet_pass w=0.07 l=0.095 +*X3 Q WL Q VPB sky130_fd_pr__special_pfet_pass w=0.14u l=25n +*X4 Q_bar WL Q_bar VPB sky130_fd_pr__special_pfet_pass w=0.14u l=25n X5 VPWR Q Q_bar VPB sky130_fd_pr__special_pfet_pass w=0.14 l=0.15 X6 Q Q_bar VPWR VPB sky130_fd_pr__special_pfet_pass w=0.14 l=0.15 X7 VGND Q Q_bar VNB sky130_fd_pr__special_nfet_latch w=0.21 l=0.15 @@ -989,8 +989,8 @@ X7 VGND Q Q_bar VNB sky130_fd_pr__special_nfet_latch w=0.21 l=0.15 X0 Q_bar WL BR VNB sky130_fd_pr__special_nfet_pass w=0.14 l=0.15 X1 Q Q_bar VGND VNB sky130_fd_pr__special_nfet_latch w=0.21 l=0.15 X2 BL WL Q VNB sky130_fd_pr__special_nfet_pass w=0.14 l=0.15 -*X3 Q WL Q VPB sky130_fd_pr__special_pfet_pass w=0.07 l=0.095 -*X4 Q_bar WL Q_bar VPB sky130_fd_pr__special_pfet_pass w=0.07 l=0.095 +*X3 Q WL Q VPB sky130_fd_pr__special_pfet_pass w=0.14u l=25n +*X4 Q_bar WL Q_bar VPB sky130_fd_pr__special_pfet_pass w=0.14u l=25n X5 VPWR Q Q_bar VPB sky130_fd_pr__special_pfet_pass w=0.14 l=0.15 X6 Q Q_bar VPWR VPB sky130_fd_pr__special_pfet_pass w=0.14 l=0.15 X7 VGND Q Q_bar VNB sky130_fd_pr__special_nfet_latch w=0.21 l=0.15 diff --git a/compiler/verify/run_script.py b/compiler/verify/run_script.py index 10c12ad9..9dfef271 100644 --- a/compiler/verify/run_script.py +++ b/compiler/verify/run_script.py @@ -29,26 +29,21 @@ def run_script(cell_name, script="lvs"): scriptpath = '{0}run_{1}.sh'.format(OPTS.openram_temp, script) - # Wrap with conda activate & conda deactivate - if OPTS.use_conda: - from openram import CONDA_HOME - with open(scriptpath, "r") as f: - script_content = f.readlines() - with open(scriptpath, "w") as f: - # First line is shebang - f.write(script_content[0]) - # Activate conda using the activate script - f.write("source {}/bin/activate\n".format(CONDA_HOME)) - for line in script_content[1:]: - f.write(line) - # Deactivate conda at the end - f.write("conda deactivate\n") - debug.info(2, "Starting {}".format(scriptpath)) start = time.time() with open(outfile, 'wb') as fo, open(errfile, 'wb') as fe: + if OPTS.use_nix: + p_cmd = [ + "nix", + "--extra-experimental-features", "nix-command flakes", + "develop", + "--command", + scriptpath, + ] + else: + p_cmd = [scriptpath] p = subprocess.Popen( - [scriptpath], stdout=fo, stderr=fe, cwd=OPTS.openram_temp) + p_cmd, stdout=fo, stderr=fe, cwd=OPTS.openram_temp) if echo_cmd_output: tailo = subprocess.Popen([ diff --git a/docs/source/basic_setup.md b/docs/source/basic_setup.md index 2fc22ae0..daac3dc7 100644 --- a/docs/source/basic_setup.md +++ b/docs/source/basic_setup.md @@ -7,8 +7,8 @@ This page shows the basic setup for using OpenRAM to generate an SRAM. ## Table of Contents 1. [Dependencies](#dependencies) -1. [Anaconda](#anaconda) -1. [Docker](#docker-deprecated-use-anaconda-instead) +1. [Nix](#nix) +1. [Docker](#docker-deprecated-use-nix-instead) 1. [Environment](#environment) 1. [Sky130 Setup](#sky130-setup) @@ -20,51 +20,25 @@ In general, the OpenRAM compiler has very few dependencies: + Make + Python 3.5 or higher + Various Python packages (pip install -r requirements.txt) -+ Anaconda ++ Nix -## Anaconda -We use Anaconda package manager to install the tools used by OpenRAM. This way, -you don't have to worry about updating/installing these tools. OpenRAM installs -Anaconda silently in the background (without affecting any existing Anaconda -setup you have). +## Nix +OpenRAM uses Nix to provide the external toolchain (layout tools, simulators, +etc.) needed for SRAM generation. -You don't have to manually activate/deactivate the Anaconda environment. OpenRAM -automatically manages this before and after running the tools. - -OpenRAM uses Anaconda by default, but you can turn this feature off by setting -`use_conda = False` in your config file. Then, OpenRAM will use the tools you -have installed on your system. - -You can also tell OpenRAM where Anaconda should be installed or which Anaconda -setup it should use. You can set the `$CONDA_HOME` variable like this: +Enter the Nix development environment with: ``` -export CONDA_HOME="/path/to/conda/setup" +nix develop ``` -> **Note**: If you want to install Anaconda without running OpenRAM (for example -> to run unit tests, which do not install Anaconda), you can run: -> ``` -> ./install_conda.sh -> ``` +Within the devShell, required executables are available on `PATH` -> **Note**: You can uninstall OpenRAM's Anaconda installation by simply deleting -> the folder Anaconda is installed to. You can run: -> ``` -> rm -rf miniconda -> ``` +OpenRAM uses the `use_nix` option (enabled by default) to initialize Nix-based +tool dependencies via `nix develop`. -> **Note**: You can change a tool's version with the following commands: -> ``` -> source ./miniconda/bin/activate -> conda uninstall -> conda install -y -c vlsida-eda = -> ``` - - - -## Docker (deprecated, use Anaconda instead) +## Docker (deprecated, use Nix instead) We have a [docker setup](../../docker) to run OpenRAM. To use this, you should run: ``` @@ -124,12 +98,11 @@ make sky130-pdk This will use ciel to get the PDK. -> **Note**: If you don't have Magic installed, you need to install and activate -> the conda environment before running this command. You can run: +> **Note**: If you don't have Magic installed, enter the OpenRAM Nix devShell +> first (it provides Magic and other tools via `PATH`): > > ``` -> ./install_conda.sh -> source miniconda/bin/activate +> nix develop > ``` Then you must also install the [Sky130] SRAM build space with the appropriate diff --git a/docs/source/debug.md b/docs/source/debug.md index 2b6dde0d..e4326066 100644 --- a/docs/source/debug.md +++ b/docs/source/debug.md @@ -61,10 +61,9 @@ make -j 3 ``` The `-j` can run with 3 threads. By default, this will run in all technologies. -> **Note**: If you have not run openram before running unit tests, the conda -> environment will not be installed. You can install it by running -> `OpenRAM/install_conda.sh` (see [Basic Setup](basic_setup.md#anaconda) for -> more details). +> **Note**: The external EDA toolchain is provided by the Nix devShell. +> If you run unit tests without being in a Nix environment, enter it first: +> `nix develop` (see [Basic Setup](basic_setup.md#nix) for more details). To run a specific test in all technologies: ``` diff --git a/docs/source/index.md b/docs/source/index.md index bc7c5f50..4dc0d299 100644 --- a/docs/source/index.md +++ b/docs/source/index.md @@ -40,7 +40,7 @@ In general, the OpenRAM compiler has very few dependencies: + Make + Python 3.5 or higher + Various Python packages (pip install -r requirements.txt) -+ Anaconda ++ Nix Commercial tools (optional): * Spice Simulator diff --git a/docs/source/openram_dev_notes.md b/docs/source/openram_dev_notes.md index f2550cb0..99da300c 100644 --- a/docs/source/openram_dev_notes.md +++ b/docs/source/openram_dev_notes.md @@ -11,7 +11,7 @@ In this section, the detailed usage of using OpenRAM framework will be demonstra > > Before you go through, make sure that environment of Sky130 has been already set up. -1. Activate miniconda +1. Enter the Nix devShell 2. Edit the sram configuration file @@ -19,11 +19,11 @@ In this section, the detailed usage of using OpenRAM framework will be demonstra 4. Check the results -#### Activate miniconda +#### Enter Nix devShell ```bash cd OpenRAM/ -source ./miniconda/bin/activate +nix develop ``` #### Modified the sram configuration diff --git a/flake.lock b/flake.lock new file mode 100644 index 00000000..e66c0499 --- /dev/null +++ b/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1777270315, + "narHash": "sha256-yKB4G6cKsQsWN7M6rZGk6gkJPDNPIzT05y4qzRyCDlI=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "6368eda62c9775c38ef7f714b2555a741c20c72d", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 00000000..1c0f0038 --- /dev/null +++ b/flake.nix @@ -0,0 +1,48 @@ +{ + description = "OpenRAM development environment (Nix)"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + }; + + outputs = { self, nixpkgs }: + let + systems = [ "x86_64-linux" ]; + forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f system); + in + { + devShells = forAllSystems (system: + let + pkgs = import nixpkgs { inherit system; }; + in + { + default = pkgs.mkShell { + packages = [ + # EDA / verification tools + pkgs.klayout + pkgs.magic-vlsi + # Use the LVS-focused netgen package; the generic netgen package + # may require a local build that can fail on some hosts. + pkgs.netgen-vlsi + pkgs.ngspice + pkgs.iverilog + pkgs.xyce + pkgs.xyce-parallel + pkgs.trilinos + pkgs.trilinos-mpi + + # Dev conveniences + pkgs.git + pkgs.gnumake + pkgs.curl + ]; + + shellHook = '' + export OPENRAM_USE_CONDA=0 + echo "OpenRAM: using tools from Nix devShell" + ''; + }; + }); + }; +} + diff --git a/install_conda.sh b/install_conda.sh deleted file mode 100755 index 6b37c720..00000000 --- a/install_conda.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/bash -CONDA_INSTALLER_URL="https://repo.anaconda.com/miniconda/Miniconda3-py38_23.11.0-2-Linux-x86_64.sh" -CONDA_INSTALLER_FILE="miniconda_installer_py38.sh" -CONDA_HOME="${CONDA_HOME:-miniconda}" - -# The tool name format is "=". -# 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+="ngspice=26 " -TOOLS+="trilinos=12.12.1=1 " -TOOLS+="xyce=7.4=3" - -# Install miniconda if not already installed -if [[ ! -d "${CONDA_HOME}/bin" ]] -then - curl -s -o ${CONDA_INSTALLER_FILE} ${CONDA_INSTALLER_URL} - /bin/bash ${CONDA_INSTALLER_FILE} -b -p ${CONDA_HOME} - rm ${CONDA_INSTALLER_FILE} - source ${CONDA_HOME}/bin/activate - - # Prioritize channels to prevent version conflicts - conda config --add channels conda-forge - conda config --add channels vlsida-eda - - # Install iverilog from conda-eda - conda install -q -y -c litex-hub iverilog - - # Install rest of the tools from vlsida-eda - for tool in ${TOOLS} - do - conda install -q -y -c vlsida-eda ${tool} - done - - # Install required Python packages - # (This step isn't required but used to prevent possible issues) - python3 -m pip install -r requirements.txt --ignore-installed - - conda deactivate -fi - 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/macros/sram_configs/sky130_sram_1rw_tiny.py b/macros/sram_configs/sky130_sram_1rw_tiny.py index 85aa34f4..20aaa465 100644 --- a/macros/sram_configs/sky130_sram_1rw_tiny.py +++ b/macros/sram_configs/sky130_sram_1rw_tiny.py @@ -4,12 +4,13 @@ Dual port (1 read/write + 1 read only) 1 kbytes SRAM with byte write. FIXME: What is this useful for? FIXME: Why would you want byte write on this? """ -word_size = 8 # Bits +word_size = 16 # Bits num_words = 16 human_byte_size = "{:.0f}kbytes".format((word_size * num_words)/1024/8) # Allow byte writes -write_size = 2 # Bits +write_size = 8 # Bits +words_per_row = 1 # Dual port num_rw_ports = 1 @@ -19,6 +20,10 @@ ports_human = '1rw' num_spare_cols = 1 num_spare_rows = 1 +#netlist_only = True +num_sim_threads = 1 +#analytical_delay = False import os exec(open(os.path.join(os.path.dirname(__file__), 'sky130_sram_common.py')).read()) +tech_file = "tech_custom_cell" \ No newline at end of file diff --git a/setup.py b/setup.py index 34526279..1cae9b3c 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ from setuptools import setup, find_namespace_packages # Include these folder from the root of repo as submodules include = ["compiler", "docker", "technology", "macros"] # Exclude files/folders with these words -exclude = ["docs", "images", "miniconda"] +exclude = ["docs", "images"] # Find all modules inside the 'compiler' folder 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..f861f707 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 @@ -23,6 +23,10 @@ class sky130_bitcell(bitcell_base): cell_name = "sky130_fd_bd_sram__sram_sp_cell_opt1" elif version == "opt1a": cell_name = "sky130_fd_bd_sram__sram_sp_cell_opt1a" + elif version == "opt1_noblcon": + cell_name = "sky130_fd_bd_sram__openram_sp_cell_opt1_noblcon" + elif version == "opt1a_noblcon": + cell_name = "sky130_fd_bd_sram__openram_sp_cell_opt1a_noblcon" else: debug.error("Invalid sky130 cell name", -1) diff --git a/technology/sky130/custom/sky130_bitcell_array.py b/technology/sky130/custom/sky130_bitcell_array.py index e5a1704b..89572624 100644 --- a/technology/sky130/custom/sky130_bitcell_array.py +++ b/technology/sky130/custom/sky130_bitcell_array.py @@ -6,88 +6,84 @@ # 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): """ Creates a rows x cols array of memory cells. 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() - + def __init__(self, rows, cols, column_offset=0, row_offset=0, name="", left_rbl=None, right_rbl=None): + super().__init__(rows=rows, cols=cols, column_offset=column_offset, row_offset=row_offset, name=name) + self.left_rbl = left_rbl + self.right_rbl = right_rbl + self.column_offset = column_offset + self.row_offset = row_offset 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.cell_noblcon = factory.create(module_type=OPTS.bitcell, version="opt1_noblcon") + #self.cella_noblcon = factory.create(module_type=OPTS.bitcell, version="opt1a_noblcon") 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={} + + #self.cell_noblcon_inst = geometry.instance("cell_noblcon_inst", mod=self.cell_noblcon, is_bitcell=True) + #self.cella_noblcon_inst = geometry.instance("cella_noblcon_inst", mod=self.cella_noblcon, is_bitcell=True) - row_layout = [] + bit_row_opt1 = [geometry.instance("00_opt1", mod=self.cell, is_bitcell=True, mirror='MX')] \ + + [geometry.instance("01_strap_p", mod=self.strap, is_bitcell=False, mirror='MX')]\ + + [geometry.instance("02_opt1", mod=self.cell, is_bitcell=True, mirror='XY')] \ + + [geometry.instance("03_strap", mod=self.strap_p, is_bitcell=False, mirror='MX')] + + bit_row_opt1a = [geometry.instance("10_opt1a", mod=self.cella, is_bitcell=True)] \ + + [geometry.instance("11_strapa", mod=self.strap, is_bitcell=False)] \ + + [geometry.instance("12_opt1a", mod=self.cella, is_bitcell=True, mirror='MY')] \ + + [geometry.instance("13_strapa_p", mod=self.strapa_p, is_bitcell=False)] + + bit_block = [] + if self.row_offset % 2 == 0: + pattern.append_row_to_block(bit_block, bit_row_opt1) + pattern.append_row_to_block(bit_block, bit_row_opt1a) + else: + pattern.append_row_to_block(bit_block, bit_row_opt1a) + pattern.append_row_to_block(bit_block, bit_row_opt1) - 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) + 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() + + # for i in range(len(self.insts)): + # if self.left_rbl: + # if "r{}".format(self.row_size-1) in self.insts[i].name: + # if self.insts[i].mod == self.cell: + # self.insts[i].mod = self.cell_noblcon_inst.mod + # self.insts[i].gds = self.cell_noblcon_inst.gds + # elif self.insts[i].mod == self.cella: + # self.insts[i].mod = self.cella_noblcon_inst.mod + # self.insts[i].gds = self.cella_noblcon_inst.gds + # if self.right_rbl: + # if "r{}".format("0") in self.insts[i].name: + # if self.insts[i].mod == self.cell: + # self.insts[i].mod = self.cell_noblcon_inst.mod + # self.insts[i].gds = self.cell_noblcon_inst.gds + # elif self.insts[i].mod == self.cella: + # self.insts[i].mod = self.cella_noblcon_inst.mod + # self.insts[i].gds = self.cella_noblcon_inst.gds diff --git a/technology/sky130/custom/sky130_bitcell_base_array.py b/technology/sky130/custom/sky130_bitcell_base_array.py index a26f323e..7fbb3124 100644 --- a/technology/sky130/custom/sky130_bitcell_base_array.py +++ b/technology/sky130/custom/sky130_bitcell_base_array.py @@ -11,56 +11,18 @@ 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): """ Abstract base class for bitcell-arrays -- bitcell, dummy, replica """ - def __init__(self, name, rows, cols, column_offset): - super().__init__(name, rows, cols, column_offset) + def __init__(self, name, rows, cols, column_offset, row_offset): + super().__init__(name, rows, cols, column_offset, row_offset) debug.info(1, "Creating {0} {1} x {2}".format(self.name, rows, cols)) 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, @@ -123,66 +85,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..3ec8286c 100644 --- a/technology/sky130/custom/sky130_capped_replica_bitcell_array.py +++ b/technology/sky130/custom/sky130_capped_replica_bitcell_array.py @@ -7,213 +7,199 @@ 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 route_power_ring(self, v_layer, h_layer): + # ring is manually added and routed in add_layout_pins + pass - # 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 = [] + def add_layout_pins(self): + """ Add the layout pins """ - self.create_netlist() - if not OPTS.netlist_only: - self.create_layout() + for row_end in self.dummy_col_insts: + row_end = row_end.mod + print(self.get_all_wordline_names(), row_end.get_wordline_names()) + 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()) - def create_netlist(self): - """ Create and connect the netlist """ - self.add_modules() - self.add_pins() - self.create_instances() + 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 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) + # 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_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.py b/technology/sky130/custom/sky130_col_cap.py index 032169ee..3bedfe19 100644 --- a/technology/sky130/custom/sky130_col_cap.py +++ b/technology/sky130/custom/sky130_col_cap.py @@ -8,20 +8,21 @@ from openram import debug from openram.base import design from openram.tech import cell_properties as props - +from openram import OPTS class sky130_col_cap(design): - def __init__(self, version, name=""): + def __init__(self, version, name="",left_rbl=[],right_rbl=[]): + if version == "colend": cell_name = "sky130_fd_bd_sram__sram_sp_colend" prop = props.col_cap_1port_bitcell + elif version == "colenda": + cell_name = "sky130_fd_bd_sram__sram_sp_colenda" + prop = props.col_cap_1port_bitcell elif version == "colend_p_cent": cell_name = "sky130_fd_bd_sram__sram_sp_colend_p_cent" prop = props.col_cap_1port_strap_ground - elif version == "colenda": - cell_name = "sky130_fd_bd_sram__sram_sp_colenda" - prop = props.col_cap_1port_bitcell elif version == "colenda_p_cent": cell_name = "sky130_fd_bd_sram__sram_sp_colenda_p_cent" prop = props.col_cap_1port_strap_ground @@ -32,5 +33,5 @@ class sky130_col_cap(design): cell_name = "sky130_fd_bd_sram__sram_sp_colenda_cent" prop = props.col_cap_1port_strap_power else: - debug.error("Invalid type for col_end", -1) + debug.error("Invalid type for col_end: {}".format(version), -1) super().__init__(name=name, cell_name=cell_name, prop=prop) diff --git a/technology/sky130/custom/sky130_col_cap_array.py b/technology/sky130/custom/sky130_col_cap_array.py index ded32039..667641fb 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,247 +9,91 @@ 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, row_offset=0, mirror=0, location="", name="", left_rbl=[],right_rbl=[]): + self.left_rbl = left_rbl + self.right_rbl = right_rbl + super().__init__(rows, cols, column_offset=column_offset, mirror=mirror, location=location, name=name, left_rbl=left_rbl, right_rbl=right_rbl) + self.no_instances = False def add_modules(self): """ Add the modules used in this design """ if self.location == "top": - self.colend1 = factory.create(module_type="col_cap", version="colend") + self.colend1 = factory.create(module_type="col_cap", version="colend",left_rbl=self.left_rbl, right_rbl=self.right_rbl) self.colend2 = factory.create(module_type="col_cap", version="colend_p_cent") self.colend3 = factory.create(module_type="col_cap", version="colend_cent") elif self.location == "bottom": - self.colend1 = factory.create(module_type="col_cap", version="colenda") + self.colend1 = factory.create(module_type="col_cap", version="colenda",left_rbl=self.left_rbl, right_rbl=self.right_rbl) self.colend2 = factory.create(module_type="col_cap", version="colenda_p_cent") self.colend3 = factory.create(module_type="col_cap", version="colenda_cent") 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..45722bef 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 == 1: + 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 == 0: + 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 == 0: + 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..ab1064ad 100644 --- a/technology/sky130/custom/sky130_row_cap_array.py +++ b/technology/sky130/custom/sky130_row_cap_array.py @@ -5,140 +5,98 @@ # 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, row_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): + pattern.append_row_to_block(bit_block, [bottom_corner]) + for row in range(1, self.row_size-1): + if row % 2 == 1: + pattern.append_row_to_block(bit_block, [rowenda]) - 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]) 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, [rowend]) - def add_pins(self): - for row in range(self.rows + 2): - for port in self.all_ports: - self.add_pin("wl_{}_{}".format(port, row), "OUTPUT") - self.add_pin("vdd", "POWER") - self.add_pin("gnd", "GROUND") + pattern.append_row_to_block(bit_block, [top_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_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()) + bitcell_pins = [] + bitcell_pins.append("vdd") # vdd + bitcell_pins.extend([x for x in self.all_wordline_names if x.endswith("_{0}".format(row))]) - # 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) + 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_layout(self): + + 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..3aa4659f 100755 --- a/technology/sky130/tech/tech.py +++ b/technology/sky130/tech/tech.py @@ -5,837 +5,15 @@ # All rights reserved. # +import sys +from openram import OPTS -import os -from openram import drc as d +import os +dir_path = os.path.dirname(os.path.realpath(__file__)) -""" -File containing the process technology parameters for Skywater 130nm. -""" +sys.path.append("{}/{}".format(dir_path,'tech_configs')) -################################################### -# Custom modules -################################################### - -# This uses the default classes to instantiate module from -# '$OPENRAM_HOME/compiler/modules'. -# Using tech_modules['cellname'] you can override each class by providing a custom -# implementation in '$OPENRAM_TECHDIR/modules/' -# For example: tech_modules["contact"] = "contact_freepdk45" -tech_modules = d.module_type() - -# These modules have been hand designed and provided in this repository. -tech_modules["nand2_dec"] = "nand2_dec" -tech_modules["nand3_dec"] = "nand3_dec" -tech_modules["nand4_dec"] = "nand4_dec" - -# Override default OpenRAM modules to sky130 modules -# These are for single port and dual port as a list, -# or for both if there is no list, -# or only applicable to one if there is no list. -tech_modules["bitcell_1port"] = "sky130_bitcell" -tech_modules["replica_bitcell_1port"] = "sky130_replica_bitcell" -tech_modules["dummy_bitcell_1port"] = "sky130_dummy_bitcell" - -tech_modules["replica_bitcell_2port"] = "replica_bitcell_2port" -tech_modules["dummy_bitcell_2port"] = "dummy_bitcell_2port" -tech_modules["bitcell_2port"] = "bitcell_2port" - -tech_modules["bitcell_array"] = ["sky130_bitcell_array", "bitcell_array"] -tech_modules["replica_bitcell_array"] = ["sky130_replica_bitcell_array", "replica_bitcell_array"] -tech_modules["capped_replica_bitcell_array"] = ["sky130_capped_replica_bitcell_array", "capped_replica_bitcell_array"] -tech_modules["dummy_array"] = ["sky130_dummy_array", "dummy_array"] - -tech_modules["replica_column"] = ["sky130_replica_column", "replica_column"] - -tech_modules["col_cap_array"] = ["sky130_col_cap_array", "col_cap_array"] -tech_modules["col_cap"] = ["sky130_col_cap", "col_cap_bitcell_2port"] -tech_modules["corner"] = ["sky130_corner", None] -tech_modules["internal"] = ["sky130_internal", None] -tech_modules["row_cap_array"] = ["sky130_row_cap_array", "row_cap_array"] -tech_modules["row_cap"] = ["sky130_row_cap", "row_cap_bitcell_2port"] - -# These modules are auto-generated from the nand decoders above and are not -# found in this. -tech_modules["buf_dec"] = "pbuf_dec" -tech_modules["inv_dec"] = "pinv_dec" -tech_modules["and2_dec"] = "and2_dec" -tech_modules["and3_dec"] = "and3_dec" -tech_modules["and4_dec"] = "and4_dec" - -################################################### -# Custom cell properties -################################################### -cell_properties = d.cell_properties() - -cell_properties.bitcell_power_pin_directions = ("H", "H") - -cell_properties.bitcell_1port.mirror.x = True -cell_properties.bitcell_1port.mirror.y = True -cell_properties.bitcell_1port.end_caps = True -cell_properties.bitcell_1port.boundary_layer = "mem" -cell_properties.bitcell_1port.port_order = ['bl', 'br', 'gnd', 'vdd', 'vpb', 'vnb', 'wl'] -cell_properties.bitcell_1port.port_types = ["OUTPUT", "OUTPUT", "GROUND", "POWER", "BIAS", "BIAS", "INPUT"] -cell_properties.bitcell_1port.port_map = {'bl': 'BL', - 'br': 'BR', - 'wl': 'WL', - 'vdd': 'VPWR', - 'vnb': 'VNB', - 'vpb': 'VPB', - 'gnd': 'VGND'} - -cell_properties.bitcell_1port.wl_layer = "m2" -cell_properties.bitcell_1port.bl_layer = "m1" -cell_properties.bitcell_1port.vdd_layer = "m1" -cell_properties.bitcell_1port.vdd_dir = "V" -cell_properties.bitcell_1port.gnd_layer = "m2" -cell_properties.bitcell_1port.gnd_dir = "H" - -cell_properties.bitcell_2port.mirror.x = True -cell_properties.bitcell_2port.mirror.y = True -cell_properties.bitcell_2port.end_caps = True -cell_properties.bitcell_2port.port_order = ['bl0', 'br0', 'bl1', 'br1', 'wl0', 'wl1', 'vdd', 'gnd'] -cell_properties.bitcell_2port.port_map = {'bl0': 'BL0', - 'br0': 'BR0', - 'bl1': 'BL1', - 'br1': 'BR1', - 'wl0': 'WL0', - 'wl1': 'WL1', - 'vdd': 'VDD', - 'gnd': 'GND'} -cell_properties.bitcell_1port.wl_layer = "m2" -cell_properties.bitcell_1port.vdd_layer = "m2" -cell_properties.bitcell_1port.vdd_dir = "H" -cell_properties.bitcell_1port.gnd_layer = "m2" -cell_properties.bitcell_1port.gnd_dir = "H" -cell_properties.bitcell_2port.wl_layer = "m2" -cell_properties.bitcell_2port.vdd_layer = "m1" -cell_properties.bitcell_2port.vdd_dir = "H" -cell_properties.bitcell_2port.gnd_layer = "m2" -cell_properties.bitcell_2port.gnd_dir = "H" - -cell_properties.col_cap_1port_bitcell = d.cell(['bl', 'br', 'vdd', 'gnd', 'vpb', 'vnb', 'gate'], - ['INPUT', 'INPUT','POWER', 'GROUND', 'BIAS', 'BIAS', 'INPUT'], - {'bl': 'bl', - 'br': 'br', - 'vdd': 'vdd', - 'gnd': 'gnd', - 'vnb': 'vnb', - 'vpb': 'vpb', - 'gate': 'gate'}) -cell_properties.col_cap_1port_bitcell.boundary_layer = "mem" - -cell_properties.col_cap_1port_strap_power = d.cell(['vdd', 'vpb', 'vnb'], - ['POWER', 'BIAS', 'BIAS'], - {'vnb': 'VNB', - 'vpb': 'VPB', - 'vdd': 'VPWR'}) -cell_properties.col_cap_1port_strap_power.boundary_layer = "mem" - -cell_properties.col_cap_1port_strap_ground = d.cell(['gnd', 'vpb', 'vnb'], - ['GROUND', 'BIAS', 'BIAS'], - {'vnb': 'VNB', - 'vpb': 'VPB', - 'gnd': 'VGND'}) -cell_properties.col_cap_1port_strap_ground.boundary_layer = "mem" - -cell_properties.row_cap_1port_cell = d.cell(['vdd', 'wl'], - ['POWER', 'INPUT'], - {'wl': 'WL', - 'vdd': 'VPWR'}) -cell_properties.row_cap_1port_cell.boundary_layer = "mem" - -cell_properties.col_cap_2port.port_order = ['bl0', 'br0', 'bl1', 'br1', 'vdd'] -cell_properties.col_cap_2port.port_map = {'bl0': 'BL0', - 'br0': 'BR0', - 'bl1': 'BL1', - 'br1': 'BR1', - 'vdd': 'VDD'} - -cell_properties.row_cap_2port.port_order = ['wl0', 'wl1', 'gnd'] -cell_properties.row_cap_2port.port_map = {'wl0': 'WL0', - 'wl1': 'WL1', - 'gnd': 'GND'} - - -cell_properties.ptx.bin_spice_models = True -cell_properties.ptx.model_is_subckt = True - -cell_properties.pgate.add_implants = True - -cell_properties.use_strap = True -cell_properties.strap_module = "internal" -cell_properties.strap_version = "wlstrap" - -cell_properties.dff.port_order = ['D', 'Q', 'clk', 'vdd', 'gnd'] -cell_properties.dff.port_map = {'D': 'D', - 'Q': 'Q', - 'clk': 'CLK', - 'vdd': 'VDD', - 'gnd': 'GND'} - -cell_properties.nand2_dec.port_order = ['A', 'B', 'Z', 'vdd', 'gnd'] -cell_properties.nand2_dec.port_map = {'A': 'A', - 'B': 'B', - 'Z': 'Z', - 'vdd': 'VDD', - 'gnd': 'GND'} - -cell_properties.nand3_dec.port_order = ['A', 'B', 'C', 'Z', 'vdd', 'gnd'] -cell_properties.nand3_dec.port_map = {'A': 'A', - 'B': 'B', - 'C': 'C', - 'Z': 'Z', - 'vdd': 'VDD', - 'gnd': 'GND'} - -cell_properties.nand4_dec.port_order = ['A', 'B', 'C', 'D', 'Z', 'vdd', 'gnd'] -cell_properties.nand4_dec.port_map = {'A': 'A', - 'B': 'B', - 'C': 'C', - 'D': 'D', - 'Z': 'Z', - 'vdd': 'VDD', - 'gnd': 'GND'} - -cell_properties.sense_amp.port_order = ['bl', 'br', 'dout', 'en', 'vdd', 'gnd'] -cell_properties.sense_amp.port_map = {'bl': 'BL', - 'br': 'BR', - 'dout': 'DOUT', - 'en': 'EN', - 'vdd': 'VDD', - 'gnd': 'GND'} - -cell_properties.write_driver.port_order = ['din', 'bl', 'br', 'en', 'vdd', 'gnd'] -cell_properties.write_driver.port_map = {'din': 'DIN', - 'bl': 'BL', - 'br': 'BR', - 'en': 'EN', - 'vdd': 'VDD', - 'gnd': 'GND'} - - -# You can override the GDS for custom cell using the following: -# If it is a list, the first is single port and the second is dual port. -# If it is string, it is used for both single and dual port. -cell_properties.names["dff"] = "sky130_fd_bd_sram__openram_dff" -cell_properties.names["nand2_dec"] = ["sky130_fd_bd_sram__openram_sp_nand2_dec", "sky130_fd_bd_sram__openram_dp_nand2_dec"] -cell_properties.names["nand3_dec"] = ["sky130_fd_bd_sram__openram_sp_nand3_dec", "sky130_fd_bd_sram__openram_dp_nand3_dec"] -cell_properties.names["nand4_dec"] = ["sky130_fd_bd_sram__openram_sp_nand4_dec", "sky130_fd_bd_sram__openram_dp_nand4_dec"] - -cell_properties.names["bitcell_2port"] = "sky130_fd_bd_sram__openram_dp_cell" -cell_properties.names["dummy_bitcell_2port"] = "sky130_fd_bd_sram__openram_dp_cell_dummy" -cell_properties.names["replica_bitcell_2port"] = "sky130_fd_bd_sram__openram_dp_cell_replica" -cell_properties.names["col_cap_bitcell_2port"] = "sky130_fd_bd_sram__openram_dp_cell_cap_col" -cell_properties.names["row_cap_bitcell_2port"] = "sky130_fd_bd_sram__openram_dp_cell_cap_row" -cell_properties.names["sense_amp"] = "sky130_fd_bd_sram__openram_sense_amp" -cell_properties.names["write_driver"] = "sky130_fd_bd_sram__openram_write_driver" - -array_row_multiple = 2 -array_col_multiple = 2 - -################################################### -# Custom layer properties -################################################### -layer_properties = d.layer_properties() -layer_properties.hierarchical_decoder.bus_layer = "m1" -layer_properties.hierarchical_decoder.bus_directions = "nonpref" -layer_properties.hierarchical_decoder.input_layer = "li" -layer_properties.hierarchical_decoder.output_layer = "m2" -layer_properties.hierarchical_decoder.vertical_supply = True - -layer_properties.hierarchical_predecode.bus_layer = "m1" -layer_properties.hierarchical_predecode.bus_directions = "nonpref" -# This is added to allow the column decoder connections on m2 -layer_properties.hierarchical_predecode.bus_pitch_factor = 1.2 -layer_properties.hierarchical_predecode.bus_space_factor = 1.5 -layer_properties.hierarchical_predecode.input_layer = "li" -layer_properties.hierarchical_predecode.output_layer = "m2" -layer_properties.hierarchical_predecode.vertical_supply = True -layer_properties.hierarchical_predecode.force_horizontal_input_contact = True - -layer_properties.bank.stack = "m2_stack" -layer_properties.bank.pitch = "m3_pitch" - -layer_properties.column_mux_array.select_layer = "m3" -layer_properties.column_mux_array.bitline_layer = "m1" - -layer_properties.port_address.supply_offset = True - -layer_properties.port_data.enable_layer = "m1" -layer_properties.port_data.channel_route_bitlines = False - -layer_properties.replica_column.even_rows = True - -layer_properties.wordline_driver.vertical_supply = True - -layer_properties.global_wordline_layer = "m5" - - -################################################### -# Discrete tx bins -################################################### -# enforce that tx sizes are within 25% of requested size after fingering. -accuracy_requirement = 0.75 -nmos_bins = { - 0.15 : [0.36, 0.39, 0.42, 0.52, 0.54, 0.55, 0.58, 0.6, 0.61, 0.64, 0.65, 0.74, 0.84, 1.0, 1.26, 1.68, 2.0, 3.0, 5.0, 7.0], - 0.18 : [0.42, 0.65, 1.0, 3.0, 5.0, 7.0], - 0.25 : [0.65, 1.0, 3.0, 5.0, 7.0], - 0.5 : [0.42, 0.55, 0.65, 1.0, 3.0, 5.0, 7.0], - 1.0 : [0.42, 0.65, 1.0, 3.0, 5.0, 7.0], - 2.0 : [0.42, 0.65, 1.0, 3.0, 5.0, 7.0], - 4.0 : [0.42, 0.65, 1.0, 3.0, 5.0, 7.0], - 8.0 : [0.42, 0.65, 1.0, 3.0, 5.0, 7.0], - 20.0 : [0.42, 0.65, 1.0, 3.0, 5.0, 7.0] -} - -pmos_bins = { - 0.15 : [0.42, 0.55, 0.64, 0.84, 1.0, 1.12, 1.26, 1.65, 1.68, 2.0, 3.0, 5.0, 7.0], - 1.0 : [0.42, 0.55, 1.0, 3.0, 5.0, 7.0], - 2.0 : [0.42, 0.55, 1.0, 3.0, 5.0, 7.0], - 4.0 : [0.42, 0.55, 1.0, 3.0, 5.0, 7.0], - 8.0 : [0.42, 0.55, 1.0, 3.0, 5.0, 7.0], - 0.17 : [0.42, 0.55, 0.64, 0.84, 1.0, 1.12], - 0.18 : [0.42, 0.55, 0.64, 0.84, 1.0, 1.12, 1.26, 1.68, 2.0, 3.0, 5.0, 7.0], - 0.25 : [1.0, 3.0, 5.0, 7.0], - 0.5 : [0.42, 0.55, 1.0, 3.0, 5.0, 7.0], - 20.0 : [0.42] -} -################################################### -# GDS file info -################################################### -GDS = {} -# gds units -# From http://www.cnf.cornell.edu/cnf_spie9.html: "The first -# is the size of a database unit in user units. The second is the size -# of a database unit in meters. For example, if your library was -# created with the default units (user unit = 1 um and 1000 database -# units per user unit), then the first number would be 0.001 and the -# second number would be 10-9. Typically, the first number is less than -# 1, since you use more than 1 database unit per user unit. To -# calculate the size of a user unit in meters, divide the second number -# by the first." -GDS["unit"] = (0.001, 1e-9) -#GDS["unit"]=(0.001, 1e-6) - -################################################### -# Interconnect stacks -################################################### - -poly_stack = ("poly", "contact", "li") -active_stack = ("active", "contact", "li") -li_stack = ("li", "mcon", "m1") -m1_stack = ("m1", "via1", "m2") -m2_stack = ("m2", "via2", "m3") -m3_stack = ("m3", "via3", "m4") -m4_stack = ("m4", "via4", "m5") - -lef_rom_interconnect = ["m1", "m2", "m3", "m4"] - -layer_indices = {"poly": 0, - "active": 0, - "nwell": 0, - "li": 1, - "m1": 2, - "m2": 3, - "m3": 4, - "m4": 5, - "m5": 6} - -# The FEOL stacks get us up to m1 -feol_stacks = [poly_stack, - active_stack, - li_stack] - -# The BEOL stacks are m1 and up -beol_stacks = [m1_stack, - m2_stack, - m3_stack, - m4_stack] - -layer_stacks = feol_stacks + beol_stacks - -preferred_directions = {"poly": "V", - "active": "V", - "li": "V", - "m1": "H", - "m2": "V", - "m3": "H", - "m4": "V", - "m5": "H"} - -################################################### -# GDS Layer Map -################################################### - - -layer = {} -layer["active"] = (65, 20) # diff -layer["activep"] = (65, 20) # diff -layer["tap"] = (65, 44) # tap -layer["pwellp"] = (122,16) -layer["nwell"] = (64, 20) # nwell -layer["dnwell"] = (64,18) -layer["nimplant"]= (93, 44) # nsdm -layer["pimplant"]= (94, 20) # psdm -layer["vtl"] = (125, 44) # lvtn -layer["vth"] = (78, 44) # hvtp (pmos only) -layer["thkox"] = (8, 0) -layer["poly"] = (66, 20) -layer["contact"] = (66, 44) # licon1 -layer["npc"] = (95, 20) # npc (nitride cut) -layer["li"] = (67, 20) # active li1 -layer["mcon"] = (67, 44) # mcon -layer["m1"] = (68, 20) # met1 -layer["m1p"] = (68, 5) # met1 pin -layer["via1"] = (68, 44) # via1 -layer["m2"] = (69, 20) # met2 -layer["m2p"] = (69, 5) # met2 pin -layer["via2"] = (69, 44) # via2 -layer["m3"] = (70, 20) # met3 -layer["m3p"] = (70, 5) # met3 pin -layer["via3"] = (70, 44) # via3 -layer["m4"] = (71, 20) # met4 -layer["m4p"] = (71, 5) # met4 pin -layer["via4"] = (71, 44) # via4 -layer["m5"] = (72, 20) # met5 -layer["m5p"] = (72, 5) # met5 pin -layer["boundary"]= (235, 4) -# specific boundary type to define standard cell regions for DRC -layer["stdc"] = (81, 4) -layer["mem"] = (81, 2) -# Not an official sky130 layer, but useful for router debug infos -layer["text"]= (234, 5) -# Excpected value according to sky130A tech file -# If calibre is enabled, these will be swapped below -#pin_purpose = 5 -label_purpose = 5 -#label_purpose = 16 -#pin_purpose = 16 -#label_purpose = 5 - -# pin_read purposes -special_purposes = {layer["nwell"][0]: [layer["nwell"][1], 5, 59, 16]} -#layer_override = {"VNB\x00": ["pwell",122]} -layer_override = {"vnb": layer["pwellp"], "VNB": layer["pwellp"]} -layer_override_name = {"vnb": "pwellp", "VNB": "pwellp"} -layer_override_purpose = {122: (64, 59)} -# Layer names for external PDKs -layer_names = {} -layer_names["active"] = "diff" -layer_names["activep"] = "diff" -layer_names["tap"] = "tap" -layer_names["pwellp"] = "pwellp" -layer_names["nwell"] = "nwell" -layer_names["dnwell"] = "dnwell" -layer_names["nimplant"]= "nsdm" -layer_names["pimplant"]= "psdm" -layer_names["vtl"] = "lvtn" -layer_names["vth"] = "hvtp" -layer_names["thkox"] = "thkox" -layer_names["poly"] = "poly" -layer_names["contact"] = "licon1" -layer_names["li"] = "li1" -layer_names["mcon"] = "mcon" -layer_names["m1"] = "met1" -layer_names["m1p"] = "met1" -layer_names["via1"] = "via" -layer_names["m2"] = "met2" -layer_names["m2p"] = "met2" -layer_names["via2"] = "via2" -layer_names["m3"] = "met3" -layer_names["m3p"] = "met3" -layer_names["via3"] = "via3" -layer_names["m4"] = "met4" -layer_names["m4p"] = "met4" -layer_names["via4"] = "via4" -layer_names["m5p"] = "met5" -layer_names["boundary"]= "boundary" -layer_names["stdc"] = "areaid.standardc" -layer_names["mem"] = "areaid.core" -layer_names["text"] = "text" - - -################################################### -# DRC/LVS Rules Setup -################################################### - -# technology parameter -parameter={} -# difftap.2b -parameter["min_tx_size"] = 0.150 -parameter["beta"] = 3 - -parameter["6T_inv_nmos_size"] = 0.205 -parameter["6T_inv_pmos_size"] = 0.09 -parameter["6T_access_size"] = 0.135 - -drc = d.design_rules("sky130") - -# grid size -drc["grid"] = 0.005 - -#DRC/LVS test set_up -# Switching between calibre and magic can be useful for development, -# it eventually should be deleted. -NDA_PDK_ROOT = os.environ.get("NDA_PDK_ROOT", False) -use_calibre = bool(NDA_PDK_ROOT) -use_calibre = False -use_klayout = False -if use_calibre: - # Correct order according to s8 - pin_purpose = 16 - label_purpose = 5 - - drc["drc_rules"] = NDA_PDK_ROOT + "/DRC/Calibre/s8_drcRules" - drc["lvs_rules"] = NDA_PDK_ROOT + "/LVS/Calibre/lvs_s8_opts" - drc["xrc_rules"] = NDA_PDK_ROOT + "/PEX/xRC/extLvsRules_s8_5lm" - drc["layer_map"] = NDA_PDK_ROOT + "/VirtuosoOA/libs/technology_library/s8phirs_10r.layermap" - -# minwidth_tx with contact (no dog bone transistors) -# difftap.2b -drc["minwidth_tx"] = 0.360 -drc["minlength_channel"] = 0.150 - -drc["pwell_to_nwell"] = 0 -# nwell.1 Minimum width of nwell/pwell -drc.add_layer("nwell", - width=0.840, - spacing=1.270) - -# poly.1a Minimum width of poly -# poly.2 Minimum spacing of poly AND active -drc.add_layer("poly", - width=0.150, - spacing=0.210) -# poly.8 -drc["poly_extend_active"] = 0.13 -# Not a rule -drc["poly_to_contact"] = 0 -# poly.7 Minimum enclosure of active around gate -drc["active_enclose_gate"] = 0.075 -# poly.4 Minimum spacing of field poly to active -drc["poly_to_active"] = 0.075 -# poly.2 Minimum spacing of field poly -drc["poly_to_field_poly"] = 0.210 - -# difftap.1 Minimum width of active -# difftap.3 Minimum spacing of active -drc.add_layer("active", - width=0.150, - spacing=0.270) -# difftap.8 -drc.add_enclosure("nwell", - layer="active", - enclosure=0.18, - extension=0.18) - -# nsd/psd.5a -drc.add_enclosure("implant", - layer="active", - enclosure=0.125) - -# Same as active enclosure? -drc["implant_to_contact"] = 0.070 -# nsd/psd.1 nsd/psd.2 -drc.add_layer("implant", - width=0.380, - spacing=0.380, - area=0.265) - -# licon.1, licon.2 -drc.add_layer("contact", - width=0.170, - spacing=0.170) -# licon.5c (0.06 extension), (licon.7 for extension) -drc.add_enclosure("active", - layer="contact", - enclosure=0.040, - extension=0.060) -# licon.7 -drc["tap_extend_contact"] = 0.120 - -# licon.8 Minimum enclosure of poly around contact -drc.add_enclosure("poly", - layer="contact", - enclosure=0.08, - extension=0.08) -# licon.11a -drc["active_contact_to_gate"] = 0.050 -# npc.4 > licon.14 0.19 > licon.11a -drc["poly_contact_to_gate"] = 0.270 -# licon.15 -drc["npc_enclose_poly"] = 0.1 - -# li.1, li.3 -drc.add_layer("li", - width=0.170, - spacing=0.170) - -# licon.5 -drc.add_enclosure("li", - layer="contact", - enclosure=0, - extension=0.080) - -drc.add_enclosure("li", - layer="mcon", - enclosure=0, - extension=0.080) -# mcon.1, mcon.2 -drc.add_layer("mcon", - width=0.170, - spacing=0.210) - -# m1.1 Minimum width of metal1 -# m1.2 Minimum spacing of metal1 -# m1.6 Minimum area of metal1 -drc.add_layer("m1", - width=0.140, - spacing=0.140, - area=0.083) -# m1.4 Minimum enclosure of metal1 -# m1.5 Minimum enclosure around contact on two opposite sides -drc.add_enclosure("m1", - layer="mcon", - enclosure=0.030, - extension=0.060) -# via.4a Minimum enclosure around via1 -# via.5a Minimum enclosure around via1 on two opposite sides -drc.add_enclosure("m1", - layer="via1", - enclosure=0.055, - extension=0.085) - -# via.1a Minimum width of via1 -# via.2 Minimum spacing of via1 -drc.add_layer("via1", - width=0.150, - spacing=0.170) - -# m2.1 Minimum width of intermediate metal -# m2.2 Minimum spacing of intermediate metal -# m2.6 Minimum area of metal2 -drc.add_layer("m2", - width=0.140, - spacing=0.140, - area=0.0676) -# m2.4 Minimum enclosure around via1 -# m2.5 Minimum enclosure around via1 on two opposite sides -drc.add_enclosure("m2", - layer="via1", - enclosure=0.055, - extension=0.085) -# via2.4 Minimum enclosure around via2 -# via2.5 Minimum enclosure around via2 on two opposite sides -drc.add_enclosure("m2", - layer="via2", - enclosure=0.040, - extension=0.085) - -# via2.1a Minimum width of Via2 -# via2.2 Minimum spacing of Via2 -drc.add_layer("via2", - width=0.200, - spacing=0.200) - -# m3.1 Minimum width of metal3 -# m3.2 Minimum spacing of metal3 -# m3.6 Minimum area of metal3 -drc.add_layer("m3", - width=0.300, - spacing=0.300, - area=0.240) -# m3.4 Minimum enclosure around via2 -drc.add_enclosure("m3", - layer="via2", - enclosure=0.065) -# via3.4 Minimum enclosure around via3 -# via3.5 Minimum enclosure around via3 on two opposite sides -drc.add_enclosure("m3", - layer="via3", - enclosure=0.060, - extension=0.090) - -# via3.1 Minimum width of Via3 -# via3.2 Minimum spacing of Via3 -drc.add_layer("via3", - width=0.200, - spacing=0.200) - -# m4.1 Minimum width of metal4 -# m4.2 Minimum spacing of metal4 -# m4.7 Minimum area of metal4 -drc.add_layer("m4", - width=0.300, - spacing=0.300, - area=0.240) -# m4.3 Minimum enclosure around via3 -drc.add_enclosure("m4", - layer="via3", - enclosure=0.065) -# FIXME: Wrong rule m4.3 Minimum enclosure around via3 -drc.add_enclosure("m4", - layer="via4", - enclosure=0.060) - - -# via4.1 Minimum width of Via4 -# via4.2 Minimum spacing of Via4 -drc.add_layer("via4", - width=0.800, - spacing=0.800) - -# FIXME: Wrong rules -# m5.1 Minimum width of metal5 -# m5.2 Minimum spacing of metal5 -# m5.7 Minimum area of metal5 -drc.add_layer("m5", - width=1.600, - spacing=1.600, - area=4.000) -# m5.3 Minimum enclosure around via4 -drc.add_enclosure("m5", - layer="via4", - enclosure=0.310) - - - -# Metal 5-10 are ommitted - -################################################### -# Spice Simulation Parameters -################################################### - -# spice info -spice = {} -spice["nmos"] = "sky130_fd_pr__nfet_01v8" -spice["pmos"] = "sky130_fd_pr__pfet_01v8" -spice["power"]="vccd1" -spice["ground"]="vssd1" - -# whether or not the device model is actually a subckt -spice["device_prefix"] = "X" - -spice["fet_libraries"] = { "TT": [[os.environ.get("SPICE_MODEL_DIR") + "/sky130.lib.spice", "tt"]], - "SS": [[os.environ.get("SPICE_MODEL_DIR") + "/sky130.lib.spice", "ss"]], - "FF": [[os.environ.get("SPICE_MODEL_DIR") + "/sky130.lib.spice", "ff"]], - "SF": [[os.environ.get("SPICE_MODEL_DIR") + "/sky130.lib.spice", "sf"]], - "FS": [[os.environ.get("SPICE_MODEL_DIR") + "/sky130.lib.spice", "fs"]] } - -# spice stimulus related variables -spice["feasible_period"] = 10 # estimated feasible period in ns -spice["supply_voltages"] = [1.7, 1.8, 1.9] # Supply voltage corners in [Volts] -spice["nom_supply_voltage"] = 1.8 # Nominal supply voltage in [Volts] -spice["rise_time"] = 0.005 # rise time in [Nano-seconds] -spice["fall_time"] = 0.005 # fall time in [Nano-seconds] -spice["temperatures"] = [0, 25, 100] # Temperature corners (celcius) -spice["nom_temperature"] = 25 # Nominal temperature (celcius) - -# analytical delay parameters -spice["nom_threshold"] = 0.49 # Typical Threshold voltage in Volts -spice["wire_unit_r"] = 0.125 # Unit wire resistance in ohms/square -spice["wire_unit_c"] = 0.134 # Unit wire capacitance ff/um^2 -spice["min_tx_drain_c"] = 0.7 # Minimum transistor drain capacitance in ff -spice["min_tx_gate_c"] = 0.2 # Minimum transistor gate capacitance in ff -spice["dff_setup"] = 102.5391 # DFF setup time in ps -spice["dff_hold"] = -56 # DFF hold time in ps -spice["dff_in_cap"] = 6.89 # Input capacitance (D) [Femto-farad] -spice["dff_out_cap"] = 6.89 # Output capacitance (Q) [Femto-farad] - -# analytical power parameters, many values are temporary -spice["bitcell_leakage"] = 1 # Leakage power of a single bitcell in nW -spice["inv_leakage"] = 1 # Leakage power of inverter in nW -spice["nand2_leakage"] = 1 # Leakage power of 2-input nand in nW -spice["nand3_leakage"] = 1 # Leakage power of 3-input nand in nW -spice["nand4_leakage"] = 1 # Leakage power of 4-input nand in nW -spice["nor2_leakage"] = 1 # Leakage power of 2-input nor in nW -spice["dff_leakage"] = 1 # Leakage power of flop in nW - -spice["default_event_frequency"] = 100 # Default event activity of every gate. MHz - -# Parameters related to sense amp enable timing and delay chain/RBL sizing -parameter["le_tau"] = 2.25 # In pico-seconds. -parameter["cap_relative_per_ff"] = 7.5 # Units of Relative Capacitance/ Femto-Farad -parameter["dff_clk_cin"] = 30.6 # relative capacitance -parameter["6tcell_wl_cin"] = 3 # relative capacitance -parameter["min_inv_para_delay"] = 2.4 # Tau delay units -parameter["sa_en_pmos_size"] = 0.72 # micro-meters -parameter["sa_en_nmos_size"] = 0.27 # micro-meters -parameter["sa_inv_pmos_size"] = 0.54 # micro-meters -parameter["sa_inv_nmos_size"] = 0.27 # micro-meters -parameter["bitcell_drain_cap"] = 0.1 # In Femto-Farad, approximation of drain capacitance - -################################################### -# Technology Tool Preferences -################################################### - -if use_calibre: - drc_name = "calibre" - lvs_name = "calibre" - pex_name = "calibre" -elif use_klayout: - drc_name = "klayout" - lvs_name = "klayout" - pex_name = "klayout" -else: - drc_name = "magic" - lvs_name = "netgen" - pex_name = "magic" - - -# This is used by uniqify to not rename the library cells -library_prefix_name = "sky130_fd_bd_sram__" -# List of cells to skip running DRC/LVS on directly -# This will look for a maglef file and copy it over the mag file -# before DRC after extraction - -# gds flatglob sky130_fd_bd_sram__openram_sp_cell_opt1a_cell -# gds flatglob sky130_fd_bd_sram__openram_sp_cell_opt1a_replica_ce -# gds flatglob sky130_fd_bd_sram__openram_sp_cell_opt1_replica_cell -# gds flatglob sky130_fd_bd_sram__openram_sp_cell_opt1_replica_ce -# gds flatglob sky130_fd_bd_sram__openram_sp_cell_opt1_replica_cell -# gds flatglob sky130_fd_bd_sram__openram_sp_cell_opt1a_cell -# gds flatglob sky130_fd_bd_sram__sram_sp_cell_fom_serifs - -flatglob = ["*_?mos_m*", - "sky130_fd_bd_sram__sram_sp_cell_fom_serifs", - - "sky130_fd_bd_sram__sram_sp_cell", - "sky130_fd_bd_sram__openram_sp_cell_opt1_replica_cell", - "sky130_fd_bd_sram__openram_sp_cell_opt1a_replica_cell", - - "sky130_fd_bd_sram__sram_sp_cell_opt1_ce", - "sky130_fd_bd_sram__openram_sp_cell_opt1_replica_ce", - "sky130_fd_bd_sram__openram_sp_cell_opt1a_replica_ce", - "sky130_fd_bd_sram__sram_sp_wlstrap_ce", - "sky130_fd_bd_sram__sram_sp_wlstrap_p_ce"] - -blackbox_cells = ["sky130_fd_bd_sram__openram_dp_cell", - "sky130_fd_bd_sram__openram_dp_cell_dummy", - "sky130_fd_bd_sram__openram_dp_cell_replica", - - "sky130_fd_bd_sram__sram_sp_cell_opt1a", - "sky130_fd_bd_sram__openram_sp_cell_opt1a_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", - "sky130_fd_bd_sram__openram_sp_cell_opt1a_replica", - "sky130_fd_bd_sram__sram_sp_colend", - "sky130_fd_bd_sram__sram_sp_colend_cent", - "sky130_fd_bd_sram__sram_sp_colend_p_cent", - "sky130_fd_bd_sram__sram_sp_colenda", - "sky130_fd_bd_sram__sram_sp_colenda_cent", - "sky130_fd_bd_sram__sram_sp_colenda_p_cent", - "sky130_fd_bd_sram__sram_sp_rowend", - "sky130_fd_bd_sram__sram_sp_rowenda", - "sky130_fd_bd_sram__openram_sp_rowend_replica", - "sky130_fd_bd_sram__openram_sp_rowenda_replica", - "sky130_fd_bd_sram__sram_sp_corner", - "sky130_fd_bd_sram__sram_sp_cornera", - "sky130_fd_bd_sram__sram_sp_cornerb", - "sky130_fd_bd_sram__sram_sp_wlstrapa", - "sky130_fd_bd_sram__sram_sp_wlstrap_ce", - "sky130_fd_bd_sram__sram_sp_wlstrap", - "sky130_fd_bd_sram__sram_sp_wlstrap_p_ce", - "sky130_fd_bd_sram__sram_sp_wlstrap_p"] +if not hasattr(OPTS, 'tech_file'): + OPTS.tech_file = 'tech_cypress_cell' +#TODO: FIX THIS TERRIBLE HACK JUST FOR TESTING +exec('from {} import *'.format(OPTS.tech_file)) diff --git a/technology/sky130/tech/tech_configs/tech_custom_cell.py b/technology/sky130/tech/tech_configs/tech_custom_cell.py new file mode 100644 index 00000000..b797cc64 --- /dev/null +++ b/technology/sky130/tech/tech_configs/tech_custom_cell.py @@ -0,0 +1,857 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2023 Regents of the University of California +# All rights reserved. +# + + +import os +from openram import drc as d + +""" +File containing the process technology parameters for Skywater 130nm. +""" + +################################################### +# Custom modules +################################################### + +# This uses the default classes to instantiate module from +# '$OPENRAM_HOME/compiler/modules'. +# Using tech_modules['cellname'] you can override each class by providing a custom +# implementation in '$OPENRAM_TECHDIR/modules/' +# For example: tech_modules["contact"] = "contact_freepdk45" +tech_modules = d.module_type() + + +# These modules have been hand designed and provided in this repository. +tech_modules["nand2_dec"] = "nand2_dec" +tech_modules["nand3_dec"] = "nand3_dec" +tech_modules["nand4_dec"] = "nand4_dec" + +# Override default OpenRAM modules to sky130 modules +# These are for single port and dual port as a list, +# or for both if there is no list, +# or only applicable to one if there is no list. + +#tech_modules["bitcell_1port"] = "sky130_bitcell" +#tech_modules["replica_bitcell_1port"] = "sky130_replica_bitcell" +#tech_modules["dummy_bitcell_1port"] = "sky130_dummy_bitcell" + +tech_modules["replica_bitcell_2port"] = "replica_bitcell_2port" +tech_modules["dummy_bitcell_2port"] = "dummy_bitcell_2port" +tech_modules["bitcell_2port"] = "bitcell_2port" + +tech_modules["bitcell_array"] = ["bitcell_array", "bitcell_array"] +tech_modules["replica_bitcell_array"] = ["replica_bitcell_array", "replica_bitcell_array"] +tech_modules["capped_replica_bitcell_array"] = ["capped_replica_bitcell_array", "capped_replica_bitcell_array"] +tech_modules["dummy_array"] = ["dummy_array", "dummy_array"] + +tech_modules["replica_column"] = ["replica_column", "replica_column"] + +tech_modules["col_cap_array"] = ["col_cap_array", "col_cap_array"] +tech_modules["col_cap"] = ["col_cap_bitcell_1port", "col_cap_bitcell_2port"] +tech_modules["corner"] = ["sky130_corner", None] +tech_modules["internal"] = ["sky130_internal", None] +tech_modules["row_cap_array"] = ["sky130_row_cap_array", "row_cap_array"] +tech_modules["row_cap"] = ["sky130_row_cap", "row_cap_bitcell_2port"] + +# These modules are auto-generated from the nand decoders above and are not +# found in this. +tech_modules["buf_dec"] = "pbuf_dec" +tech_modules["inv_dec"] = "pinv_dec" +tech_modules["and2_dec"] = "and2_dec" +tech_modules["and3_dec"] = "and3_dec" +tech_modules["and4_dec"] = "and4_dec" + +################################################### +# Custom cell properties +################################################### +cell_properties = d.cell_properties() + +cell_properties.power_name = 'VPWR' +cell_properties.ground_name = 'VGND' + +cell_properties.bitcell_power_pin_directions = ("V", "V") + +cell_properties.bitcell_1port.mirror.x = True +cell_properties.bitcell_1port.mirror.y = True +cell_properties.bitcell_1port.end_caps = False +cell_properties.bitcell_1port.has_corners = True + +cell_properties.bitcell_1port.boundary_layer = "boundary" +cell_properties.bitcell_1port.port_order = ['bl', 'br', 'wl', 'vdd', 'gnd'] +cell_properties.bitcell_1port.port_types = ["INPUT", "INPUT", "GROUND", "POWER", "OUTPUT"] +cell_properties.bitcell_1port.port_map = {'bl': 'BL', + 'br': 'BR', + 'gnd': 'VGND', + 'vdd': 'VPWR', + 'wl': 'WL'} + + + + +cell_properties.bitcell_1port.wl_layer = "m2" +cell_properties.bitcell_1port.bl_layer = "m1" +cell_properties.bitcell_1port.vdd_layer = "m1" +cell_properties.bitcell_1port.vdd_dir = "V" +cell_properties.bitcell_1port.gnd_layer = "m1" +cell_properties.bitcell_1port.gnd_dir = "V" + +cell_properties.bitcell_2port.mirror.x = True +cell_properties.bitcell_2port.mirror.y = True +cell_properties.bitcell_2port.end_caps = True +cell_properties.bitcell_2port.has_corners = True +cell_properties.bitcell_2port.port_order = ['bl0', 'br0', 'bl1', 'br1', 'wl0', 'wl1', 'vdd', 'gnd'] +cell_properties.bitcell_2port.port_map = {'bl0': 'BL0', + 'br0': 'BR0', + 'bl1': 'BL1', + 'br1': 'BR1', + 'wl0': 'WL0', + 'wl1': 'WL1', + 'vdd': 'VDD', + 'gnd': 'GND'} + +cell_properties.bitcell_1port.wl_layer = "m2" +cell_properties.bitcell_1port.vdd_layer = "m1" +cell_properties.bitcell_1port.vdd_dir = "V" +cell_properties.bitcell_1port.gnd_layer = "m1" +cell_properties.bitcell_1port.gnd_dir = "V" +cell_properties.bitcell_2port.wl_layer = "m2" +cell_properties.bitcell_2port.vdd_layer = "m1" +cell_properties.bitcell_2port.vdd_dir = "H" +cell_properties.bitcell_2port.gnd_layer = "m2" +cell_properties.bitcell_2port.gnd_dir = "H" + +cell_properties.col_cap_1port_bitcell = d.cell(['bl', 'br', 'vdd', 'gnd',], + ['INPUT', 'INPUT','POWER', 'GROUND', ], + {'bl': 'bl', + 'br': 'br', + 'vdd': 'vdd', + 'gnd': 'gnd',}) +cell_properties.col_cap_1port_bitcell.boundary_layer = "boundary" + +cell_properties.col_cap_1port_strap_power = d.cell(['vdd', 'vpb', 'vnb'], + ['POWER', 'BIAS', 'BIAS'], + {'vnb': 'VNB', + 'vpb': 'VPB', + 'vdd': 'VPWR'}) +cell_properties.col_cap_1port_strap_power.boundary_layer = "boundary" + +cell_properties.col_cap_1port_strap_ground = d.cell(['gnd', 'vpb', 'vnb'], + ['GROUND', 'BIAS', 'BIAS'], + {'vnb': 'VNB', + 'vpb': 'VPB', + 'gnd': 'VGND'}) +cell_properties.col_cap_1port_strap_ground.boundary_layer = "boundary" + +cell_properties.row_cap_1port_cell = d.cell(['vdd', 'wl'], + ['POWER', 'INPUT'], + {'wl': 'WL', + 'vdd': 'VPWR'}) +cell_properties.row_cap_1port_cell.boundary_layer = "boundary" + +cell_properties.col_cap_2port.port_order = ['bl0', 'br0', 'bl1', 'br1', 'vdd'] +cell_properties.col_cap_2port.port_map = {'bl0': 'BL0', + 'br0': 'BR0', + 'bl1': 'BL1', + 'br1': 'BR1', + 'vdd': 'VDD'} + +cell_properties.row_cap_2port.port_order = ['wl0', 'wl1', 'gnd'] +cell_properties.row_cap_2port.port_map = {'wl0': 'WL0', + 'wl1': 'WL1', + 'gnd': 'GND'} + + +cell_properties.ptx.bin_spice_models = True +cell_properties.ptx.model_is_subckt = True + +cell_properties.pgate.add_implants = True + +cell_properties.use_strap = False +cell_properties.strap_module = "internal" +cell_properties.strap_version = "wlstrap" + +cell_properties.dff.port_order = ['D', 'Q', 'clk', 'vdd', 'gnd'] +cell_properties.dff.port_map = {'D': 'D', + 'Q': 'Q', + 'clk': 'CLK', + 'vdd': 'VDD', + 'gnd': 'GND'} + +cell_properties.nand2_dec.port_order = ['A', 'B', 'Z', 'vdd', 'gnd'] +cell_properties.nand2_dec.port_map = {'A': 'A', + 'B': 'B', + 'Z': 'Z', + 'vdd': 'VDD', + 'gnd': 'GND'} + +cell_properties.nand3_dec.port_order = ['A', 'B', 'C', 'Z', 'vdd', 'gnd'] +cell_properties.nand3_dec.port_map = {'A': 'A', + 'B': 'B', + 'C': 'C', + 'Z': 'Z', + 'vdd': 'VDD', + 'gnd': 'GND'} + +cell_properties.nand4_dec.port_order = ['A', 'B', 'C', 'D', 'Z', 'vdd', 'gnd'] +cell_properties.nand4_dec.port_map = {'A': 'A', + 'B': 'B', + 'C': 'C', + 'D': 'D', + 'Z': 'Z', + 'vdd': 'VDD', + 'gnd': 'GND'} + +cell_properties.sense_amp.port_order = ['bl', 'br', 'dout', 'en', 'vdd', 'gnd'] +cell_properties.sense_amp.port_map = {'bl': 'BL', + 'br': 'BR', + 'dout': 'DOUT', + 'en': 'EN', + 'vdd': 'VDD', + 'gnd': 'GND'} + +cell_properties.write_driver.port_order = ['din', 'bl', 'br', 'en', 'vdd', 'gnd'] +cell_properties.write_driver.port_map = {'din': 'DIN', + 'bl': 'BL', + 'br': 'BR', + 'en': 'EN', + 'vdd': 'VDD', + 'gnd': 'GND'} + + +# You can override the GDS for custom cell using the following: +# If it is a list, the first is single port and the second is dual port. +# If it is string, it is used for both single and dual port. +cell_properties.names["dff"] = "sky130_fd_bd_sram__openram_dff" +cell_properties.names["nand2_dec"] = ["sky130_fd_bd_sram__openram_dp_nand2_dec", "sky130_fd_bd_sram__openram_dp_nand2_dec"] +cell_properties.names["nand3_dec"] = ["sky130_fd_bd_sram__openram_dp_nand3_dec", "sky130_fd_bd_sram__openram_dp_nand3_dec"] +cell_properties.names["nand4_dec"] = ["sky130_fd_bd_sram__openram_dp_nand4_dec", "sky130_fd_bd_sram__openram_dp_nand4_dec"] + +cell_properties.names["bitcell_1port"] = "sky130_custom_cell" +cell_properties.names["replica_bitcell_1port"] = "sky130_custom_replica" +cell_properties.names["dummy_bitcell_1port"] = "sky130_custom_dummy" + +cell_properties.names["bitcell_2port"] = "sky130_fd_bd_sram__openram_dp_cell" +cell_properties.names["dummy_bitcell_2port"] = "sky130_fd_bd_sram__openram_dp_cell_dummy" +cell_properties.names["replica_bitcell_2port"] = "sky130_fd_bd_sram__openram_dp_cell_replica" +cell_properties.names["col_cap_bitcell_2port"] = "sky130_fd_bd_sram__openram_dp_cell_cap_col" +cell_properties.names["row_cap_bitcell_2port"] = "sky130_fd_bd_sram__openram_dp_cell_cap_row" +cell_properties.names["sense_amp"] = "sky130_fd_bd_sram__openram_sense_amp" +cell_properties.names["write_driver"] = "sky130_fd_bd_sram__openram_write_driver" + +array_row_multiple = 2 +array_col_multiple = 2 + +################################################### +# Custom layer properties +################################################### +layer_properties = d.layer_properties() +layer_properties.hierarchical_decoder.bus_layer = "m1" +layer_properties.hierarchical_decoder.bus_directions = "nonpref" +layer_properties.hierarchical_decoder.input_layer = "li" +layer_properties.hierarchical_decoder.output_layer = "m2" +layer_properties.hierarchical_decoder.vertical_supply = True + +layer_properties.hierarchical_predecode.bus_layer = "m1" +layer_properties.hierarchical_predecode.bus_directions = "nonpref" +# This is added to allow the column decoder connections on m2 +layer_properties.hierarchical_predecode.bus_pitch_factor = 1.2 +layer_properties.hierarchical_predecode.bus_space_factor = 1.5 +layer_properties.hierarchical_predecode.input_layer = "li" +layer_properties.hierarchical_predecode.output_layer = "m2" +layer_properties.hierarchical_predecode.vertical_supply = True +layer_properties.hierarchical_predecode.force_horizontal_input_contact = True + +layer_properties.bank.stack = "m2_stack" +layer_properties.bank.pitch = "m3_pitch" + +layer_properties.column_mux_array.select_layer = "m3" +layer_properties.column_mux_array.bitline_layer = "m1" + +layer_properties.port_address.supply_offset = True + +layer_properties.port_data.enable_layer = "m1" +layer_properties.port_data.channel_route_bitlines = False + +layer_properties.replica_column.even_rows = False + +layer_properties.wordline_driver.vertical_supply = True + +layer_properties.global_wordline_layer = "m5" + + +################################################### +# Discrete tx bins +################################################### +# enforce that tx sizes are within 25% of requested size after fingering. +accuracy_requirement = 0.75 +nmos_bins = { + 0.15 : [0.36, 0.39, 0.42, 0.52, 0.54, 0.55, 0.58, 0.6, 0.61, 0.64, 0.65, 0.74, 0.84, 1.0, 1.26, 1.68, 2.0, 3.0, 5.0, 7.0], + 0.18 : [0.42, 0.65, 1.0, 3.0, 5.0, 7.0], + 0.25 : [0.65, 1.0, 3.0, 5.0, 7.0], + 0.5 : [0.42, 0.55, 0.65, 1.0, 3.0, 5.0, 7.0], + 1.0 : [0.42, 0.65, 1.0, 3.0, 5.0, 7.0], + 2.0 : [0.42, 0.65, 1.0, 3.0, 5.0, 7.0], + 4.0 : [0.42, 0.65, 1.0, 3.0, 5.0, 7.0], + 8.0 : [0.42, 0.65, 1.0, 3.0, 5.0, 7.0], + 20.0 : [0.42, 0.65, 1.0, 3.0, 5.0, 7.0] +} + +pmos_bins = { + 0.15 : [0.42, 0.55, 0.64, 0.84, 1.0, 1.12, 1.26, 1.65, 1.68, 2.0, 3.0, 5.0, 7.0], + 1.0 : [0.42, 0.55, 1.0, 3.0, 5.0, 7.0], + 2.0 : [0.42, 0.55, 1.0, 3.0, 5.0, 7.0], + 4.0 : [0.42, 0.55, 1.0, 3.0, 5.0, 7.0], + 8.0 : [0.42, 0.55, 1.0, 3.0, 5.0, 7.0], + 0.17 : [0.42, 0.55, 0.64, 0.84, 1.0, 1.12], + 0.18 : [0.42, 0.55, 0.64, 0.84, 1.0, 1.12, 1.26, 1.68, 2.0, 3.0, 5.0, 7.0], + 0.25 : [1.0, 3.0, 5.0, 7.0], + 0.5 : [0.42, 0.55, 1.0, 3.0, 5.0, 7.0], + 20.0 : [0.42] +} +################################################### +# GDS file info +################################################### +GDS = {} +# gds units +# From http://www.cnf.cornell.edu/cnf_spie9.html: "The first +# is the size of a database unit in user units. The second is the size +# of a database unit in meters. For example, if your library was +# created with the default units (user unit = 1 um and 1000 database +# units per user unit), then the first number would be 0.001 and the +# second number would be 10-9. Typically, the first number is less than +# 1, since you use more than 1 database unit per user unit. To +# calculate the size of a user unit in meters, divide the second number +# by the first." +GDS["unit"] = (0.001, 1e-9) +#GDS["unit"]=(0.001, 1e-6) + +################################################### +# Interconnect stacks +################################################### + +poly_stack = ("poly", "contact", "li") +active_stack = ("active", "contact", "li") +li_stack = ("li", "mcon", "m1") +m1_stack = ("m1", "via1", "m2") +m2_stack = ("m2", "via2", "m3") +m3_stack = ("m3", "via3", "m4") +m4_stack = ("m4", "via4", "m5") + +lef_rom_interconnect = ["m1", "m2", "m3", "m4"] + +layer_indices = {"poly": 0, + "active": 0, + "nwell": 0, + "li": 1, + "m1": 2, + "m2": 3, + "m3": 4, + "m4": 5, + "m5": 6} + +# The FEOL stacks get us up to m1 +feol_stacks = [poly_stack, + active_stack, + li_stack] + +# The BEOL stacks are m1 and up +beol_stacks = [m1_stack, + m2_stack, + m3_stack, + m4_stack] + +layer_stacks = feol_stacks + beol_stacks + +preferred_directions = {"poly": "V", + "active": "V", + "li": "V", + "m1": "H", + "m2": "V", + "m3": "H", + "m4": "V", + "m5": "H"} + +################################################### +# GDS Layer Map +################################################### + + +layer = {} +layer["active"] = (65, 20) # diff +layer["activep"] = (65, 20) # diff +layer["tap"] = (65, 44) # tap +layer["pwellp"] = (122,16) +layer["nwell"] = (64, 20) # nwell +layer["dnwell"] = (64,18) +layer["nimplant"]= (93, 44) # nsdm +layer["pimplant"]= (94, 20) # psdm +layer["vtl"] = (125, 44) # lvtn +layer["vth"] = (78, 44) # hvtp (pmos only) +layer["thkox"] = (8, 0) +layer["poly"] = (66, 20) +layer["contact"] = (66, 44) # licon1 +layer["npc"] = (95, 20) # npc (nitride cut) +layer["li"] = (67, 20) # active li1 +layer["mcon"] = (67, 44) # mcon +layer["m1"] = (68, 20) # met1 +layer["m1p"] = (68, 5) # met1 pin +layer["via1"] = (68, 44) # via1 +layer["m2"] = (69, 20) # met2 +layer["m2p"] = (69, 5) # met2 pin +layer["via2"] = (69, 44) # via2 +layer["m3"] = (70, 20) # met3 +layer["m3p"] = (70, 5) # met3 pin +layer["via3"] = (70, 44) # via3 +layer["m4"] = (71, 20) # met4 +layer["m4p"] = (71, 5) # met4 pin +layer["via4"] = (71, 44) # via4 +layer["m5"] = (72, 20) # met5 +layer["m5p"] = (72, 5) # met5 pin +layer["boundary"]= (235, 4) +# specific boundary type to define standard cell regions for DRC +layer["stdc"] = (81, 4) +layer["mem"] = (81, 2) +# Not an official sky130 layer, but useful for router debug infos +layer["text"]= (234, 5) +# Excpected value according to sky130A tech file +# If calibre is enabled, these will be swapped below +#pin_purpose = 5 +label_purpose = 5 +#label_purpose = 16 +#pin_purpose = 16 +#label_purpose = 5 + +# pin_read purposes +special_purposes = {layer["nwell"][0]: [layer["nwell"][1], 5, 59, 16]} +#layer_override = {"VNB\x00": ["pwell",122]} +layer_override = {"vnb": layer["pwellp"], "VNB": layer["pwellp"]} +layer_override_name = {"vnb": "pwellp", "VNB": "pwellp"} +layer_override_purpose = {122: (64, 59)} +# Layer names for external PDKs +layer_names = {} +layer_names["active"] = "diff" +layer_names["activep"] = "diff" +layer_names["tap"] = "tap" +layer_names["pwellp"] = "pwellp" +layer_names["nwell"] = "nwell" +layer_names["dnwell"] = "dnwell" +layer_names["nimplant"]= "nsdm" +layer_names["pimplant"]= "psdm" +layer_names["vtl"] = "lvtn" +layer_names["vth"] = "hvtp" +layer_names["thkox"] = "thkox" +layer_names["poly"] = "poly" +layer_names["contact"] = "licon1" +layer_names["li"] = "li1" +layer_names["mcon"] = "mcon" +layer_names["m1"] = "met1" +layer_names["m1p"] = "met1" +layer_names["via1"] = "via" +layer_names["m2"] = "met2" +layer_names["m2p"] = "met2" +layer_names["via2"] = "via2" +layer_names["m3"] = "met3" +layer_names["m3p"] = "met3" +layer_names["via3"] = "via3" +layer_names["m4"] = "met4" +layer_names["m4p"] = "met4" +layer_names["via4"] = "via4" +layer_names["m5p"] = "met5" +layer_names["boundary"]= "boundary" +layer_names["stdc"] = "areaid.standardc" +layer_names["mem"] = "areaid.core" +layer_names["text"] = "text" + + +################################################### +# DRC/LVS Rules Setup +################################################### + +# technology parameter +parameter={} +# difftap.2b +parameter["min_tx_size"] = 0.150 +parameter["beta"] = 3 + +parameter["6T_inv_nmos_size"] = 0.205 +parameter["6T_inv_pmos_size"] = 0.09 +parameter["6T_access_size"] = 0.135 + +drc = d.design_rules("sky130") + +# grid size +drc["grid"] = 0.005 + +#DRC/LVS test set_up +# Switching between calibre and magic can be useful for development, +# it eventually should be deleted. +NDA_PDK_ROOT = os.environ.get("NDA_PDK_ROOT", False) +use_calibre = bool(NDA_PDK_ROOT) +use_calibre = False +use_klayout = False +if use_calibre: + # Correct order according to s8 + pin_purpose = 16 + label_purpose = 5 + + drc["drc_rules"] = NDA_PDK_ROOT + "/DRC/Calibre/s8_drcRules" + drc["lvs_rules"] = NDA_PDK_ROOT + "/LVS/Calibre/lvs_s8_opts" + drc["xrc_rules"] = NDA_PDK_ROOT + "/PEX/xRC/extLvsRules_s8_5lm" + drc["layer_map"] = NDA_PDK_ROOT + "/VirtuosoOA/libs/technology_library/s8phirs_10r.layermap" + +# minwidth_tx with contact (no dog bone transistors) +# difftap.2b +drc["minwidth_tx"] = 0.360 +drc["minlength_channel"] = 0.150 + +drc["pwell_to_nwell"] = 0 +# nwell.1 Minimum width of nwell/pwell +drc.add_layer("nwell", + width=0.840, + spacing=1.270) + +# poly.1a Minimum width of poly +# poly.2 Minimum spacing of poly AND active +drc.add_layer("poly", + width=0.150, + spacing=0.210) +# poly.8 +drc["poly_extend_active"] = 0.13 +# Not a rule +drc["poly_to_contact"] = 0 +# poly.7 Minimum enclosure of active around gate +drc["active_enclose_gate"] = 0.075 +# poly.4 Minimum spacing of field poly to active +drc["poly_to_active"] = 0.075 +# poly.2 Minimum spacing of field poly +drc["poly_to_field_poly"] = 0.210 + +# difftap.1 Minimum width of active +# difftap.3 Minimum spacing of active +drc.add_layer("active", + width=0.150, + spacing=0.270) +# difftap.8 +drc.add_enclosure("nwell", + layer="active", + enclosure=0.18, + extension=0.18) + +# nsd/psd.5a +drc.add_enclosure("implant", + layer="active", + enclosure=0.125) + +# Same as active enclosure? +drc["implant_to_contact"] = 0.070 +# nsd/psd.1 nsd/psd.2 +drc.add_layer("implant", + width=0.380, + spacing=0.380, + area=0.265) + +# licon.1, licon.2 +drc.add_layer("contact", + width=0.170, + spacing=0.170) +# licon.5c (0.06 extension), (licon.7 for extension) +drc.add_enclosure("active", + layer="contact", + enclosure=0.040, + extension=0.060) +# licon.7 +drc["tap_extend_contact"] = 0.120 + +# licon.8 Minimum enclosure of poly around contact +drc.add_enclosure("poly", + layer="contact", + enclosure=0.08, + extension=0.08) +# licon.11a +drc["active_contact_to_gate"] = 0.050 +# npc.4 > licon.14 0.19 > licon.11a +drc["poly_contact_to_gate"] = 0.270 +# licon.15 +drc["npc_enclose_poly"] = 0.1 + +# li.1, li.3 +drc.add_layer("li", + width=0.170, + spacing=0.170) + +# licon.5 +drc.add_enclosure("li", + layer="contact", + enclosure=0, + extension=0.080) + +drc.add_enclosure("li", + layer="mcon", + enclosure=0, + extension=0.080) +# mcon.1, mcon.2 +drc.add_layer("mcon", + width=0.170, + spacing=0.210) + +# m1.1 Minimum width of metal1 +# m1.2 Minimum spacing of metal1 +# m1.6 Minimum area of metal1 +drc.add_layer("m1", + width=0.140, + spacing=0.140, + area=0.083) +# m1.4 Minimum enclosure of metal1 +# m1.5 Minimum enclosure around contact on two opposite sides +drc.add_enclosure("m1", + layer="mcon", + enclosure=0.030, + extension=0.060) +# via.4a Minimum enclosure around via1 +# via.5a Minimum enclosure around via1 on two opposite sides +drc.add_enclosure("m1", + layer="via1", + enclosure=0.055, + extension=0.085) + +# via.1a Minimum width of via1 +# via.2 Minimum spacing of via1 +drc.add_layer("via1", + width=0.150, + spacing=0.170) + +# m2.1 Minimum width of intermediate metal +# m2.2 Minimum spacing of intermediate metal +# m2.6 Minimum area of metal2 +drc.add_layer("m2", + width=0.140, + spacing=0.140, + area=0.0676) +# m2.4 Minimum enclosure around via1 +# m2.5 Minimum enclosure around via1 on two opposite sides +drc.add_enclosure("m2", + layer="via1", + enclosure=0.055, + extension=0.085) +# via2.4 Minimum enclosure around via2 +# via2.5 Minimum enclosure around via2 on two opposite sides +drc.add_enclosure("m2", + layer="via2", + enclosure=0.040, + extension=0.085) + +# via2.1a Minimum width of Via2 +# via2.2 Minimum spacing of Via2 +drc.add_layer("via2", + width=0.200, + spacing=0.200) + +# m3.1 Minimum width of metal3 +# m3.2 Minimum spacing of metal3 +# m3.6 Minimum area of metal3 +drc.add_layer("m3", + width=0.300, + spacing=0.300, + area=0.240) +# m3.4 Minimum enclosure around via2 +drc.add_enclosure("m3", + layer="via2", + enclosure=0.065) +# via3.4 Minimum enclosure around via3 +# via3.5 Minimum enclosure around via3 on two opposite sides +drc.add_enclosure("m3", + layer="via3", + enclosure=0.060, + extension=0.090) + +# via3.1 Minimum width of Via3 +# via3.2 Minimum spacing of Via3 +drc.add_layer("via3", + width=0.200, + spacing=0.200) + +# m4.1 Minimum width of metal4 +# m4.2 Minimum spacing of metal4 +# m4.7 Minimum area of metal4 +drc.add_layer("m4", + width=0.300, + spacing=0.300, + area=0.240) +# m4.3 Minimum enclosure around via3 +drc.add_enclosure("m4", + layer="via3", + enclosure=0.065) +# FIXME: Wrong rule m4.3 Minimum enclosure around via3 +drc.add_enclosure("m4", + layer="via4", + enclosure=0.060) + + +# via4.1 Minimum width of Via4 +# via4.2 Minimum spacing of Via4 +drc.add_layer("via4", + width=0.800, + spacing=0.800) + +# FIXME: Wrong rules +# m5.1 Minimum width of metal5 +# m5.2 Minimum spacing of metal5 +# m5.7 Minimum area of metal5 +drc.add_layer("m5", + width=1.600, + spacing=1.600, + area=4.000) +# m5.3 Minimum enclosure around via4 +drc.add_enclosure("m5", + layer="via4", + enclosure=0.310) + + + +# Metal 5-10 are ommitted + +################################################### +# Spice Simulation Parameters +################################################### + +# spice info +spice = {} +spice["nmos"] = "sky130_fd_pr__nfet_01v8" +spice["pmos"] = "sky130_fd_pr__pfet_01v8" +spice["power"]="vccd1" +spice["ground"]="vssd1" + +# whether or not the device model is actually a subckt +spice["device_prefix"] = "X" + +spice["fet_libraries"] = { "TT": [[os.environ.get("SPICE_MODEL_DIR") + "/sky130.lib.spice", "tt"]], + "SS": [[os.environ.get("SPICE_MODEL_DIR") + "/sky130.lib.spice", "ss"]], + "FF": [[os.environ.get("SPICE_MODEL_DIR") + "/sky130.lib.spice", "ff"]], + "SF": [[os.environ.get("SPICE_MODEL_DIR") + "/sky130.lib.spice", "sf"]], + "FS": [[os.environ.get("SPICE_MODEL_DIR") + "/sky130.lib.spice", "fs"]] } + +# spice stimulus related variables +spice["feasible_period"] = 10 # estimated feasible period in ns +spice["supply_voltages"] = [1.7, 1.8, 1.9] # Supply voltage corners in [Volts] +spice["nom_supply_voltage"] = 1.8 # Nominal supply voltage in [Volts] +spice["rise_time"] = 0.005 # rise time in [Nano-seconds] +spice["fall_time"] = 0.005 # fall time in [Nano-seconds] +spice["temperatures"] = [0, 25, 100] # Temperature corners (celcius) +spice["nom_temperature"] = 25 # Nominal temperature (celcius) + +# analytical delay parameters +spice["nom_threshold"] = 0.49 # Typical Threshold voltage in Volts +spice["wire_unit_r"] = 0.125 # Unit wire resistance in ohms/square +spice["wire_unit_c"] = 0.134 # Unit wire capacitance ff/um^2 +spice["min_tx_drain_c"] = 0.7 # Minimum transistor drain capacitance in ff +spice["min_tx_gate_c"] = 0.2 # Minimum transistor gate capacitance in ff +spice["dff_setup"] = 102.5391 # DFF setup time in ps +spice["dff_hold"] = -56 # DFF hold time in ps +spice["dff_in_cap"] = 6.89 # Input capacitance (D) [Femto-farad] +spice["dff_out_cap"] = 6.89 # Output capacitance (Q) [Femto-farad] + +# analytical power parameters, many values are temporary +spice["bitcell_leakage"] = 1 # Leakage power of a single bitcell in nW +spice["inv_leakage"] = 1 # Leakage power of inverter in nW +spice["nand2_leakage"] = 1 # Leakage power of 2-input nand in nW +spice["nand3_leakage"] = 1 # Leakage power of 3-input nand in nW +spice["nand4_leakage"] = 1 # Leakage power of 4-input nand in nW +spice["nor2_leakage"] = 1 # Leakage power of 2-input nor in nW +spice["dff_leakage"] = 1 # Leakage power of flop in nW + +spice["default_event_frequency"] = 100 # Default event activity of every gate. MHz + +# Parameters related to sense amp enable timing and delay chain/RBL sizing +parameter["le_tau"] = 2.25 # In pico-seconds. +parameter["cap_relative_per_ff"] = 7.5 # Units of Relative Capacitance/ Femto-Farad +parameter["dff_clk_cin"] = 30.6 # relative capacitance +parameter["6tcell_wl_cin"] = 3 # relative capacitance +parameter["min_inv_para_delay"] = 2.4 # Tau delay units +parameter["sa_en_pmos_size"] = 0.72 # micro-meters +parameter["sa_en_nmos_size"] = 0.27 # micro-meters +parameter["sa_inv_pmos_size"] = 0.54 # micro-meters +parameter["sa_inv_nmos_size"] = 0.27 # micro-meters +parameter["bitcell_drain_cap"] = 0.1 # In Femto-Farad, approximation of drain capacitance + +################################################### +# Technology Tool Preferences +################################################### + +if use_calibre: + drc_name = "calibre" + lvs_name = "calibre" + pex_name = "calibre" +elif use_klayout: + drc_name = "klayout" + lvs_name = "klayout" + pex_name = "klayout" +else: + drc_name = "magic" + lvs_name = "netgen" + pex_name = "magic" + + +# This is used by uniqify to not rename the library cells +library_prefix_name = "sky130_fd_bd_sram__" +# List of cells to skip running DRC/LVS on directly +# This will look for a maglef file and copy it over the mag file +# before DRC after extraction + +# gds flatglob sky130_fd_bd_sram__openram_sp_cell_opt1a_cell +# gds flatglob sky130_fd_bd_sram__openram_sp_cell_opt1a_replica_ce +# gds flatglob sky130_fd_bd_sram__openram_sp_cell_opt1_replica_cell +# gds flatglob sky130_fd_bd_sram__openram_sp_cell_opt1_replica_ce +# gds flatglob sky130_fd_bd_sram__openram_sp_cell_opt1_replica_cell +# gds flatglob sky130_fd_bd_sram__openram_sp_cell_opt1a_cell +# gds flatglob sky130_fd_bd_sram__sram_sp_cell_fom_serifs + +flatglob = ["*_?mos_m*", + "sky130_fd_bd_sram__sram_sp_cell_fom_serifs", + + "sky130_fd_bd_sram__sram_sp_cell", + "sky130_fd_bd_sram__openram_sp_cell_opt1_replica_cell", + "sky130_fd_bd_sram__openram_sp_cell_opt1a_replica_cell", + + "sky130_fd_bd_sram__sram_sp_cell_opt1_ce", + "sky130_fd_bd_sram__openram_sp_cell_opt1_replica_ce", + "sky130_fd_bd_sram__openram_sp_cell_opt1a_replica_ce", + "sky130_fd_bd_sram__sram_sp_wlstrap_ce", + "sky130_fd_bd_sram__sram_sp_wlstrap_p_ce"] + +blackbox_cells = ["sky130_fd_bd_sram__openram_dp_cell", + "sky130_fd_bd_sram__openram_dp_cell_dummy", + "sky130_fd_bd_sram__openram_dp_cell_replica", + + "sky130_fd_bd_sram__openram_sp_cell_opt1_noblcon", + "sky130_fd_bd_sram__openram_sp_cell_opt1a_noblcon", + "sky130_fd_bd_sram__openram_sp_colend_replica", + "sky130_fd_bd_sram__openram_sp_colenda_replica", + "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", + "sky130_fd_bd_sram__openram_sp_cell_opt1a_replica", + "sky130_fd_bd_sram__sram_sp_colend", + "sky130_fd_bd_sram__sram_sp_colend_cent", + "sky130_fd_bd_sram__sram_sp_colend_p_cent", + "sky130_fd_bd_sram__sram_sp_colenda", + "sky130_fd_bd_sram__sram_sp_colenda_cent", + "sky130_fd_bd_sram__sram_sp_colenda_p_cent", + "sky130_fd_bd_sram__sram_sp_rowend", + "sky130_fd_bd_sram__sram_sp_rowenda", + "sky130_fd_bd_sram__openram_sp_rowend_replica", + "sky130_fd_bd_sram__openram_sp_rowenda_replica", + "sky130_fd_bd_sram__sram_sp_corner", + "sky130_fd_bd_sram__sram_sp_cornera", + "sky130_fd_bd_sram__sram_sp_cornerb", + "sky130_fd_bd_sram__sram_sp_wlstrapa", + "sky130_fd_bd_sram__sram_sp_wlstrap_ce", + "sky130_fd_bd_sram__sram_sp_wlstrap", + "sky130_fd_bd_sram__sram_sp_wlstrap_p_ce", + "sky130_fd_bd_sram__sram_sp_wlstrap_p"] \ No newline at end of file diff --git a/technology/sky130/tech/tech_configs/tech_cypress_cell.py b/technology/sky130/tech/tech_configs/tech_cypress_cell.py new file mode 100644 index 00000000..e3e10617 --- /dev/null +++ b/technology/sky130/tech/tech_configs/tech_cypress_cell.py @@ -0,0 +1,849 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2023 Regents of the University of California +# All rights reserved. +# + + +import os +from openram import drc as d + +""" +File containing the process technology parameters for Skywater 130nm. +""" + +################################################### +# Custom modules +################################################### + +# This uses the default classes to instantiate module from +# '$OPENRAM_HOME/compiler/modules'. +# Using tech_modules['cellname'] you can override each class by providing a custom +# implementation in '$OPENRAM_TECHDIR/modules/' +# For example: tech_modules["contact"] = "contact_freepdk45" +tech_modules = d.module_type() + +# These modules have been hand designed and provided in this repository. +tech_modules["nand2_dec"] = "nand2_dec" +tech_modules["nand3_dec"] = "nand3_dec" +tech_modules["nand4_dec"] = "nand4_dec" + +# Override default OpenRAM modules to sky130 modules +# These are for single port and dual port as a list, +# or for both if there is no list, +# or only applicable to one if there is no list. +tech_modules["bitcell_1port"] = "sky130_bitcell" +tech_modules["replica_bitcell_1port"] = "sky130_replica_bitcell" +tech_modules["dummy_bitcell_1port"] = "sky130_dummy_bitcell" + +tech_modules["replica_bitcell_2port"] = "replica_bitcell_2port" +tech_modules["dummy_bitcell_2port"] = "dummy_bitcell_2port" +tech_modules["bitcell_2port"] = "bitcell_2port" + +tech_modules["bitcell_array"] = ["sky130_bitcell_array", "bitcell_array"] +tech_modules["replica_bitcell_array"] = ["sky130_replica_bitcell_array", "replica_bitcell_array"] +tech_modules["capped_replica_bitcell_array"] = ["sky130_capped_replica_bitcell_array", "capped_replica_bitcell_array"] +tech_modules["dummy_array"] = ["sky130_dummy_array", "dummy_array"] + +tech_modules["replica_column"] = ["sky130_replica_column", "replica_column"] + +tech_modules["col_cap_array"] = ["sky130_col_cap_array", "col_cap_array"] +tech_modules["col_cap"] = ["sky130_col_cap", "col_cap_bitcell_2port"] +tech_modules["corner"] = ["sky130_corner", None] +tech_modules["internal"] = ["sky130_internal", None] +tech_modules["row_cap_array"] = ["sky130_row_cap_array", "row_cap_array"] +tech_modules["row_cap"] = ["sky130_row_cap", "row_cap_bitcell_2port"] + +# These modules are auto-generated from the nand decoders above and are not +# found in this. +tech_modules["buf_dec"] = "pbuf_dec" +tech_modules["inv_dec"] = "pinv_dec" +tech_modules["and2_dec"] = "and2_dec" +tech_modules["and3_dec"] = "and3_dec" +tech_modules["and4_dec"] = "and4_dec" + +################################################### +# Custom cell properties +################################################### +cell_properties = d.cell_properties() + +cell_properties.bitcell_power_pin_directions = ("H", "H") + +cell_properties.bitcell_1port.mirror.x = True +cell_properties.bitcell_1port.mirror.y = True +cell_properties.bitcell_1port.end_caps = True +cell_properties.bitcell_1port.has_corners = True +cell_properties.bitcell_1port.boundary_layer = "mem" +cell_properties.bitcell_1port.port_order = ['bl', 'br', 'gnd', 'vdd', 'vpb', 'vnb', 'wl'] +cell_properties.bitcell_1port.port_types = ["OUTPUT", "OUTPUT", "GROUND", "POWER", "BIAS", "BIAS", "INPUT"] +cell_properties.bitcell_1port.port_map = {'bl': 'BL', + 'br': 'BR', + 'wl': 'WL', + 'vdd': 'VPWR', + 'vnb': 'VNB', + 'vpb': 'VPB', + 'gnd': 'VGND'} + +cell_properties.bitcell_1port.wl_layer = "m2" +cell_properties.bitcell_1port.bl_layer = "m1" +cell_properties.bitcell_1port.vdd_layer = "m1" +cell_properties.bitcell_1port.vdd_dir = "V" +cell_properties.bitcell_1port.gnd_layer = "m2" +cell_properties.bitcell_1port.gnd_dir = "H" + +cell_properties.bitcell_2port.mirror.x = True +cell_properties.bitcell_2port.mirror.y = True +cell_properties.bitcell_2port.end_caps = True +cell_properties.bitcell_2port.has_corners = False +cell_properties.bitcell_2port.port_order = ['bl0', 'br0', 'bl1', 'br1', 'wl0', 'wl1', 'vdd', 'gnd'] +cell_properties.bitcell_2port.port_map = {'bl0': 'BL0', + 'br0': 'BR0', + 'bl1': 'BL1', + 'br1': 'BR1', + 'wl0': 'WL0', + 'wl1': 'WL1', + 'vdd': 'VDD', + 'gnd': 'GND'} +cell_properties.bitcell_1port.wl_layer = "m2" +cell_properties.bitcell_1port.vdd_layer = "m2" +cell_properties.bitcell_1port.vdd_dir = "H" +cell_properties.bitcell_1port.gnd_layer = "m2" +cell_properties.bitcell_1port.gnd_dir = "H" +cell_properties.bitcell_2port.wl_layer = "m2" +cell_properties.bitcell_2port.vdd_layer = "m1" +cell_properties.bitcell_2port.vdd_dir = "H" +cell_properties.bitcell_2port.gnd_layer = "m2" +cell_properties.bitcell_2port.gnd_dir = "H" + +cell_properties.col_cap_1port_bitcell = d.cell(['bl', 'br', 'vdd', 'gnd', 'vpb', 'vnb', 'gate'], + ['INPUT', 'INPUT','POWER', 'GROUND', 'BIAS', 'BIAS', 'INPUT'], + {'bl': 'bl', + 'br': 'br', + 'vdd': 'vdd', + 'gnd': 'gnd', + 'vnb': 'vnb', + 'vpb': 'vpb', + 'gate': 'gate'}) +cell_properties.col_cap_1port_bitcell.boundary_layer = "mem" + +cell_properties.col_cap_1port_strap_power = d.cell(['vdd', 'vpb', 'vnb'], + ['POWER', 'BIAS', 'BIAS'], + {'vnb': 'VNB', + 'vpb': 'VPB', + 'vdd': 'VPWR'}) +cell_properties.col_cap_1port_strap_power.boundary_layer = "mem" + +cell_properties.col_cap_1port_strap_ground = d.cell(['gnd', 'vpb', 'vnb'], + ['GROUND', 'BIAS', 'BIAS'], + {'vnb': 'VNB', + 'vpb': 'VPB', + 'gnd': 'VGND'}) +cell_properties.col_cap_1port_strap_ground.boundary_layer = "mem" + +cell_properties.row_cap_1port_cell = d.cell(['vdd', 'wl'], + ['POWER', 'INPUT'], + {'wl': 'WL', + 'vdd': 'VPWR'}) +cell_properties.row_cap_1port_cell.boundary_layer = "mem" + +cell_properties.col_cap_2port.port_order = ['bl0', 'br0', 'bl1', 'br1', 'vdd'] +cell_properties.col_cap_2port.port_map = {'bl0': 'BL0', + 'br0': 'BR0', + 'bl1': 'BL1', + 'br1': 'BR1', + 'vdd': 'VDD'} + +cell_properties.row_cap_2port.port_order = ['wl0', 'wl1', 'gnd'] +cell_properties.row_cap_2port.port_map = {'wl0': 'WL0', + 'wl1': 'WL1', + 'gnd': 'GND'} + + +cell_properties.ptx.bin_spice_models = True +cell_properties.ptx.model_is_subckt = True + +cell_properties.pgate.add_implants = True + +cell_properties.use_strap = True +cell_properties.strap_module = "internal" +cell_properties.strap_version = "wlstrap" + +cell_properties.dff.port_order = ['D', 'Q', 'clk', 'vdd', 'gnd'] +cell_properties.dff.port_map = {'D': 'D', + 'Q': 'Q', + 'clk': 'CLK', + 'vdd': 'VDD', + 'gnd': 'GND'} + +cell_properties.nand2_dec.port_order = ['A', 'B', 'Z', 'vdd', 'gnd'] +cell_properties.nand2_dec.port_map = {'A': 'A', + 'B': 'B', + 'Z': 'Z', + 'vdd': 'VDD', + 'gnd': 'GND'} + +cell_properties.nand3_dec.port_order = ['A', 'B', 'C', 'Z', 'vdd', 'gnd'] +cell_properties.nand3_dec.port_map = {'A': 'A', + 'B': 'B', + 'C': 'C', + 'Z': 'Z', + 'vdd': 'VDD', + 'gnd': 'GND'} + +cell_properties.nand4_dec.port_order = ['A', 'B', 'C', 'D', 'Z', 'vdd', 'gnd'] +cell_properties.nand4_dec.port_map = {'A': 'A', + 'B': 'B', + 'C': 'C', + 'D': 'D', + 'Z': 'Z', + 'vdd': 'VDD', + 'gnd': 'GND'} + +cell_properties.sense_amp.port_order = ['bl', 'br', 'dout', 'en', 'vdd', 'gnd'] +cell_properties.sense_amp.port_map = {'bl': 'BL', + 'br': 'BR', + 'dout': 'DOUT', + 'en': 'EN', + 'vdd': 'VDD', + 'gnd': 'GND'} + +cell_properties.write_driver.port_order = ['din', 'bl', 'br', 'en', 'vdd', 'gnd'] +cell_properties.write_driver.port_map = {'din': 'DIN', + 'bl': 'BL', + 'br': 'BR', + 'en': 'EN', + 'vdd': 'VDD', + 'gnd': 'GND'} + + +# You can override the GDS for custom cell using the following: +# If it is a list, the first is single port and the second is dual port. +# If it is string, it is used for both single and dual port. +cell_properties.names["dff"] = "sky130_fd_bd_sram__openram_dff" +cell_properties.names["nand2_dec"] = ["sky130_fd_bd_sram__openram_sp_nand2_dec", "sky130_fd_bd_sram__openram_dp_nand2_dec"] +cell_properties.names["nand3_dec"] = ["sky130_fd_bd_sram__openram_sp_nand3_dec", "sky130_fd_bd_sram__openram_dp_nand3_dec"] +cell_properties.names["nand4_dec"] = ["sky130_fd_bd_sram__openram_sp_nand4_dec", "sky130_fd_bd_sram__openram_dp_nand4_dec"] + +cell_properties.names["bitcell_2port"] = "sky130_fd_bd_sram__openram_dp_cell" +cell_properties.names["dummy_bitcell_2port"] = "sky130_fd_bd_sram__openram_dp_cell_dummy" +cell_properties.names["replica_bitcell_2port"] = "sky130_fd_bd_sram__openram_dp_cell_replica" +cell_properties.names["col_cap_bitcell_2port"] = "sky130_fd_bd_sram__openram_dp_cell_cap_col" +cell_properties.names["row_cap_bitcell_2port"] = "sky130_fd_bd_sram__openram_dp_cell_cap_row" +cell_properties.names["sense_amp"] = "sky130_fd_bd_sram__openram_sense_amp" +cell_properties.names["write_driver"] = "sky130_fd_bd_sram__openram_write_driver" + +array_row_multiple = 2 +array_col_multiple = 2 + +################################################### +# Custom layer properties +################################################### +layer_properties = d.layer_properties() +layer_properties.hierarchical_decoder.bus_layer = "m1" +layer_properties.hierarchical_decoder.bus_directions = "nonpref" +layer_properties.hierarchical_decoder.input_layer = "li" +layer_properties.hierarchical_decoder.output_layer = "m2" +layer_properties.hierarchical_decoder.vertical_supply = True + +layer_properties.hierarchical_predecode.bus_layer = "m1" +layer_properties.hierarchical_predecode.bus_directions = "nonpref" +# This is added to allow the column decoder connections on m2 +layer_properties.hierarchical_predecode.bus_pitch_factor = 1.2 +layer_properties.hierarchical_predecode.bus_space_factor = 1.5 +layer_properties.hierarchical_predecode.input_layer = "li" +layer_properties.hierarchical_predecode.output_layer = "m2" +layer_properties.hierarchical_predecode.vertical_supply = True +layer_properties.hierarchical_predecode.force_horizontal_input_contact = True + +layer_properties.bank.stack = "m2_stack" +layer_properties.bank.pitch = "m3_pitch" + +layer_properties.column_mux_array.select_layer = "m3" +layer_properties.column_mux_array.bitline_layer = "m1" + +layer_properties.port_address.supply_offset = True + +layer_properties.port_data.enable_layer = "m1" +layer_properties.port_data.channel_route_bitlines = False + +layer_properties.replica_column.even_rows = True + +layer_properties.wordline_driver.vertical_supply = True + +layer_properties.global_wordline_layer = "m5" + + +################################################### +# Discrete tx bins +################################################### +# enforce that tx sizes are within 25% of requested size after fingering. +accuracy_requirement = 0.75 +nmos_bins = { + 0.15 : [0.36, 0.39, 0.42, 0.52, 0.54, 0.55, 0.58, 0.6, 0.61, 0.64, 0.65, 0.74, 0.84, 1.0, 1.26, 1.68, 2.0, 3.0, 5.0, 7.0], + 0.18 : [0.42, 0.65, 1.0, 3.0, 5.0, 7.0], + 0.25 : [0.65, 1.0, 3.0, 5.0, 7.0], + 0.5 : [0.42, 0.55, 0.65, 1.0, 3.0, 5.0, 7.0], + 1.0 : [0.42, 0.65, 1.0, 3.0, 5.0, 7.0], + 2.0 : [0.42, 0.65, 1.0, 3.0, 5.0, 7.0], + 4.0 : [0.42, 0.65, 1.0, 3.0, 5.0, 7.0], + 8.0 : [0.42, 0.65, 1.0, 3.0, 5.0, 7.0], + 20.0 : [0.42, 0.65, 1.0, 3.0, 5.0, 7.0] +} + +pmos_bins = { + 0.15 : [0.42, 0.55, 0.64, 0.84, 1.0, 1.12, 1.26, 1.65, 1.68, 2.0, 3.0, 5.0, 7.0], + 1.0 : [0.42, 0.55, 1.0, 3.0, 5.0, 7.0], + 2.0 : [0.42, 0.55, 1.0, 3.0, 5.0, 7.0], + 4.0 : [0.42, 0.55, 1.0, 3.0, 5.0, 7.0], + 8.0 : [0.42, 0.55, 1.0, 3.0, 5.0, 7.0], + 0.17 : [0.42, 0.55, 0.64, 0.84, 1.0, 1.12], + 0.18 : [0.42, 0.55, 0.64, 0.84, 1.0, 1.12, 1.26, 1.68, 2.0, 3.0, 5.0, 7.0], + 0.25 : [1.0, 3.0, 5.0, 7.0], + 0.5 : [0.42, 0.55, 1.0, 3.0, 5.0, 7.0], + 20.0 : [0.42] +} +################################################### +# GDS file info +################################################### +GDS = {} +# gds units +# From http://www.cnf.cornell.edu/cnf_spie9.html: "The first +# is the size of a database unit in user units. The second is the size +# of a database unit in meters. For example, if your library was +# created with the default units (user unit = 1 um and 1000 database +# units per user unit), then the first number would be 0.001 and the +# second number would be 10-9. Typically, the first number is less than +# 1, since you use more than 1 database unit per user unit. To +# calculate the size of a user unit in meters, divide the second number +# by the first." +GDS["unit"] = (0.001, 1e-9) +#GDS["unit"]=(0.001, 1e-6) + +################################################### +# Interconnect stacks +################################################### + +poly_stack = ("poly", "contact", "li") +active_stack = ("active", "contact", "li") +li_stack = ("li", "mcon", "m1") +m1_stack = ("m1", "via1", "m2") +m2_stack = ("m2", "via2", "m3") +m3_stack = ("m3", "via3", "m4") +m4_stack = ("m4", "via4", "m5") + +lef_rom_interconnect = ["m1", "m2", "m3", "m4"] + +layer_indices = {"poly": 0, + "active": 0, + "nwell": 0, + "li": 1, + "m1": 2, + "m2": 3, + "m3": 4, + "m4": 5, + "m5": 6} + +# The FEOL stacks get us up to m1 +feol_stacks = [poly_stack, + active_stack, + li_stack] + +# The BEOL stacks are m1 and up +beol_stacks = [m1_stack, + m2_stack, + m3_stack, + m4_stack] + +layer_stacks = feol_stacks + beol_stacks + +preferred_directions = {"poly": "V", + "active": "V", + "li": "V", + "m1": "H", + "m2": "V", + "m3": "H", + "m4": "V", + "m5": "H"} + +################################################### +# GDS Layer Map +################################################### + + +layer = {} +layer["active"] = (65, 20) # diff +layer["activep"] = (65, 20) # diff +layer["tap"] = (65, 44) # tap +layer["pwellp"] = (122,16) +layer["nwell"] = (64, 20) # nwell +layer["dnwell"] = (64,18) +layer["nimplant"]= (93, 44) # nsdm +layer["pimplant"]= (94, 20) # psdm +layer["vtl"] = (125, 44) # lvtn +layer["vth"] = (78, 44) # hvtp (pmos only) +layer["thkox"] = (8, 0) +layer["poly"] = (66, 20) +layer["contact"] = (66, 44) # licon1 +layer["npc"] = (95, 20) # npc (nitride cut) +layer["li"] = (67, 20) # active li1 +layer["mcon"] = (67, 44) # mcon +layer["m1"] = (68, 20) # met1 +layer["m1p"] = (68, 5) # met1 pin +layer["via1"] = (68, 44) # via1 +layer["m2"] = (69, 20) # met2 +layer["m2p"] = (69, 5) # met2 pin +layer["via2"] = (69, 44) # via2 +layer["m3"] = (70, 20) # met3 +layer["m3p"] = (70, 5) # met3 pin +layer["via3"] = (70, 44) # via3 +layer["m4"] = (71, 20) # met4 +layer["m4p"] = (71, 5) # met4 pin +layer["via4"] = (71, 44) # via4 +layer["m5"] = (72, 20) # met5 +layer["m5p"] = (72, 5) # met5 pin +layer["boundary"]= (235, 4) +# specific boundary type to define standard cell regions for DRC +layer["stdc"] = (81, 4) +layer["mem"] = (81, 2) +# Not an official sky130 layer, but useful for router debug infos +layer["text"]= (234, 5) +# Excpected value according to sky130A tech file +# If calibre is enabled, these will be swapped below +#pin_purpose = 5 +label_purpose = 5 +#label_purpose = 16 +#pin_purpose = 16 +#label_purpose = 5 + +# pin_read purposes +special_purposes = {layer["nwell"][0]: [layer["nwell"][1], 5, 59, 16]} +#layer_override = {"VNB\x00": ["pwell",122]} +layer_override = {"vnb": layer["pwellp"], "VNB": layer["pwellp"]} +layer_override_name = {"vnb": "pwellp", "VNB": "pwellp"} +layer_override_purpose = {122: (64, 59)} +# Layer names for external PDKs +layer_names = {} +layer_names["active"] = "diff" +layer_names["activep"] = "diff" +layer_names["tap"] = "tap" +layer_names["pwellp"] = "pwellp" +layer_names["nwell"] = "nwell" +layer_names["dnwell"] = "dnwell" +layer_names["nimplant"]= "nsdm" +layer_names["pimplant"]= "psdm" +layer_names["vtl"] = "lvtn" +layer_names["vth"] = "hvtp" +layer_names["thkox"] = "thkox" +layer_names["poly"] = "poly" +layer_names["contact"] = "licon1" +layer_names["li"] = "li1" +layer_names["mcon"] = "mcon" +layer_names["m1"] = "met1" +layer_names["m1p"] = "met1" +layer_names["via1"] = "via" +layer_names["m2"] = "met2" +layer_names["m2p"] = "met2" +layer_names["via2"] = "via2" +layer_names["m3"] = "met3" +layer_names["m3p"] = "met3" +layer_names["via3"] = "via3" +layer_names["m4"] = "met4" +layer_names["m4p"] = "met4" +layer_names["via4"] = "via4" +layer_names["m5p"] = "met5" +layer_names["boundary"]= "boundary" +layer_names["stdc"] = "areaid.standardc" +layer_names["mem"] = "areaid.core" +layer_names["text"] = "text" + + +################################################### +# DRC/LVS Rules Setup +################################################### + +# technology parameter +parameter={} +# difftap.2b +parameter["min_tx_size"] = 0.150 +parameter["beta"] = 3 + +parameter["6T_inv_nmos_size"] = 0.205 +parameter["6T_inv_pmos_size"] = 0.09 +parameter["6T_access_size"] = 0.135 + +drc = d.design_rules("sky130") + +# grid size +drc["grid"] = 0.005 + +#DRC/LVS test set_up +# Switching between calibre and magic can be useful for development, +# it eventually should be deleted. +NDA_PDK_ROOT = os.environ.get("NDA_PDK_ROOT", False) +use_calibre = bool(NDA_PDK_ROOT) +use_calibre = False +use_klayout = False +if use_calibre: + # Correct order according to s8 + pin_purpose = 16 + label_purpose = 5 + + drc["drc_rules"] = NDA_PDK_ROOT + "/DRC/Calibre/s8_drcRules" + drc["lvs_rules"] = NDA_PDK_ROOT + "/LVS/Calibre/lvs_s8_opts" + drc["xrc_rules"] = NDA_PDK_ROOT + "/PEX/xRC/extLvsRules_s8_5lm" + drc["layer_map"] = NDA_PDK_ROOT + "/VirtuosoOA/libs/technology_library/s8phirs_10r.layermap" + +# minwidth_tx with contact (no dog bone transistors) +# difftap.2b +drc["minwidth_tx"] = 0.360 +drc["minlength_channel"] = 0.150 + +drc["pwell_to_nwell"] = 0 +# nwell.1 Minimum width of nwell/pwell +drc.add_layer("nwell", + width=0.840, + spacing=1.270) + +# poly.1a Minimum width of poly +# poly.2 Minimum spacing of poly AND active +drc.add_layer("poly", + width=0.150, + spacing=0.210) +# poly.8 +drc["poly_extend_active"] = 0.13 +# Not a rule +drc["poly_to_contact"] = 0 +# poly.7 Minimum enclosure of active around gate +drc["active_enclose_gate"] = 0.075 +# poly.4 Minimum spacing of field poly to active +drc["poly_to_active"] = 0.075 +# poly.2 Minimum spacing of field poly +drc["poly_to_field_poly"] = 0.210 + +# difftap.1 Minimum width of active +# difftap.3 Minimum spacing of active +drc.add_layer("active", + width=0.150, + spacing=0.270) +# difftap.8 +drc.add_enclosure("nwell", + layer="active", + enclosure=0.18, + extension=0.18) + +# nsd/psd.5a +drc.add_enclosure("implant", + layer="active", + enclosure=0.125) + +# Same as active enclosure? +drc["implant_to_contact"] = 0.070 +# nsd/psd.1 nsd/psd.2 +drc.add_layer("implant", + width=0.380, + spacing=0.380, + area=0.265) + +# licon.1, licon.2 +drc.add_layer("contact", + width=0.170, + spacing=0.170) +# licon.5c (0.06 extension), (licon.7 for extension) +drc.add_enclosure("active", + layer="contact", + enclosure=0.040, + extension=0.060) +# licon.7 +drc["tap_extend_contact"] = 0.120 + +# licon.8 Minimum enclosure of poly around contact +drc.add_enclosure("poly", + layer="contact", + enclosure=0.08, + extension=0.08) +# licon.11a +drc["active_contact_to_gate"] = 0.050 +# npc.4 > licon.14 0.19 > licon.11a +drc["poly_contact_to_gate"] = 0.270 +# licon.15 +drc["npc_enclose_poly"] = 0.1 + +# li.1, li.3 +drc.add_layer("li", + width=0.170, + spacing=0.170) + +# licon.5 +drc.add_enclosure("li", + layer="contact", + enclosure=0, + extension=0.080) + +drc.add_enclosure("li", + layer="mcon", + enclosure=0, + extension=0.080) +# mcon.1, mcon.2 +drc.add_layer("mcon", + width=0.170, + spacing=0.210) + +# m1.1 Minimum width of metal1 +# m1.2 Minimum spacing of metal1 +# m1.6 Minimum area of metal1 +drc.add_layer("m1", + width=0.140, + spacing=0.140, + area=0.083) +# m1.4 Minimum enclosure of metal1 +# m1.5 Minimum enclosure around contact on two opposite sides +drc.add_enclosure("m1", + layer="mcon", + enclosure=0.030, + extension=0.060) +# via.4a Minimum enclosure around via1 +# via.5a Minimum enclosure around via1 on two opposite sides +drc.add_enclosure("m1", + layer="via1", + enclosure=0.055, + extension=0.085) + +# via.1a Minimum width of via1 +# via.2 Minimum spacing of via1 +drc.add_layer("via1", + width=0.150, + spacing=0.170) + +# m2.1 Minimum width of intermediate metal +# m2.2 Minimum spacing of intermediate metal +# m2.6 Minimum area of metal2 +drc.add_layer("m2", + width=0.140, + spacing=0.140, + area=0.0676) +# m2.4 Minimum enclosure around via1 +# m2.5 Minimum enclosure around via1 on two opposite sides +drc.add_enclosure("m2", + layer="via1", + enclosure=0.055, + extension=0.085) +# via2.4 Minimum enclosure around via2 +# via2.5 Minimum enclosure around via2 on two opposite sides +drc.add_enclosure("m2", + layer="via2", + enclosure=0.040, + extension=0.085) + +# via2.1a Minimum width of Via2 +# via2.2 Minimum spacing of Via2 +drc.add_layer("via2", + width=0.200, + spacing=0.200) + +# m3.1 Minimum width of metal3 +# m3.2 Minimum spacing of metal3 +# m3.6 Minimum area of metal3 +drc.add_layer("m3", + width=0.300, + spacing=0.300, + area=0.240) +# m3.4 Minimum enclosure around via2 +drc.add_enclosure("m3", + layer="via2", + enclosure=0.065) +# via3.4 Minimum enclosure around via3 +# via3.5 Minimum enclosure around via3 on two opposite sides +drc.add_enclosure("m3", + layer="via3", + enclosure=0.060, + extension=0.090) + +# via3.1 Minimum width of Via3 +# via3.2 Minimum spacing of Via3 +drc.add_layer("via3", + width=0.200, + spacing=0.200) + +# m4.1 Minimum width of metal4 +# m4.2 Minimum spacing of metal4 +# m4.7 Minimum area of metal4 +drc.add_layer("m4", + width=0.300, + spacing=0.300, + area=0.240) +# m4.3 Minimum enclosure around via3 +drc.add_enclosure("m4", + layer="via3", + enclosure=0.065) +# FIXME: Wrong rule m4.3 Minimum enclosure around via3 +drc.add_enclosure("m4", + layer="via4", + enclosure=0.060) + + +# via4.1 Minimum width of Via4 +# via4.2 Minimum spacing of Via4 +drc.add_layer("via4", + width=0.800, + spacing=0.800) + +# FIXME: Wrong rules +# m5.1 Minimum width of metal5 +# m5.2 Minimum spacing of metal5 +# m5.7 Minimum area of metal5 +drc.add_layer("m5", + width=1.600, + spacing=1.600, + area=4.000) +# m5.3 Minimum enclosure around via4 +drc.add_enclosure("m5", + layer="via4", + enclosure=0.310) + + + +# Metal 5-10 are ommitted + +################################################### +# Spice Simulation Parameters +################################################### + +# spice info +spice = {} +spice["nmos"] = "sky130_fd_pr__nfet_01v8" +spice["pmos"] = "sky130_fd_pr__pfet_01v8" +spice["power"]="vccd1" +spice["ground"]="vssd1" + +# whether or not the device model is actually a subckt +spice["device_prefix"] = "X" + +spice["fet_libraries"] = { "TT": [[os.environ.get("SPICE_MODEL_DIR") + "/sky130.lib.spice", "tt"]], + "SS": [[os.environ.get("SPICE_MODEL_DIR") + "/sky130.lib.spice", "ss"]], + "FF": [[os.environ.get("SPICE_MODEL_DIR") + "/sky130.lib.spice", "ff"]], + "SF": [[os.environ.get("SPICE_MODEL_DIR") + "/sky130.lib.spice", "sf"]], + "FS": [[os.environ.get("SPICE_MODEL_DIR") + "/sky130.lib.spice", "fs"]] } + +# spice stimulus related variables +spice["feasible_period"] = 10 # estimated feasible period in ns +spice["supply_voltages"] = [1.7, 1.8, 1.9] # Supply voltage corners in [Volts] +spice["nom_supply_voltage"] = 1.8 # Nominal supply voltage in [Volts] +spice["rise_time"] = 0.005 # rise time in [Nano-seconds] +spice["fall_time"] = 0.005 # fall time in [Nano-seconds] +spice["temperatures"] = [0, 25, 100] # Temperature corners (celcius) +spice["nom_temperature"] = 25 # Nominal temperature (celcius) + +# analytical delay parameters +spice["nom_threshold"] = 0.49 # Typical Threshold voltage in Volts +spice["wire_unit_r"] = 0.125 # Unit wire resistance in ohms/square +spice["wire_unit_c"] = 0.134 # Unit wire capacitance ff/um^2 +spice["min_tx_drain_c"] = 0.7 # Minimum transistor drain capacitance in ff +spice["min_tx_gate_c"] = 0.2 # Minimum transistor gate capacitance in ff +spice["dff_setup"] = 102.5391 # DFF setup time in ps +spice["dff_hold"] = -56 # DFF hold time in ps +spice["dff_in_cap"] = 6.89 # Input capacitance (D) [Femto-farad] +spice["dff_out_cap"] = 6.89 # Output capacitance (Q) [Femto-farad] + +# analytical power parameters, many values are temporary +spice["bitcell_leakage"] = 1 # Leakage power of a single bitcell in nW +spice["inv_leakage"] = 1 # Leakage power of inverter in nW +spice["nand2_leakage"] = 1 # Leakage power of 2-input nand in nW +spice["nand3_leakage"] = 1 # Leakage power of 3-input nand in nW +spice["nand4_leakage"] = 1 # Leakage power of 4-input nand in nW +spice["nor2_leakage"] = 1 # Leakage power of 2-input nor in nW +spice["dff_leakage"] = 1 # Leakage power of flop in nW + +spice["default_event_frequency"] = 100 # Default event activity of every gate. MHz + +# Parameters related to sense amp enable timing and delay chain/RBL sizing +parameter["le_tau"] = 2.25 # In pico-seconds. +parameter["cap_relative_per_ff"] = 7.5 # Units of Relative Capacitance/ Femto-Farad +parameter["dff_clk_cin"] = 30.6 # relative capacitance +parameter["6tcell_wl_cin"] = 3 # relative capacitance +parameter["min_inv_para_delay"] = 2.4 # Tau delay units +parameter["sa_en_pmos_size"] = 0.72 # micro-meters +parameter["sa_en_nmos_size"] = 0.27 # micro-meters +parameter["sa_inv_pmos_size"] = 0.54 # micro-meters +parameter["sa_inv_nmos_size"] = 0.27 # micro-meters +parameter["bitcell_drain_cap"] = 0.1 # In Femto-Farad, approximation of drain capacitance + +################################################### +# Technology Tool Preferences +################################################### + +if use_calibre: + drc_name = "calibre" + lvs_name = "calibre" + pex_name = "calibre" +elif use_klayout: + drc_name = "klayout" + lvs_name = "klayout" + pex_name = "klayout" +else: + drc_name = "magic" + lvs_name = "netgen" + pex_name = "magic" + + +# This is used by uniqify to not rename the library cells +library_prefix_name = "sky130_fd_bd_sram__" +# List of cells to skip running DRC/LVS on directly +# This will look for a maglef file and copy it over the mag file +# before DRC after extraction + +# gds flatglob sky130_fd_bd_sram__openram_sp_cell_opt1a_cell +# gds flatglob sky130_fd_bd_sram__openram_sp_cell_opt1a_replica_ce +# gds flatglob sky130_fd_bd_sram__openram_sp_cell_opt1_replica_cell +# gds flatglob sky130_fd_bd_sram__openram_sp_cell_opt1_replica_ce +# gds flatglob sky130_fd_bd_sram__openram_sp_cell_opt1_replica_cell +# gds flatglob sky130_fd_bd_sram__openram_sp_cell_opt1a_cell +# gds flatglob sky130_fd_bd_sram__sram_sp_cell_fom_serifs + +flatglob = ["*_?mos_m*", + "sky130_fd_bd_sram__sram_sp_cell_fom_serifs", + + "sky130_fd_bd_sram__sram_sp_cell", + "sky130_fd_bd_sram__openram_sp_cell_opt1_replica_cell", + "sky130_fd_bd_sram__openram_sp_cell_opt1a_replica_cell", + + "sky130_fd_bd_sram__sram_sp_cell_opt1_ce", + "sky130_fd_bd_sram__openram_sp_cell_opt1_replica_ce", + "sky130_fd_bd_sram__openram_sp_cell_opt1a_replica_ce", + "sky130_fd_bd_sram__sram_sp_wlstrap_ce", + "sky130_fd_bd_sram__sram_sp_wlstrap_p_ce"] + +blackbox_cells = ["sky130_fd_bd_sram__openram_dp_cell", + "sky130_fd_bd_sram__openram_dp_cell_dummy", + "sky130_fd_bd_sram__openram_dp_cell_replica", + + "sky130_fd_bd_sram__openram_sp_cell_opt1_noblcon", + "sky130_fd_bd_sram__openram_sp_cell_opt1a_noblcon", + "sky130_fd_bd_sram__openram_sp_colend_replica", + "sky130_fd_bd_sram__openram_sp_colenda_replica", + "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", + "sky130_fd_bd_sram__openram_sp_cell_opt1a_replica", + "sky130_fd_bd_sram__sram_sp_colend", + "sky130_fd_bd_sram__sram_sp_colend_cent", + "sky130_fd_bd_sram__sram_sp_colend_p_cent", + "sky130_fd_bd_sram__sram_sp_colenda", + "sky130_fd_bd_sram__sram_sp_colenda_cent", + "sky130_fd_bd_sram__sram_sp_colenda_p_cent", + "sky130_fd_bd_sram__sram_sp_rowend", + "sky130_fd_bd_sram__sram_sp_rowenda", + "sky130_fd_bd_sram__openram_sp_rowend_replica", + "sky130_fd_bd_sram__openram_sp_rowenda_replica", + "sky130_fd_bd_sram__sram_sp_corner", + "sky130_fd_bd_sram__sram_sp_cornera", + "sky130_fd_bd_sram__sram_sp_cornerb", + "sky130_fd_bd_sram__sram_sp_wlstrapa", + "sky130_fd_bd_sram__sram_sp_wlstrap_ce", + "sky130_fd_bd_sram__sram_sp_wlstrap", + "sky130_fd_bd_sram__sram_sp_wlstrap_p_ce", + "sky130_fd_bd_sram__sram_sp_wlstrap_p", + "sky130_fd_bd_sram__sram_sp_wlstrapa_p"] \ No newline at end of file