diff --git a/dependencies.mk b/dependencies.mk index a78b26f31..aea082167 100644 --- a/dependencies.mk +++ b/dependencies.mk @@ -18,7 +18,7 @@ update: # # Target for cleaning up generated files # clean: -# # Command to clean up generated files goes here, but +# # Command to clean up generated files goes here, but # # currently this makefile does not generate new files in the first place # # Target for cleaning up and reinstalling dependencies diff --git a/dependencies.sh b/dependencies.sh index 7f4c72bed..bd8178087 100755 --- a/dependencies.sh +++ b/dependencies.sh @@ -1,13 +1,13 @@ -#!/bin/bash +#!/bin/bash -printf "Function: \nIf this script runs smoothly, all necessary dependencies for OpenFASoC will be -downloaded at once. If you've already downloaded all dependencies with this script, +printf "Function: \nIf this script runs smoothly, all necessary dependencies for OpenFASoC will be +downloaded at once. If you've already downloaded all dependencies with this script, you can run this script again to update the installed dependencies.\n -Basic Requirements (not exhaustive): +Basic Requirements (not exhaustive): (1) Python 3.6 or higher is required. (2) Intel x86 architecture is required, as this script will use Conda to download several Python packages for which versions compatible with ARM architecture currently do not -exist for installation in Conda's package repository. If your machine does not run +exist for installation in Conda's package repository. If your machine does not run on Intel x86 architecture, this script will likely not work. (3) CentOS and Ubuntu are the only operating systems this script has been verified to work on. We cannot guarantee successful compilation on other systems.\n\n" @@ -16,12 +16,12 @@ proceed_confirmed=false update_confirmed=false while ! $proceed_confirmed do - echo "[OpenFASoC] Do you wish to proceed with the installation? + echo "[OpenFASoC] Do you wish to proceed with the installation? [y] Yes. Install for the first time. [u] Yes. Update already-installed dependencies. -[n] No. Exit this script." +[n] No. Exit this script." read -p "Select the desired option: " selection - if [ "$selection" == "y" ] || [ "$selection" == "Y" ]; then + if [ "$selection" == "y" ] || [ "$selection" == "Y" ]; then echo "Beginning installation..."; proceed_confirmed=true elif [ "$selection" == "n" ] || [ "$selection" == "N" ]; then echo "Quitting script."; exit @@ -45,10 +45,10 @@ if $update_confirmed; then if [ $? == 0 ] then printf "\n\n[OpenFASoC] Conda updated successfully with: conda update conda -y." - else + else printf "\n\n[OpenFASoC] Failed to update Conda using: conda update conda -y." printf "[OpenFASoC] Attempting instead to update Conda using: install -c anaconda conda -y" - conda install -c anaconda conda -y; if [ $? == 0 ]; then + conda install -c anaconda conda -y; if [ $? == 0 ]; then printf "\n\n[OpenFASoC] Conda updated successfully with: install -c anaconda conda -y" else printf "\n\n[OpenFASoC] Conda could not be updated."; fi @@ -57,9 +57,9 @@ if $update_confirmed; then update_successful=true printf "\n\n[OpenFASoC] Attempting to update packages using: conda update --all -y \n" conda update --all -y - if [ $? == 0 ]; then + if [ $? == 0 ]; then printf "[OpenFASoC] Packages updated successfully with: conda update --all -y" - else + else printf "\n\n[OpenFASoC] Failed to update packages using: conda update --all -y." printf "Attempting instead to install core packages individually..." conda install -c litex-hub magic -y; if [ $? != 0 ]; then update_successful=false; echo "magic could not be updated"; fi @@ -73,15 +73,15 @@ if $update_confirmed; then # echo "Updating ngspice..." # cd ngspice # git pull - # ./compile_linux.sh + # ./compile_linux.sh # if [ $? == 0 ]; then # ngspice_updated=true # echo "ngspice updated successfully." - # else + # else # echo "nspice could not be updated." # fi # cd .. - + # cd ./docker/conda/scripts/Xyce # echo "Updating xyce..." # SRCDIR=$PWD/Trilinos-trilinos-release-12-12-1 @@ -101,7 +101,7 @@ if $update_confirmed; then # make install # if [ $? == 0 ]; then # echo "xyce updated successfully." - # else + # else # echo "xyce could not be updated." # fi @@ -197,7 +197,7 @@ if which pip3 >> /dev/null then echo "[OpenFASoC] Pip3 exists" pip3 install -r requirements.txt - if [ $? == 0 ]; then + if [ $? == 0 ]; then echo "[OpenFASoC] Python packages installed successfully." else echo "[OpenFASoC] Python packages could not be installed." diff --git a/docker/conda/scripts/xyce_install_centos.sh b/docker/conda/scripts/xyce_install_centos.sh index 3fb57b9e4..4f6df7e09 100755 --- a/docker/conda/scripts/xyce_install_centos.sh +++ b/docker/conda/scripts/xyce_install_centos.sh @@ -13,7 +13,7 @@ #Install Dependancies ########################################################################### -yum install -y wget git make gcc-c++ python3 +yum install -y wget git make gcc-c++ python3 yum install -y gcc-gfortran bison flex libtool-ltdl-devel yum install -y fftw-devel suitesparse-devel blas-devel lapack-devel yum install -y openmpi-devel openmpi @@ -42,7 +42,7 @@ source /opt/rh/devtoolset-8/enable #Install Trilinos from source ########################################################################### -wget https://github.com/trilinos/Trilinos/archive/trilinos-release-12-12-1.tar.gz +wget https://github.com/trilinos/Trilinos/archive/trilinos-release-12-12-1.tar.gz tar zxvf trilinos-release-12-12-1.tar.gz SRCDIR=$PWD/Trilinos-trilinos-release-12-12-1 @@ -107,4 +107,4 @@ mkdir -p $INSTALLDIR make install #Test installation -$INSTALLDIR/bin/Xyce \ No newline at end of file +$INSTALLDIR/bin/Xyce diff --git a/openfasoc/generators/gdsfactory-gen/PDK/gf180_mapped/__init__.py b/openfasoc/generators/gdsfactory-gen/PDK/gf180_mapped/__init__.py deleted file mode 100644 index fc6ab5b7a..000000000 --- a/openfasoc/generators/gdsfactory-gen/PDK/gf180_mapped/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -""" -Usage at the package level: from PDK.gf180_mapped import gf180_mapped_pdk -""" - -from PDK.gf180_mapped.gf180_mapped import gf180_mapped_pdk diff --git a/openfasoc/generators/gdsfactory-gen/PDK/gf180_mapped/gf180_mapped.py b/openfasoc/generators/gdsfactory-gen/PDK/gf180_mapped/gf180_mapped.py deleted file mode 100644 index 09aaa065f..000000000 --- a/openfasoc/generators/gdsfactory-gen/PDK/gf180_mapped/gf180_mapped.py +++ /dev/null @@ -1,40 +0,0 @@ -# TODO: add all the cells from gf180 and a rule deck -# TODO: note that gf180 pip is not up to date with github repo (no layer views) -""" -usage: from gf180_mapped import gf180_mapped_pdk -""" - -from gf180.layers import LAYER # , LAYER_VIEWS - -# import mappedpdk from the main pdk dir (parent of this dir) -import sys -from pathlib import Path - -sys.path.append(Path(__file__).resolve().parent.parent) -from mappedpdk import MappedPDK - - -gf180_glayer_mapping = { - "met4": "metal4", - "via3": "via3", - "met3": "metal3", - "via2": "via2", - "met2": "metal2", - "via1": "via1", - "met1": "metal1", - "mcon": "contact", - "poly": "poly2", - "active": "comp", - "n+s/d": "nplus", - "p+s/d": "pplus", - "nwell": "nwell", - "pwell": "lvpwell", - "dnwell": "dnwell", -} - - -gf180_mapped_pdk = MappedPDK( - name="gf180", - glayers=gf180_glayer_mapping, - layers=LAYER.dict() -) diff --git a/openfasoc/generators/gdsfactory-gen/PDK/mappedpdk.py b/openfasoc/generators/gdsfactory-gen/PDK/mappedpdk.py deleted file mode 100644 index 7b6e84eac..000000000 --- a/openfasoc/generators/gdsfactory-gen/PDK/mappedpdk.py +++ /dev/null @@ -1,163 +0,0 @@ -""" -usage: from mappedpdk import MappedPDK -""" - -import gdsfactory as gf -from pydantic import validator, StrictStr, ValidationError -from typing import ClassVar, Optional -from pathlib import Path -import tempfile -import subprocess - - -class MappedPDK(gf.pdk.Pdk): - """Inherits everything from the PDK class but also requires mapping to glayers - glayers are generic layers which can be returned with get_glayer(name: str) - validate_glayers(list[str]) is used to verify all required generic layers are - present""" - - valid_glayers: ClassVar[list[str]] = [ - "dnwell", - "pwell", - "nwell", - "p+s/d", - "n+s/d", - "active", - "poly", - "mcon", - "met1", - "via1", - "met2", - "via2", - "met3", - "via3", - "met4", - ] - - glayers: dict[StrictStr, StrictStr] - - klayout_lydrc_file_path: Optional[Path] = None - - # force people to pick glayers from a finite set of string layers that you define - # if someone tries to pass a glayers dict that has a bad key, throw an error - @validator("glayers") - def glayers_check_keys(cls, glayers_obj: dict[StrictStr, StrictStr]): - """checks glayers to ensure valid keys,type. Glayers must be passed as dict[str,str]""" - for glayer, mapped_layer in glayers_obj.items(): - if (not isinstance(glayer, str)) or (not isinstance(mapped_layer, str)): - raise TypeError("glayers should be passed as dict[str, str]") - if glayer not in cls.valid_glayers: - raise ValueError( - "glayers keys must be one of generic layers listed in class variable valid_glayers" - ) - return glayers_obj - - @validator("klayout_lydrc_file_path") - def lydrc_file_exists(cls, lydrc_file_path): - """Check that lydrc_file_path exists if not none""" - if lydrc_file_path != None and not lydrc_file_path.is_file(): - raise ValueError(".lydrc script: the path given is not a file") - - def drc( - self, - layout: gf.typings.Component | gf.typings.PathType, - output_dir_or_file: Optional[gf.typings.PathType] = None, - ): - """Returns true if the layout is DRC clean and false if not - Also saves detailed results to output_dir_or_file location as lyrdb - layout can be passed as a file path or gdsfactory component""" - if not self.klayout_lydrc_file_path: - raise NotImplementedError("no drc script for this PDK") - # find layout gds file path - if isinstance(layout, gf.typings.Component): - tempdir = tempfile.TemporaryDirectory() - layout_path = Path(layout.write_gds(tempdir)).resolve() - elif isinstance(layout, gf.typings.PathType): - layout_path = Path(layout).resolve() - else: - raise TypeError("layout should be a Component, Path, or string") - if not layout_path.is_file(): - raise ValueError("layout must exist, the path given is not a file") - # find report file path, if None the use current directory - report_path = ( - Path(output_dir_or_file).resolve() - if output_dir_or_file - else Path.cwd().resolve() - ) - if report_path.is_dir(): - report_path = Path( - report_path - / str( - self.name - + layout_path.name.replace(layout_path.suffix, "") - + "_drcreport.lyrdb" - ) - ) - elif not report_path.is_file(): - raise ValueError("report_path must be file or dir") - # run klayout drc - drc_args = [ - "klayout", - "-b", - "-r", - str(self.klayout_lydrc_file_path), - "-rd", - "input=" + str(layout_path), - "-rd", - "report=" + str(report_path), - ] - rtr_code = subprocess.Popen(drc_args).wait() - if rtr_code: - raise RuntimeError("error running klayout DRC") - # clean up and return - if tempdir: - tempdir.cleanup() - # there is a drc parsing open-source at: - # https://github.com/google/globalfoundries-pdk-libs-gf180mcu_fd_pr/blob/main/rules/klayout/drc - # eventually I can return more info on the drc run, but for now just void and view the lyrdb in klayout - # return True or False - - # similar to the validate_layers function in gdsfactory default PDK class - def has_required_glayers(self, layers_required: list[str]): - """Raises ValueError if any of the generic layers in layers_required: list[str] - are not mapped to anything in the pdk.glayers dictionary""" - for layer in layers_required: - if layer not in self.glayers: - raise ValueError( - f"{layer!r} not in Pdk.glayers {list(self.glayers.keys())}" - ) - - # TODO: implement LayerSpec type - def get_glayer(self, layer: str) -> gf.typings.Layer: - """Returns the PDK layer from the generic layer name""" - return self.get_layer(self.glayers[layer]) - - @classmethod - def from_gf_pdk(cls, gfpdk: gf.pdk.Pdk, glayers: dict[str, str]): - """Construct a mapped pdk from an existing pdk and a generic layers mapping""" - # input type validation - if not isinstance(gfpdk, gf.pdk.Pdk): - raise TypeError("from_gf_pdk: gfpdk arg only accepts GDSFactory PDK type") - # convert gfpdk to dictionary - parent_dict = gfpdk.dict() - # print(parent_dict) - # remove all none keys to pass pydantic validation - keys_to_remove = list() - for key in parent_dict: - if parent_dict[key] is None: - keys_to_remove.append(key) - for key in keys_to_remove: - parent_dict.pop(key) - # add glayers mapping - parent_dict["glayers"] = glayers - # get mapped value and try to resolve validation issues - try: - rtrval = cls.parse_obj(parent_dict) - except ValidationError as valerr: - errorobj_list = valerr.errors() - for errorobj in errorobj_list: - problem_field = errorobj["loc"][0] - if problem_field in parent_dict: - parent_dict.pop(problem_field) - rtrval = cls.parse_obj(parent_dict) - return rtrval diff --git a/openfasoc/generators/gdsfactory-gen/PDK/sky130_mapped/__init__.py b/openfasoc/generators/gdsfactory-gen/PDK/sky130_mapped/__init__.py deleted file mode 100644 index 7c4f63767..000000000 --- a/openfasoc/generators/gdsfactory-gen/PDK/sky130_mapped/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -""" -Usage at the package level: from PDK.sky130_mapped import sky130_mapped_pdk -""" - -from PDK.sky130_mapped.sky130_mapped import sky130_mapped_pdk diff --git a/openfasoc/generators/gdsfactory-gen/PDK/sky130_mapped/sky130_mapped.py b/openfasoc/generators/gdsfactory-gen/PDK/sky130_mapped/sky130_mapped.py deleted file mode 100644 index 5a5c870d2..000000000 --- a/openfasoc/generators/gdsfactory-gen/PDK/sky130_mapped/sky130_mapped.py +++ /dev/null @@ -1,33 +0,0 @@ -""" -usage: from sky130_mapped import sky130_mapped_pdk -""" -import sky130 - -# import mappedpdk from the main pdk dir (parent of this dir) -import sys -from pathlib import Path - -sys.path.append(str(Path(__file__).resolve().parent.parent)) -from mappedpdk import MappedPDK - - -sky130_glayer_mapping = { - "met4": "met3drawing", - "via3": "via2drawing", - "met3": "met2drawing", - "via2": "viadrawing", - "met2": "met1drawing", - "via1": "mcondrawing", - "met1": "li1drawing", - "mcon": "licon1drawing", - "poly": "polydrawing", - "active": "diffdrawing", - "n+s/d": "nsdmdrawing", - "p+s/d": "psdmdrawing", - "nwell": "nwelldrawing", - "pwell": "pwelldrawing", - "dnwell": "dnwelldrawing", -} - - -sky130_mapped_pdk = MappedPDK.from_gf_pdk(sky130.PDK, sky130_glayer_mapping) diff --git a/openfasoc/generators/gdsfactory-gen/current_mirror.py b/openfasoc/generators/gdsfactory-gen/current_mirror.py deleted file mode 100644 index f84da4d83..000000000 --- a/openfasoc/generators/gdsfactory-gen/current_mirror.py +++ /dev/null @@ -1,317 +0,0 @@ -import gdsfactory as gf -from gdsfactory.generic_tech import get_generic_pdk -from gdsfactory.component import Component, ComponentReference - -from gdsfactory.generic_tech import get_generic_pdk -import sky130 - -gf.config.rich_output() -PDK = get_generic_pdk() -PDK.activate() - -pwell_drawing = (64,13) -dnwell_drawing = (64,18) -nwell_drawing = (64,20) -outline_ref = (236,0) -diff_drawing = (65,20) -psdm_drawing = (94,20) -nsdm_drawing = (93,44) -poly_drawing = (66,20) -licon1_drawing = (66, 44) -npc_drawing = (95, 20) -li1_drawing = (67,20) -mcon_drawing = (67,44) -met1_drawing = (68,20) -met1_label = (68,5) -met1_pin = (68,16) -via_drawing = (68,44) -met2_drawing = (69,20) -met2_label = (69,5) -met2_pin = (69,16) -via2_drawing = (69,44) -met3_drawing = (70,20) -met3_label = (70,5) -met3_pin = (70,16) -text_drawing = (83,44) - -@gf.cell -def nmos() -> Component: - c = Component() - - ##nsdm - nsdm_height = 0.67 - nsdm_width = 1.1 - - nsdm_outline_rect = gf.components.rectangle(size=(nsdm_width,nsdm_height), layer=nsdm_drawing) - nsdm_outline_rect_ref = c << nsdm_outline_rect - - ##poly - poly_width = 0.25 - poly_height = 0.68 - - poly_rect = gf.components.rectangle(size=(poly_width,poly_height), layer=poly_drawing) - poly_rect_ref = c << poly_rect - - poly_rect_ref.movex(0.425).movey(-0.005) - - ##diff - diff_width = 0.85 - diff_height = 0.42 - diff_rect = gf.components.rectangle(size=(diff_width,diff_height), layer=diff_drawing) - diff_rect_ref = c << diff_rect - - diff_rect_ref.movex(0.125).movey(0.125) - - ##li1_drawing - li1_height = 0.5 - li1_width = 0.17 - li1_rect = gf.components.rectangle(size=(li1_width,li1_height), layer=li1_drawing) - - li1_rect_ref1 = c << li1_rect - li1_rect_ref1.movey(0.085).movex(0.19) - li1_rect_ref2 = c << li1_rect - li1_rect_ref2.movey(0.085).movex(0.74) - - ##mcon - mcon_height = 0.17 - mcon_width = 0.17 - - mcon_rect = gf.components.rectangle(size=(mcon_width,mcon_height), layer=mcon_drawing) - mcon_rect_ref1 = c << mcon_rect - mcon_rect_ref1.movey(0.25).movex(0.19) - mcon_rect_ref2 = c << mcon_rect - mcon_rect_ref2.movey(0.25).movex(0.74) - - ##licon1 - licon1_height = 0.17 - licon1_width = 0.17 - - licon1_rect = gf.components.rectangle(size=(licon1_width,licon1_height), layer=licon1_drawing) - licon1_rect_ref1 = c << licon1_rect - licon1_rect_ref1.movey(0.25).movex(0.19) - licon1_rect_ref2 = c << licon1_rect - licon1_rect_ref2.movey(0.25).movex(0.74) - - ##met1 - met1_height = 0.42 - met1_width = 0.23 - - met1_rect = gf.components.rectangle(size=(met1_width,met1_height), layer=met1_drawing) - - met1_rect_ref1 = c << met1_rect - met1_rect_ref1.movey(0.125).movex(0.16) - - met1_rect_ref2 = c << met1_rect - met1_rect_ref2.movey(0.125).movex(0.71) - - ##labels - - met1_label_s = c.add_label("S", position=(0.255,0.36), layer=met1_label, magnification=0.2) - met1_label_d = c.add_label("D", position=(0.81,0.36), layer=met1_label, magnification=0.2) - #c.add_label() - - return c - -@gf.cell -def cmirror_top(mult=3) -> Component: - - Top_cell = gf.Component("top") - - cmirror = nmos() - cell_height = 0.67 - cell_width = 1.1 - space_bet_rows = 0.66 - #mult = 8 - - ##pwell - pwell_width = (cell_width*mult) + 0.11 - pwell_height = (cell_height*2) + space_bet_rows + 0.11 - - pwell_rect = gf.components.rectangle(size=(pwell_width,pwell_height), layer=pwell_drawing) - pwell_rect_ref = Top_cell << pwell_rect - - pwell_rect_ref.movex(-0.055).movey(-0.055) - - ##dnwell - dnwell_width = (cell_width*mult) + 0.91 - dnwell_height = (cell_height*2) + space_bet_rows + 0.91 - - dnwell_rect = gf.components.rectangle(size=(dnwell_width,dnwell_height), layer=dnwell_drawing) - dnwell_rect_ref = Top_cell << dnwell_rect - - dnwell_rect_ref.movex(-0.455).movey(-0.455) - - - for i in range(mult): - for j in range(2): - print(j) - ref = Top_cell << cmirror - ref.movex(cell_width*i).movey(cell_height*(j) + space_bet_rows*(j)) - - for i in range(mult): - for j in range(2): - poly_width = 0.25 - poly_height = 1.85 - - poly_row_conn_rect = gf.components.rectangle(size=(poly_width,poly_height), layer=poly_drawing) - poly_row_conn_rect_ref = Top_cell << poly_row_conn_rect - - poly_row_conn_rect_ref.movex(cell_width*i + 0.425).movey(-0.005) - - via_height = 0.17 - via_width = 0.17 - via_rect = gf.components.rectangle(size=(via_height,via_width), layer=via_drawing) - - via_rect_ref = Top_cell << via_rect - via_rect_ref.movex(cell_width*i + 0.425 + 0.04).movey(0.9) - - - for i in range(mult): - for j in range(2): - met1_height = 1.59 - met1_width = 0.23 - - met1_row_conn_rect = gf.components.rectangle(size=(met1_width,met1_height), layer=met1_drawing) - - met1_row_conn_rect_ref = Top_cell << met1_row_conn_rect - met1_row_conn_rect_ref.movex(cell_width*i + 0.16).movey(0.125) - - via_height = 0.17 - via_width = 0.17 - via_rect = gf.components.rectangle(size=(via_height,via_width), layer=via_drawing) - - via_rect_ref = Top_cell << via_rect - via_rect_ref.movex(cell_width*i + 0.16 + 0.03).movey(0.9) - - met2_pin_width = 0.23 - met2_pin_height = 0.23 - - met2_pin_rect = gf.components.rectangle(size=(met2_pin_width,met2_pin_height), layer=met2_pin) - - ## Center VSS M2 path - met2_center_width = 0.5 + cell_width*mult - met2_center_height = 0.23 - met2_center = gf.components.rectangle(size=(met2_center_width,met2_center_height), layer=met2_drawing) - - met2_center_ref = Top_cell << met2_center - - met2_center_ref.movex(-0.5).movey(0.9 - 0.03) - - met2_pin_VSS = Top_cell << met2_pin_rect - met2_pin_VSS.movex(-0.5).movey(0.9 - 0.03) - - met2_label_I_in = Top_cell.add_label("VSS", position=(-0.5,0.9 - 0.03), layer=met2_label, magnification=0.2) - - #Iin and Iout Metal trunk - met2_tb_width = 0.5 + cell_width*mult + 1.25 - met2_tb_height = 0.23 - - met2_tb = gf.components.rectangle(size=(met2_tb_width,met2_tb_height), layer=met2_drawing) - - ##Top trunk - met2_top_I_in_ref = Top_cell << met2_tb - met2_top_I_in_ref.movex(-0.5).movey(2.5) - - met2_pin_I_in = Top_cell << met2_pin_rect - met2_pin_I_in.movex(-0.5).movey(2.5) - - met2_label_I_in = Top_cell.add_label("I_in", position=(-0.5,2.5), layer=met2_label, magnification=0.2) - - met2_top_I_out_ref = Top_cell << met2_tb - met2_top_I_out_ref.movex(-0.5).movey(2.5 + 0.5) - - met2_pin_I_out = Top_cell << met2_pin_rect - met2_pin_I_out.movex(-0.5).movey(2.5 + 0.5) - - met2_label_I_out = Top_cell.add_label("I_out", position=(-0.5,2.5+0.5), layer=met2_label, magnification=0.2) - - ## Bottom trunk - met2_bottom_I_in_ref = Top_cell << met2_tb - met2_bottom_I_in_ref.movex(-0.5).movey(-0.73) - - met2_bottom_I_out_ref = Top_cell << met2_tb - met2_bottom_I_out_ref.movex(-0.5).movey(-0.73 - 0.5) - - - ## Right end Trunks - met1_right_width = 0.23 - met1_right_height_1 = 4.46 - met1_right_height_2 = 3.46 - - met1_right_trunk_1 = gf.components.rectangle(size=(met1_right_width,met1_right_height_2), layer=met1_drawing) - met1_right_trunk_2 = gf.components.rectangle(size=(met1_right_width,met1_right_height_1), layer=met1_drawing) - - ## Right - I_in trunk - met1_right_trunk_ref_I_in = Top_cell << met1_right_trunk_1 - trunk_1_x_shift = 0.5 + cell_width*mult - met1_right_trunk_ref_I_in.movex(trunk_1_x_shift).movey(-0.73) - - ## Right - I_out trunk - met1_right_trunk_ref_I_out = Top_cell << met1_right_trunk_2 - trunk_2_x_shift = 0.5 + trunk_1_x_shift - met1_right_trunk_ref_I_out.movex(trunk_2_x_shift).movey(-1.23) - - via_height = 0.17 - via_width = 0.17 - via_rect = gf.components.rectangle(size=(via_height,via_width), layer=via_drawing) - - via_rect_ref = Top_cell << via_rect - via_rect_ref.movex(trunk_1_x_shift + 0.03).movey(-0.73 + 0.03) - via_rect_ref = Top_cell << via_rect - via_rect_ref.movex(trunk_1_x_shift + 0.03).movey(met1_right_height_2 - 0.73 -0.17 - 0.03) - - via_rect_ref = Top_cell << via_rect - via_rect_ref.movex(trunk_2_x_shift + 0.03).movey(-1.23 + 0.03) - via_rect_ref = Top_cell << via_rect - via_rect_ref.movex(trunk_2_x_shift + 0.03).movey(met1_right_height_1 - 1.23 -0.17 - 0.03) - - - ##Connecting to trunk - ## i ---> col - ## j ---> row - for i in range(mult): - for j in range(2): - met1_tr_conn_height_1 = 1.275 - met1_tr_conn_height_2 = 1.775 - met1_tr_conn_width = 0.23 - - via_height = 0.17 - via_width = 0.17 - via_rect = gf.components.rectangle(size=(via_height,via_width), layer=via_drawing) - - met1_tr_conn_rect_1 = gf.components.rectangle(size=(met1_tr_conn_width,met1_tr_conn_height_1), layer=met1_drawing) - - met1_tr_conn_rect_2 = gf.components.rectangle(size=(met1_tr_conn_width,met1_tr_conn_height_2), layer=met1_drawing) - - - if(i%2 != 0): - if(j%2 != 0): - met1_tr_conn_rect_ref = Top_cell << met1_tr_conn_rect_2 - met1_tr_conn_rect_ref.movex(cell_width*i + 0.16+0.55).movey(-1.23) - - via_rect_ref = Top_cell << via_rect - via_rect_ref.movex(cell_width*i + 0.16 + 0.55 + 0.03).movey(-1.23 + 0.03) - else: - met1_tr_conn_rect_ref = Top_cell << met1_tr_conn_rect_1 - met1_tr_conn_rect_ref.movex(cell_width*i + 0.16+0.55).movey(1.455) - - via_rect_ref = Top_cell << via_rect - via_rect_ref.movex(cell_width*i + 0.16 + 0.55 + 0.03).movey(1.455 + met1_tr_conn_height_1 - 0.17 - 0.03) - else: - if(j%2 != 0): - met1_tr_conn_rect_ref = Top_cell << met1_tr_conn_rect_1 - met1_tr_conn_rect_ref.movex(cell_width*i + 0.16+0.55).movey(-0.73) - - via_rect_ref = Top_cell << via_rect - via_rect_ref.movex(cell_width*i + 0.16 + 0.55 + 0.03).movey(-0.73 + 0.03) - else: - met1_tr_conn_rect_ref = Top_cell << met1_tr_conn_rect_2 - met1_tr_conn_rect_ref.movex(cell_width*i + 0.16+0.55).movey(1.455) - - via_rect_ref = Top_cell << via_rect - via_rect_ref.movex(cell_width*i + 0.16 + 0.55 + 0.03).movey(1.455 + met1_tr_conn_height_2 - 0.17 - 0.03) - return Top_cell - -Top_cell = cmirror_top(2) -Top_cell.show() - diff --git a/openfasoc/generators/gdsfactory-gen/Makefile b/openfasoc/generators/gdsfactory-gen/deprecated/Makefile similarity index 99% rename from openfasoc/generators/gdsfactory-gen/Makefile rename to openfasoc/generators/gdsfactory-gen/deprecated/Makefile index 614f20331..27c05e4d9 100755 --- a/openfasoc/generators/gdsfactory-gen/Makefile +++ b/openfasoc/generators/gdsfactory-gen/deprecated/Makefile @@ -63,7 +63,7 @@ run_flow: # Via Chain and Line Resistance targets -# +# # met1 = layer 68, width = via_dim + 2 * 0.06, spacing = seg_length = width + 0.14 (met1 spacing) lr_vc__met1: python3 ./scripts/line-res_via-chain/via_chain_gen.py --dimension 40 --spacing 0.43 --width 0.29 --res_sets 40 --via_sets 20 --seg_length 0.43 --seg_width 0.2 --via_dim 0.17 --wire_layer 68 --output_dir=./result_dir/line-res_via-chain diff --git a/openfasoc/generators/gdsfactory-gen/common_source_amp.py b/openfasoc/generators/gdsfactory-gen/deprecated/common_source_amp.py similarity index 100% rename from openfasoc/generators/gdsfactory-gen/common_source_amp.py rename to openfasoc/generators/gdsfactory-gen/deprecated/common_source_amp.py diff --git a/openfasoc/generators/gdsfactory-gen/deprecated/current_mirror.py b/openfasoc/generators/gdsfactory-gen/deprecated/current_mirror.py new file mode 100644 index 000000000..5dc8a01f0 --- /dev/null +++ b/openfasoc/generators/gdsfactory-gen/deprecated/current_mirror.py @@ -0,0 +1,384 @@ +import gdsfactory as gf +from gdsfactory.generic_tech import get_generic_pdk +from gdsfactory.component import Component, ComponentReference + +from gdsfactory.generic_tech import get_generic_pdk +import sky130 + +gf.config.rich_output() +pdk = get_generic_pdk() +pdk.activate() + +pwell_drawing = (64, 13) +dnwell_drawing = (64, 18) +nwell_drawing = (64, 20) +outline_ref = (236, 0) +diff_drawing = (65, 20) +psdm_drawing = (94, 20) +nsdm_drawing = (93, 44) +poly_drawing = (66, 20) +licon1_drawing = (66, 44) +npc_drawing = (95, 20) +li1_drawing = (67, 20) +mcon_drawing = (67, 44) +met1_drawing = (68, 20) +met1_label = (68, 5) +met1_pin = (68, 16) +via_drawing = (68, 44) +met2_drawing = (69, 20) +met2_label = (69, 5) +met2_pin = (69, 16) +via2_drawing = (69, 44) +met3_drawing = (70, 20) +met3_label = (70, 5) +met3_pin = (70, 16) +text_drawing = (83, 44) + + +@gf.cell +def nmos() -> Component: + c = Component() + + ##nsdm + nsdm_height = 0.67 + nsdm_width = 1.1 + + nsdm_outline_rect = gf.components.rectangle( + size=(nsdm_width, nsdm_height), layer=nsdm_drawing + ) + nsdm_outline_rect_ref = c << nsdm_outline_rect + + ##poly + poly_width = 0.25 + poly_height = 0.68 + + poly_rect = gf.components.rectangle( + size=(poly_width, poly_height), layer=poly_drawing + ) + poly_rect_ref = c << poly_rect + + poly_rect_ref.movex(0.425).movey(-0.005) + + ##diff + diff_width = 0.85 + diff_height = 0.42 + diff_rect = gf.components.rectangle( + size=(diff_width, diff_height), layer=diff_drawing + ) + diff_rect_ref = c << diff_rect + + diff_rect_ref.movex(0.125).movey(0.125) + + ##li1_drawing + li1_height = 0.5 + li1_width = 0.17 + li1_rect = gf.components.rectangle(size=(li1_width, li1_height), layer=li1_drawing) + + li1_rect_ref1 = c << li1_rect + li1_rect_ref1.movey(0.085).movex(0.19) + li1_rect_ref2 = c << li1_rect + li1_rect_ref2.movey(0.085).movex(0.74) + + ##mcon + mcon_height = 0.17 + mcon_width = 0.17 + + mcon_rect = gf.components.rectangle( + size=(mcon_width, mcon_height), layer=mcon_drawing + ) + mcon_rect_ref1 = c << mcon_rect + mcon_rect_ref1.movey(0.25).movex(0.19) + mcon_rect_ref2 = c << mcon_rect + mcon_rect_ref2.movey(0.25).movex(0.74) + + ##licon1 + licon1_height = 0.17 + licon1_width = 0.17 + + licon1_rect = gf.components.rectangle( + size=(licon1_width, licon1_height), layer=licon1_drawing + ) + licon1_rect_ref1 = c << licon1_rect + licon1_rect_ref1.movey(0.25).movex(0.19) + licon1_rect_ref2 = c << licon1_rect + licon1_rect_ref2.movey(0.25).movex(0.74) + + ##met1 + met1_height = 0.42 + met1_width = 0.23 + + met1_rect = gf.components.rectangle( + size=(met1_width, met1_height), layer=met1_drawing + ) + + met1_rect_ref1 = c << met1_rect + met1_rect_ref1.movey(0.125).movex(0.16) + + met1_rect_ref2 = c << met1_rect + met1_rect_ref2.movey(0.125).movex(0.71) + + ##labels + + met1_label_s = c.add_label( + "S", position=(0.255, 0.36), layer=met1_label, magnification=0.2 + ) + met1_label_d = c.add_label( + "D", position=(0.81, 0.36), layer=met1_label, magnification=0.2 + ) + # c.add_label() + + return c + + +@gf.cell +def cmirror_top(mult=3) -> Component: + + Top_cell = gf.Component("top") + + cmirror = nmos() + cell_height = 0.67 + cell_width = 1.1 + space_bet_rows = 0.66 + # mult = 8 + + ##pwell + pwell_width = (cell_width * mult) + 0.11 + pwell_height = (cell_height * 2) + space_bet_rows + 0.11 + + pwell_rect = gf.components.rectangle( + size=(pwell_width, pwell_height), layer=pwell_drawing + ) + pwell_rect_ref = Top_cell << pwell_rect + + pwell_rect_ref.movex(-0.055).movey(-0.055) + + ##dnwell + dnwell_width = (cell_width * mult) + 0.91 + dnwell_height = (cell_height * 2) + space_bet_rows + 0.91 + + dnwell_rect = gf.components.rectangle( + size=(dnwell_width, dnwell_height), layer=dnwell_drawing + ) + dnwell_rect_ref = Top_cell << dnwell_rect + + dnwell_rect_ref.movex(-0.455).movey(-0.455) + + for i in range(mult): + for j in range(2): + print(j) + ref = Top_cell << cmirror + ref.movex(cell_width * i).movey(cell_height * (j) + space_bet_rows * (j)) + + for i in range(mult): + for j in range(2): + poly_width = 0.25 + poly_height = 1.85 + + poly_row_conn_rect = gf.components.rectangle( + size=(poly_width, poly_height), layer=poly_drawing + ) + poly_row_conn_rect_ref = Top_cell << poly_row_conn_rect + + poly_row_conn_rect_ref.movex(cell_width * i + 0.425).movey(-0.005) + + via_height = 0.17 + via_width = 0.17 + via_rect = gf.components.rectangle( + size=(via_height, via_width), layer=via_drawing + ) + + via_rect_ref = Top_cell << via_rect + via_rect_ref.movex(cell_width * i + 0.425 + 0.04).movey(0.9) + + for i in range(mult): + for j in range(2): + met1_height = 1.59 + met1_width = 0.23 + + met1_row_conn_rect = gf.components.rectangle( + size=(met1_width, met1_height), layer=met1_drawing + ) + + met1_row_conn_rect_ref = Top_cell << met1_row_conn_rect + met1_row_conn_rect_ref.movex(cell_width * i + 0.16).movey(0.125) + + via_height = 0.17 + via_width = 0.17 + via_rect = gf.components.rectangle( + size=(via_height, via_width), layer=via_drawing + ) + + via_rect_ref = Top_cell << via_rect + via_rect_ref.movex(cell_width * i + 0.16 + 0.03).movey(0.9) + + met2_pin_width = 0.23 + met2_pin_height = 0.23 + + met2_pin_rect = gf.components.rectangle( + size=(met2_pin_width, met2_pin_height), layer=met2_pin + ) + + ## Center VSS M2 path + met2_center_width = 0.5 + cell_width * mult + met2_center_height = 0.23 + met2_center = gf.components.rectangle( + size=(met2_center_width, met2_center_height), layer=met2_drawing + ) + + met2_center_ref = Top_cell << met2_center + + met2_center_ref.movex(-0.5).movey(0.9 - 0.03) + + met2_pin_VSS = Top_cell << met2_pin_rect + met2_pin_VSS.movex(-0.5).movey(0.9 - 0.03) + + met2_label_I_in = Top_cell.add_label( + "VSS", position=(-0.5, 0.9 - 0.03), layer=met2_label, magnification=0.2 + ) + + # Iin and Iout Metal trunk + met2_tb_width = 0.5 + cell_width * mult + 1.25 + met2_tb_height = 0.23 + + met2_tb = gf.components.rectangle( + size=(met2_tb_width, met2_tb_height), layer=met2_drawing + ) + + ##Top trunk + met2_top_I_in_ref = Top_cell << met2_tb + met2_top_I_in_ref.movex(-0.5).movey(2.5) + + met2_pin_I_in = Top_cell << met2_pin_rect + met2_pin_I_in.movex(-0.5).movey(2.5) + + met2_label_I_in = Top_cell.add_label( + "I_in", position=(-0.5, 2.5), layer=met2_label, magnification=0.2 + ) + + met2_top_I_out_ref = Top_cell << met2_tb + met2_top_I_out_ref.movex(-0.5).movey(2.5 + 0.5) + + met2_pin_I_out = Top_cell << met2_pin_rect + met2_pin_I_out.movex(-0.5).movey(2.5 + 0.5) + + met2_label_I_out = Top_cell.add_label( + "I_out", position=(-0.5, 2.5 + 0.5), layer=met2_label, magnification=0.2 + ) + + ## Bottom trunk + met2_bottom_I_in_ref = Top_cell << met2_tb + met2_bottom_I_in_ref.movex(-0.5).movey(-0.73) + + met2_bottom_I_out_ref = Top_cell << met2_tb + met2_bottom_I_out_ref.movex(-0.5).movey(-0.73 - 0.5) + + ## Right end Trunks + met1_right_width = 0.23 + met1_right_height_1 = 4.46 + met1_right_height_2 = 3.46 + + met1_right_trunk_1 = gf.components.rectangle( + size=(met1_right_width, met1_right_height_2), layer=met1_drawing + ) + met1_right_trunk_2 = gf.components.rectangle( + size=(met1_right_width, met1_right_height_1), layer=met1_drawing + ) + + ## Right - I_in trunk + met1_right_trunk_ref_I_in = Top_cell << met1_right_trunk_1 + trunk_1_x_shift = 0.5 + cell_width * mult + met1_right_trunk_ref_I_in.movex(trunk_1_x_shift).movey(-0.73) + + ## Right - I_out trunk + met1_right_trunk_ref_I_out = Top_cell << met1_right_trunk_2 + trunk_2_x_shift = 0.5 + trunk_1_x_shift + met1_right_trunk_ref_I_out.movex(trunk_2_x_shift).movey(-1.23) + + via_height = 0.17 + via_width = 0.17 + via_rect = gf.components.rectangle(size=(via_height, via_width), layer=via_drawing) + + via_rect_ref = Top_cell << via_rect + via_rect_ref.movex(trunk_1_x_shift + 0.03).movey(-0.73 + 0.03) + via_rect_ref = Top_cell << via_rect + via_rect_ref.movex(trunk_1_x_shift + 0.03).movey( + met1_right_height_2 - 0.73 - 0.17 - 0.03 + ) + + via_rect_ref = Top_cell << via_rect + via_rect_ref.movex(trunk_2_x_shift + 0.03).movey(-1.23 + 0.03) + via_rect_ref = Top_cell << via_rect + via_rect_ref.movex(trunk_2_x_shift + 0.03).movey( + met1_right_height_1 - 1.23 - 0.17 - 0.03 + ) + + ##Connecting to trunk + ## i ---> col + ## j ---> row + for i in range(mult): + for j in range(2): + met1_tr_conn_height_1 = 1.275 + met1_tr_conn_height_2 = 1.775 + met1_tr_conn_width = 0.23 + + via_height = 0.17 + via_width = 0.17 + via_rect = gf.components.rectangle( + size=(via_height, via_width), layer=via_drawing + ) + + met1_tr_conn_rect_1 = gf.components.rectangle( + size=(met1_tr_conn_width, met1_tr_conn_height_1), layer=met1_drawing + ) + + met1_tr_conn_rect_2 = gf.components.rectangle( + size=(met1_tr_conn_width, met1_tr_conn_height_2), layer=met1_drawing + ) + + if i % 2 != 0: + if j % 2 != 0: + met1_tr_conn_rect_ref = Top_cell << met1_tr_conn_rect_2 + met1_tr_conn_rect_ref.movex(cell_width * i + 0.16 + 0.55).movey( + -1.23 + ) + + via_rect_ref = Top_cell << via_rect + via_rect_ref.movex(cell_width * i + 0.16 + 0.55 + 0.03).movey( + -1.23 + 0.03 + ) + else: + met1_tr_conn_rect_ref = Top_cell << met1_tr_conn_rect_1 + met1_tr_conn_rect_ref.movex(cell_width * i + 0.16 + 0.55).movey( + 1.455 + ) + + via_rect_ref = Top_cell << via_rect + via_rect_ref.movex(cell_width * i + 0.16 + 0.55 + 0.03).movey( + 1.455 + met1_tr_conn_height_1 - 0.17 - 0.03 + ) + else: + if j % 2 != 0: + met1_tr_conn_rect_ref = Top_cell << met1_tr_conn_rect_1 + met1_tr_conn_rect_ref.movex(cell_width * i + 0.16 + 0.55).movey( + -0.73 + ) + + via_rect_ref = Top_cell << via_rect + via_rect_ref.movex(cell_width * i + 0.16 + 0.55 + 0.03).movey( + -0.73 + 0.03 + ) + else: + met1_tr_conn_rect_ref = Top_cell << met1_tr_conn_rect_2 + met1_tr_conn_rect_ref.movex(cell_width * i + 0.16 + 0.55).movey( + 1.455 + ) + + via_rect_ref = Top_cell << via_rect + via_rect_ref.movex(cell_width * i + 0.16 + 0.55 + 0.03).movey( + 1.455 + met1_tr_conn_height_2 - 0.17 - 0.03 + ) + return Top_cell + + +Top_cell = cmirror_top(2) +Top_cell.show() diff --git a/openfasoc/generators/gdsfactory-gen/scripts/line-res_via-chain/drc-check/magic_commands.tcl b/openfasoc/generators/gdsfactory-gen/deprecated/scripts/line-res_via-chain/drc-check/magic_commands.tcl similarity index 100% rename from openfasoc/generators/gdsfactory-gen/scripts/line-res_via-chain/drc-check/magic_commands.tcl rename to openfasoc/generators/gdsfactory-gen/deprecated/scripts/line-res_via-chain/drc-check/magic_commands.tcl diff --git a/openfasoc/generators/gdsfactory-gen/scripts/line-res_via-chain/drc-check/run_drc.sh b/openfasoc/generators/gdsfactory-gen/deprecated/scripts/line-res_via-chain/drc-check/run_drc.sh similarity index 100% rename from openfasoc/generators/gdsfactory-gen/scripts/line-res_via-chain/drc-check/run_drc.sh rename to openfasoc/generators/gdsfactory-gen/deprecated/scripts/line-res_via-chain/drc-check/run_drc.sh diff --git a/openfasoc/generators/gdsfactory-gen/scripts/line-res_via-chain/drc-check/sky130A/sky130A.magicrc b/openfasoc/generators/gdsfactory-gen/deprecated/scripts/line-res_via-chain/drc-check/sky130A/sky130A.magicrc similarity index 100% rename from openfasoc/generators/gdsfactory-gen/scripts/line-res_via-chain/drc-check/sky130A/sky130A.magicrc rename to openfasoc/generators/gdsfactory-gen/deprecated/scripts/line-res_via-chain/drc-check/sky130A/sky130A.magicrc diff --git a/openfasoc/generators/gdsfactory-gen/scripts/line-res_via-chain/drc-check/sky130A/sky130A_setup.tcl b/openfasoc/generators/gdsfactory-gen/deprecated/scripts/line-res_via-chain/drc-check/sky130A/sky130A_setup.tcl similarity index 100% rename from openfasoc/generators/gdsfactory-gen/scripts/line-res_via-chain/drc-check/sky130A/sky130A_setup.tcl rename to openfasoc/generators/gdsfactory-gen/deprecated/scripts/line-res_via-chain/drc-check/sky130A/sky130A_setup.tcl diff --git a/openfasoc/generators/gdsfactory-gen/scripts/line-res_via-chain/line_res_gen.py b/openfasoc/generators/gdsfactory-gen/deprecated/scripts/line-res_via-chain/line_res_gen.py similarity index 100% rename from openfasoc/generators/gdsfactory-gen/scripts/line-res_via-chain/line_res_gen.py rename to openfasoc/generators/gdsfactory-gen/deprecated/scripts/line-res_via-chain/line_res_gen.py diff --git a/openfasoc/generators/gdsfactory-gen/scripts/line-res_via-chain/merge_structures.py b/openfasoc/generators/gdsfactory-gen/deprecated/scripts/line-res_via-chain/merge_structures.py similarity index 100% rename from openfasoc/generators/gdsfactory-gen/scripts/line-res_via-chain/merge_structures.py rename to openfasoc/generators/gdsfactory-gen/deprecated/scripts/line-res_via-chain/merge_structures.py diff --git a/openfasoc/generators/gdsfactory-gen/scripts/line-res_via-chain/pad_forty_met1_met5.GDS b/openfasoc/generators/gdsfactory-gen/deprecated/scripts/line-res_via-chain/pad_forty_met1_met5.GDS similarity index 100% rename from openfasoc/generators/gdsfactory-gen/scripts/line-res_via-chain/pad_forty_met1_met5.GDS rename to openfasoc/generators/gdsfactory-gen/deprecated/scripts/line-res_via-chain/pad_forty_met1_met5.GDS diff --git a/openfasoc/generators/gdsfactory-gen/scripts/line-res_via-chain/via_chain_gen.py b/openfasoc/generators/gdsfactory-gen/deprecated/scripts/line-res_via-chain/via_chain_gen.py similarity index 100% rename from openfasoc/generators/gdsfactory-gen/scripts/line-res_via-chain/via_chain_gen.py rename to openfasoc/generators/gdsfactory-gen/deprecated/scripts/line-res_via-chain/via_chain_gen.py diff --git a/openfasoc/generators/gdsfactory-gen/scripts/mimcap-array/drc-check/magic_commands.tcl b/openfasoc/generators/gdsfactory-gen/deprecated/scripts/mimcap-array/drc-check/magic_commands.tcl similarity index 100% rename from openfasoc/generators/gdsfactory-gen/scripts/mimcap-array/drc-check/magic_commands.tcl rename to openfasoc/generators/gdsfactory-gen/deprecated/scripts/mimcap-array/drc-check/magic_commands.tcl diff --git a/openfasoc/generators/gdsfactory-gen/scripts/mimcap-array/drc-check/run_drc.sh b/openfasoc/generators/gdsfactory-gen/deprecated/scripts/mimcap-array/drc-check/run_drc.sh similarity index 100% rename from openfasoc/generators/gdsfactory-gen/scripts/mimcap-array/drc-check/run_drc.sh rename to openfasoc/generators/gdsfactory-gen/deprecated/scripts/mimcap-array/drc-check/run_drc.sh diff --git a/openfasoc/generators/gdsfactory-gen/scripts/mimcap-array/drc-check/sky130A/sky130A.magicrc b/openfasoc/generators/gdsfactory-gen/deprecated/scripts/mimcap-array/drc-check/sky130A/sky130A.magicrc similarity index 100% rename from openfasoc/generators/gdsfactory-gen/scripts/mimcap-array/drc-check/sky130A/sky130A.magicrc rename to openfasoc/generators/gdsfactory-gen/deprecated/scripts/mimcap-array/drc-check/sky130A/sky130A.magicrc diff --git a/openfasoc/generators/gdsfactory-gen/scripts/mimcap-array/drc-check/sky130A/sky130A_setup.tcl b/openfasoc/generators/gdsfactory-gen/deprecated/scripts/mimcap-array/drc-check/sky130A/sky130A_setup.tcl similarity index 100% rename from openfasoc/generators/gdsfactory-gen/scripts/mimcap-array/drc-check/sky130A/sky130A_setup.tcl rename to openfasoc/generators/gdsfactory-gen/deprecated/scripts/mimcap-array/drc-check/sky130A/sky130A_setup.tcl diff --git a/openfasoc/generators/gdsfactory-gen/scripts/mimcap-array/mimcap_gen.py b/openfasoc/generators/gdsfactory-gen/deprecated/scripts/mimcap-array/mimcap_gen.py similarity index 100% rename from openfasoc/generators/gdsfactory-gen/scripts/mimcap-array/mimcap_gen.py rename to openfasoc/generators/gdsfactory-gen/deprecated/scripts/mimcap-array/mimcap_gen.py diff --git a/openfasoc/generators/gdsfactory-gen/scripts/mimcap-array/pad_forty_met1_met5.GDS b/openfasoc/generators/gdsfactory-gen/deprecated/scripts/mimcap-array/pad_forty_met1_met5.GDS similarity index 100% rename from openfasoc/generators/gdsfactory-gen/scripts/mimcap-array/pad_forty_met1_met5.GDS rename to openfasoc/generators/gdsfactory-gen/deprecated/scripts/mimcap-array/pad_forty_met1_met5.GDS diff --git a/openfasoc/generators/gdsfactory-gen/scripts/mimcap-array/sky130_fd_pr__cap_mim_m3_1_FJK8MM.gds b/openfasoc/generators/gdsfactory-gen/deprecated/scripts/mimcap-array/sky130_fd_pr__cap_mim_m3_1_FJK8MM.gds similarity index 100% rename from openfasoc/generators/gdsfactory-gen/scripts/mimcap-array/sky130_fd_pr__cap_mim_m3_1_FJK8MM.gds rename to openfasoc/generators/gdsfactory-gen/deprecated/scripts/mimcap-array/sky130_fd_pr__cap_mim_m3_1_FJK8MM.gds diff --git a/openfasoc/generators/gdsfactory-gen/scripts/mimcap-array/sky130_fd_pr__cap_mim_m3_2_FJK8MM.gds b/openfasoc/generators/gdsfactory-gen/deprecated/scripts/mimcap-array/sky130_fd_pr__cap_mim_m3_2_FJK8MM.gds similarity index 100% rename from openfasoc/generators/gdsfactory-gen/scripts/mimcap-array/sky130_fd_pr__cap_mim_m3_2_FJK8MM.gds rename to openfasoc/generators/gdsfactory-gen/deprecated/scripts/mimcap-array/sky130_fd_pr__cap_mim_m3_2_FJK8MM.gds diff --git a/openfasoc/generators/gdsfactory-gen/differential_pair.py b/openfasoc/generators/gdsfactory-gen/differential_pair.py deleted file mode 100644 index 2b2679ef6..000000000 --- a/openfasoc/generators/gdsfactory-gen/differential_pair.py +++ /dev/null @@ -1,401 +0,0 @@ -import gdsfactory as gf -from gdsfactory.generic_tech import get_generic_pdk -from gdsfactory.component import Component, ComponentReference - -from gdsfactory.generic_tech import get_generic_pdk -import sky130 - -gf.config.rich_output() -PDK = get_generic_pdk() -PDK.activate() - -pwell_drawing = (64,13) -dnwell_drawing = (64,18) -nwell_drawing = (64,20) -outline_ref = (236,0) -diff_drawing = (65,20) -psdm_drawing = (94,20) -nsdm_drawing = (93,44) -poly_drawing = (66,20) -poly_pin = (66,16) -poly_label = (66,5) -licon1_drawing = (66, 44) -npc_drawing = (95, 20) -li1_drawing = (67,20) -mcon_drawing = (67,44) -met1_drawing = (68,20) -met1_label = (68,5) -met1_pin = (68,16) -via_drawing = (68,44) -met2_drawing = (69,20) -met2_label = (69,5) -met2_pin = (69,16) -via2_drawing = (69,44) -met3_drawing = (70,20) -met3_label = (70,5) -met3_pin = (70,16) -text_drawing = (83,44) - -@gf.cell -def nmos(cell_height) -> Component: - c = Component() - - - - ##nsdm - nsdm_height = cell_height ##0.67 - nsdm_width = 1.1 - - nsdm_outline_rect = gf.components.rectangle(size=(nsdm_width,nsdm_height), layer=nsdm_drawing) - nsdm_outline_rect_ref = c << nsdm_outline_rect - - ##poly - poly_width = 0.25 - poly_height = cell_height + 0.01 ##0.68 - - poly_rect = gf.components.rectangle(size=(poly_width,poly_height), layer=poly_drawing) - poly_rect_ref = c << poly_rect - - poly_rect_ref.movex(0.425).movey(-0.005) - - ##diff - diff_width = 0.85 - diff_height = (cell_height - (0.125*2)) ##0.42 - diff_rect = gf.components.rectangle(size=(diff_width,diff_height), layer=diff_drawing) - diff_rect_ref = c << diff_rect - - diff_rect_ref.movex(0.125).movey(0.125) - - ##li1_drawing - li1_height = (cell_height - (0.085*2)) ##0.5 - li1_width = 0.17 - li1_rect = gf.components.rectangle(size=(li1_width,li1_height), layer=li1_drawing) - - li1_rect_ref1 = c << li1_rect - li1_rect_ref1.movey(0.085).movex(0.19) - li1_rect_ref2 = c << li1_rect - li1_rect_ref2.movey(0.085).movex(0.74) - - ##mcon - mcon_height = 0.17 - mcon_width = 0.17 - - mcon_rect = gf.components.rectangle(size=(mcon_width,mcon_height), layer=mcon_drawing) - - for i in range(int(cell_height / 0.67)): - mcon_rect_ref1 = c << mcon_rect - mcon_rect_ref1.movey(0.67*i + 0.25).movex(0.19) - mcon_rect_ref2 = c << mcon_rect - mcon_rect_ref2.movey(0.67*i + 0.25).movex(0.74) - - ##licon1 - licon1_height = 0.17 - licon1_width = 0.17 - - licon1_rect = gf.components.rectangle(size=(licon1_width,licon1_height), layer=licon1_drawing) - - for i in range(int(cell_height / 0.67)): - licon1_rect_ref1 = c << licon1_rect - licon1_rect_ref1.movey(0.67*i + 0.25).movex(0.19) - licon1_rect_ref2 = c << licon1_rect - licon1_rect_ref2.movey(0.67*i + 0.25).movex(0.74) - - ##met1 - met1_height = (cell_height - (0.125*2)) ##0.42 - met1_width = 0.23 - - met1_rect = gf.components.rectangle(size=(met1_width,met1_height), layer=met1_drawing) - - met1_rect_ref1 = c << met1_rect - met1_rect_ref1.movey( 0.125).movex(0.16) - - met1_rect_ref2 = c << met1_rect - met1_rect_ref2.movey(0.125).movex(0.71) - - ##labels - - met1_label_s = c.add_label("S", position=(0.255,(cell_height/2)), layer=met1_label, magnification=0.2) - met1_label_d = c.add_label("D", position=(0.81,(cell_height/2)), layer=met1_label, magnification=0.2) - #c.add_label() - - return c - -@gf.cell -def diff_pair_top(mult=3, cell_height=0.67) -> Component: - - Top_cell = gf.Component("top") - - mult = mult*2 - mos_comp = nmos(cell_height) - #cell_height = 0.67 - cell_width = 1.1 - space_bet_rows = 0.66 - #mult = 8 - - ##pwell - pwell_width = (cell_width*((mult+1)/2)) + 0.11 - pwell_height = (cell_height*1) + 0.11 - - pwell_rect = gf.components.rectangle(size=(pwell_width,pwell_height), layer=pwell_drawing) - pwell_rect_ref = Top_cell << pwell_rect - - pwell_rect_ref.movex(-0.055).movey(-0.055) - - ##dnwell - dnwell_width = (cell_width*((mult+1)/2)) + 0.91 - dnwell_height = (cell_height*1) + 0.91 - - dnwell_rect = gf.components.rectangle(size=(dnwell_width,dnwell_height), layer=dnwell_drawing) - dnwell_rect_ref = Top_cell << dnwell_rect - - dnwell_rect_ref.movex(-0.455).movey(-0.455) - - - for i in range(mult): - j=0 - #for j in range(2): - print(j) - ref = Top_cell << mos_comp - if i == 0: - ref.movex(cell_width*i).movey(cell_height*(j) + space_bet_rows*(j)) - else: - ref.movex(cell_width*i - (cell_width*i/2)).movey(cell_height*(j) + space_bet_rows*(j)) - - - for i in range(mult): - #for j in range(2): - if (i%2 == 0) : - poly_width = 0.25 - poly_height = cell_height + 0.1 - - poly_row_conn_rect = gf.components.rectangle(size=(poly_width,poly_height), layer=poly_drawing) - poly_row_conn_rect_ref = Top_cell << poly_row_conn_rect - - poly_row_conn_rect_ref.movex(cell_width*i - (cell_width*i/2) + 0.425).movey(-0.1) - - elif (i%2 != 0) : - poly_width = 0.25 - poly_height = cell_height + 0.1 - - poly_row_conn_rect = gf.components.rectangle(size=(poly_width,poly_height), layer=poly_drawing) - poly_row_conn_rect_ref = Top_cell << poly_row_conn_rect - - poly_row_conn_rect_ref.movex(cell_width*i - (cell_width*i/2) + 0.425).movey(0) - - #via_height = 0.17 - #via_width = 0.17 - #via_rect = gf.components.rectangle(size=(via_height,via_width), #layer=via_drawing) - - #via_rect_ref = Top_cell << via_rect - #via_rect_ref.movex(cell_width*i + 0.425 + 0.04).movey(0.9) - - ''' - for i in range(mult): - #for j in range(2): - met1_height = 1.59 - met1_width = 0.23 - - met1_row_conn_rect = gf.components.rectangle(size=(met1_width,met1_height), layer=met1_drawing) - - met1_row_conn_rect_ref = Top_cell << met1_row_conn_rect - met1_row_conn_rect_ref.movex(cell_width*i + 0.16).movey(0.125) - - via_height = 0.17 - via_width = 0.17 - via_rect = gf.components.rectangle(size=(via_height,via_width), layer=via_drawing) - - #via_rect_ref = Top_cell << via_rect - #via_rect_ref.movex(cell_width*i + 0.16 + 0.03).movey(0.9) - ''' - - met2_pin_width = 0.23 - met2_pin_height = 0.23 - - met2_pin_rect = gf.components.rectangle(size=(met2_pin_width,met2_pin_height), layer=met2_pin) - - poly_pin_width = poly_pin_height = 0.25 - poly_pin_rect = gf.components.rectangle(size=(poly_pin_width,poly_pin_height), layer=poly_pin) - - ## Center VSS M2 path - #met2_center_width = 0.5 + cell_width*mult - #met2_center_height = 0.23 - #met2_center = gf.components.rectangle(size=(met2_center_width,#met2_center_height), layer=met2_drawing) - - #met2_center_ref = Top_cell << met2_center - - #met2_center_ref.movex(-0.5).movey(0.9 - 0.03) - - #met2_pin_VSS = Top_cell << met2_pin_rect - #met2_pin_VSS.movex(-0.5).movey(0.9 - 0.03) - - ## VSS pin label - #met2_label_VSS = Top_cell.add_label("VSS", position=(-0.5,0.9 - 0.03), layer=met2_label, magnification=0.2) - - #G_M1 and G_M2 Poly Trunk - poly_tb_width = 0.5 + cell_width*((mult+1)/2) - poly_tb_height = 0.25 - - poly_tb_ref = gf.components.rectangle(size=(poly_tb_width,poly_tb_height), layer=poly_drawing) - ## Top poly trunk - - #G_M1 - poly_top_G_M1 = Top_cell << poly_tb_ref - poly_top_G_M1.movex(-0.5).movey(cell_height + 0.1) - - poly_pin_G_M1 = Top_cell << poly_pin_rect - poly_pin_G_M1.movex(-0.5).movey(cell_height + 0.1) - - poly_label_G_M1 = Top_cell.add_label("G_M1", position=(-0.5 + 0.25,cell_height + 0.1 + 0.25), layer=poly_label, magnification=0.2) - - #G_M2 - poly_top_G_M2 = Top_cell << poly_tb_ref - poly_top_G_M2.movex(-0.5).movey(-0.23- 0.1) - - poly_pin_G_M2 = Top_cell << poly_pin_rect - poly_pin_G_M2.movex(-0.5).movey( -0.23 - 0.1) - - poly_label_G_M2 = Top_cell.add_label("G_M2", position=(-0.5 + 0.25, -0.23 - 0.1 +0.25), layer=poly_label, magnification=0.2) - - #D_M1 and D_M2 Metal trunk - met2_tb_width = 0.5 + cell_width*((mult+1)/2) + 0.75 - met2_tb_height = 0.23 - - met2_tb = gf.components.rectangle(size=(met2_tb_width,met2_tb_height), layer=met2_drawing) - - ##Top trunk - - #D_M1 - met2_top_I_in_ref = Top_cell << met2_tb - met2_top_I_in_ref.movex(-0.5).movey(cell_height + 0.5) - - met2_pin_I_in = Top_cell << met2_pin_rect - met2_pin_I_in.movex(-0.5).movey(cell_height + 0.5) - - met2_label_I_in = Top_cell.add_label("VSS", position=(-0.5 + 0.25,cell_height + 0.5 + 0.2), layer=met2_label, magnification=0.2) - - #D_M2 - met2_top_I_out_ref = Top_cell << met2_tb - met2_top_I_out_ref.movex(-0.5).movey(cell_height + 0.5 + 0.5) - - met2_pin_I_out = Top_cell << met2_pin_rect - met2_pin_I_out.movex(-0.5).movey(cell_height + 0.5 + 0.5) - - met2_label_I_out = Top_cell.add_label("D_M1", position=(-0.5 + 0.25, cell_height + 1.0 + 0.25), layer=met2_label, magnification=0.2) - - ## Bottom trunk - - ##D_M1 - met2_bottom_I_in_ref = Top_cell << met2_tb - met2_bottom_I_in_ref.movex(-0.5).movey(-0.5 - 0.23) - - ##D_M2 - met2_bottom_I_out_ref = Top_cell << met2_tb - met2_bottom_I_out_ref.movex(-0.5).movey(-0.5 - 0.23 - 0.5) - - met2_pin_I_in = Top_cell << met2_pin_rect - met2_pin_I_in.movex(-0.5).movey(-0.5 - 0.23 - 0.5) - - met2_label_I_in = Top_cell.add_label("D_M1", position=(-0.5 + 0.25,-0.5 - 0.23 - 0.5 + 0.25), layer=met2_label, magnification=0.2) - - - ## Right end Trunks - met1_right_width = 0.23 - met1_right_height_1 = cell_height + (0.5*2) + (0.23*2) - met1_right_height_2 = 3.46 - - met1_right_trunk_1 = gf.components.rectangle(size=(met1_right_width,met1_right_height_1), layer=met1_drawing) - met1_right_trunk_2 = gf.components.rectangle(size=(met1_right_width,met1_right_height_2), layer=met1_drawing) - - ## Right - I_in trunk - met1_right_trunk_ref_I_in = Top_cell << met1_right_trunk_1 - trunk_1_x_shift = 0.5 + cell_width*((mult+1)/2) - met1_right_trunk_ref_I_in.movex(trunk_1_x_shift).movey( -(0.5 + 0.23)) - - ## Right - I_out trunk - #met1_right_trunk_ref_I_out = Top_cell << met1_right_trunk_2 - #trunk_2_x_shift = 0.5 + trunk_1_x_shift - #met1_right_trunk_ref_I_out.movex(trunk_2_x_shift).movey(-1.23) - - via_height = 0.17 - via_width = 0.17 - via_rect = gf.components.rectangle(size=(via_height,via_width), layer=via_drawing) - - via_rect_ref = Top_cell << via_rect - via_rect_ref.movex(trunk_1_x_shift + 0.03).movey(-(0.5+0.23) + 0.03) - via_rect_ref = Top_cell << via_rect - via_rect_ref.movex(trunk_1_x_shift + 0.03).movey(met1_right_height_1 - (0.5 + 0.23) -0.17 - 0.03) - - #via_rect_ref = Top_cell << via_rect - #via_rect_ref.movex(trunk_2_x_shift + 0.03).movey(-1.23 + 0.03) - #via_rect_ref = Top_cell << via_rect - #via_rect_ref.movex(trunk_2_x_shift + 0.03).movey(met1_right_height_2 - 1.23 -0.17 - 0.03) - - - ##Connecting to trunk - ## i ---> col - ## j ---> row - connect = "up" - for i in range(mult): - #for j in range(2): - met1_tr_conn_height_1 = (cell_height + 0.5 + 0.23 ) - 0.125 ##1.275 - met1_tr_conn_height_2 = (cell_height + 1.0 + 0.23 ) - 0.125 ##1.775 - met1_tr_conn_width = 0.23 - - via_height = 0.17 - via_width = 0.17 - via_rect = gf.components.rectangle(size=(via_height,via_width), layer=via_drawing) - - met1_tr_conn_rect_1 = gf.components.rectangle(size=(met1_tr_conn_width,met1_tr_conn_height_1), layer=met1_drawing) - - met1_tr_conn_rect_2 = gf.components.rectangle(size=(met1_tr_conn_width,met1_tr_conn_height_2), layer=met1_drawing) - - if(i == 0): - met1_tr_conn_rect_ref = Top_cell << met1_tr_conn_rect_2 - met1_tr_conn_rect_ref.movex( 0.16).movey(0.125) - - via_rect_ref = Top_cell << via_rect - via_rect_ref.movex(0.16 + 0.03).movey(0.125 + met1_tr_conn_height_2 - 0.17 - 0.03) - - if(i%2 != 0): - #if((i+3)%2 == 0): - if connect == "up": - print("\nMult position") - print(i) - met1_tr_conn_rect_ref = Top_cell << met1_tr_conn_rect_2 - met1_tr_conn_rect_ref.movex(cell_width*i - (cell_width*i/2) + 0.16+0.55).movey(-1.23) - - via_rect_ref = Top_cell << via_rect - via_rect_ref.movex(cell_width*i - (cell_width*i/2) + 0.16 + 0.55 + 0.03).movey(-1.23 + 0.03) - connect = "down" - else: - print("\nElse ---- > Mult position") - print(i) - met1_tr_conn_rect_ref = Top_cell << met1_tr_conn_rect_2 - met1_tr_conn_rect_ref.movex(cell_width*i - (cell_width*i/2) + 0.16+0.55).movey(0.125) - - via_rect_ref = Top_cell << via_rect - via_rect_ref.movex(cell_width*i - (cell_width*i/2) + 0.16 + 0.55 + 0.03).movey(0.125 + met1_tr_conn_height_2 - 0.17 - 0.03) - connect = "up" - - if(i%2 == 0): - if(i%4 != 0): - - met1_tr_conn_rect_ref = Top_cell << met1_tr_conn_rect_1 - met1_tr_conn_rect_ref.movex(cell_width*i - (cell_width*i/2) + 0.16+0.55).movey(-0.73) - - via_rect_ref = Top_cell << via_rect - via_rect_ref.movex(cell_width*i - (cell_width*i/2) + 0.16 + 0.55 + 0.03).movey(-0.73 + 0.03) - else: - met1_tr_conn_rect_ref = Top_cell << met1_tr_conn_rect_1 - met1_tr_conn_rect_ref.movex(cell_width*i - (cell_width*i/2) + 0.16+0.55).movey(0.125) - - via_rect_ref = Top_cell << via_rect - via_rect_ref.movex(cell_width*i - (cell_width*i/2) + 0.16 + 0.55 + 0.03).movey(0.125 + met1_tr_conn_height_1 - 0.17 - 0.03) - - - return Top_cell - - -## Top cell creation -Top_cell = diff_pair_top(5, 1.34) -Top_cell.show() diff --git a/openfasoc/generators/gdsfactory-gen/glayout/README.md b/openfasoc/generators/gdsfactory-gen/glayout/README.md new file mode 100644 index 000000000..1f0d9f5bc --- /dev/null +++ b/openfasoc/generators/gdsfactory-gen/glayout/README.md @@ -0,0 +1,220 @@ +All functions, classes, etc have a help docustring. See python help() for specific questions + +- [Glayout](#glayout) + - [MappedPDK](#mappedpdk) + - [Generic Layers](#generic-layers) + - [Generic Rule Guide](#generic-rule-guide) + - [Creating a MappedPDK](#creating-a-mappedpdk) + - [PDK Agnostic Layout, Basics](#pdk-agnostic-layout-basics) + - [Via Stack Generator](#via-stack-generator) + - [Routing](#routing) + - [PDK Agnostic Hierarchical Cells](#pdk-agnostic-hierarchical-cells) + - [Example 1: via\_array](#example-1-via_array) + - [Example 2: tapring](#example-2-tapring) + - [Example 3: fet](#example-3-fet) + - [Advanced Topics](#advanced-topics) + - [Cells and PDK.activate()](#cells-and-pdkactivate) + - [Important GDSFactory Notes and Glayout Utilities](#important-gdsfactory-notes-and-glayout-utilities) + - [Port Naming Best Practices Guide](#port-naming-best-practices-guide) + - [PortTree](#porttree) + - [Snap to 2x grid](#snap-to-2x-grid) + - [Mimcaps Implementation](#mimcaps-implementation) + - [DRC](#drc) + - [LVS, and Labeling Issues](#lvs-and-labeling-issues) + - [Addressing Complicated Requirments with Default Decorators](#addressing-complicated-requirments-with-default-decorators) + - [API overview](#api-overview) + + +# Glayout +Glayout is a layout automation tool which generates DRC clean circuit layouts for any technology implementing the Glayout framework. Glayout is implemented as an easy-to-install python package. All required Glayout dependencies are available on pypi and are installed automatically from pypi when downloading OpenFASOC. Glayout (being a generic layout automation tool) does not require an installed pdk (just a MappedPDK description, explained below). Glayout is composed of 2 main parts: the generic pdk framework and the circuit generators. +The generic pdk framework allows for describing any pdk in a standardized format. The “pdk” sub-package within Glayout contains all code for the generic pdk class (known as “MappedPDK”) in addition to sky130 and gf180 MappedPDK objects. Because MappedPDK is a python class, describing a technology with a MappedPDK allows for passing the pdk as a python object. +The PDK generic circuit generator programs (also known as cells) are python functions which take as arguments a MappedPDK object and a set of optional layout parameters to produce a DRC clean layout. For example, an nMOS circuit generator (also known as nMOS cell), would require the user to specify a MappedPDK and, optionally, the transistor length, width, number of fingers, etc. + +## MappedPDK +There are only two absolute requirements for drawing a layout: a set of drawing layers, and a set of rules governing the geometric dimensions between layers. All CMOS technologies must satisfy these two requirements. +### Generic Layers +Almost all CMOS technologies have some version of basically the same layers, some of which are: “active/diffusion”, ”metal contact”, “metal 1”, “via1”, …etc. The layer description format of tuple(integer, integer) is standard. The idea of a generic layer is to map the standard layer names to a specific tuple(integer, integer) depending on the technology. For example, a generic layer present in most technologies is “metal 2”. The sky130 version of “metal 2” is the integer tuple (68, 20). The gf180 version of “metal 2” is the integer tuple (34,0). Importantly, the designer does not use or care about the value of the integer tuple. The designer only cares about the layer it represents, which they can always access in python from the generic name. +MappedPDK provides the designer with all the generic layers necessary, some of which are: “diffusion”, “dnwell”, “nwell”, “pwell”, “p+s/d”, “n+s/d”, “mcon”...etc. MappedPDK guarantees that regardless of which technology is represented ‘under the hood’ and what the value of the tuple(integer, integer), these layers will be accessible by the same names. The designer can access generic layers using the following syntax (for example): +`MappedPDK.get_glayer(“metal 2”)` +This “get_glayer” instance method of the MappedPDK takes the generic layer name and returns the tuple(integer, integer) specific to the technology. A MappedPDK object supports mappings for all design layers necessary. The BEOL generic layers support a metal stack met1-met5. Because metal stacks and some layers are technology dependent, the MappedPDK contains the “MappedPDK.verify_glayers()” method for verifying the presence of layers. For example, if a technology BEOL contains met1-met2, but a cell requires met3, it is possible for the cell generator to verify at runtime that the technology contains met3. + +**BEOL Example Generic Layer Mappings** +| Generic Layer Name | sky130 | gf180 | +| :-: | :-: | :-: | +|mcon| (66,44) | (33,0) | +|met1| (67,20) | (34,0) | +|via1| (67,44) | (35,0) | +|met2| (68,20) | (36,0) | + + +### Generic Rule Guide +Almost all CMOS technologies have some version of basically the same three rules: “min_separation”, “min_enclosure”, and “min_width” (or “width” for via layers). Hundreds of rules arise by prescribing one of these three rules between combinations of layers. For example, there may be a rule which requires a “min_enclosure” between “via1” and “metal 2”. There can also be “self rules” or rules describing a requirement between a layer and itself; most “min_width” rules are self rules. An example of a “self rule” between “metal 2” and “metal 2” would be the “min_width” rule. +The description of CMOS rules provided in the above paragraph fits very well within a mathematical graph. Layers can be thought of as vertices in the graph. Rules describe relationships between layers; rules can be thought of as edges in the graph. A “self rule” can be thought of as a “self edge” in the graph (an edge connecting a vertex to itself). This graph can be described mathematically as an undirected graph. +**figure here of example rule graph** +To greatly simplify the rule graph, context dependent rules (sometimes referred to as lambda rules) are eliminated by taking the worst case value for each rule. This allows the designer to lookup rules without providing any additional context of surrounding layer geometry (usually required for dependent rules). +Rule lookups are performed using the following syntax (for example, rules between metal2 and via1): +`MappedPDK.get_grule(“metal 2”, “via 1”)` +The MappedPDK.get_grule method returns a python dictionary containing all rules between the two layers provided (all edges between the two vertices). The keys are one of the three rule names “min_enclosure”, “min_separation”, or “min_width” / “width” (depending on the context). Furthermore, as an undirected graph, an equivalent lookup for this dictionary is the following syntax: +`MappedPDK.get_grule(“via 1”, “metal 2”)` +For self edges, the following simplified syntax is available: +`MappedPDK.get_grule(“metal 2”, “metal 2”)` or `MappedPDK.get_grule(“metal 2”)` +### Creating a MappedPDK +To create a MappedPDK for an arbitrary technology, the generic layer mapping and the rule deck must be provided. MappedPDK stores generic layers as a python dictionary; the keys are generic layer names and the values are tuple(int, int) layers. Keys must be one of the generic layers listed in the class variable MappedPDK.valid_glayers; this class variable is an attribute which belongs to the MappedPDK type rather than an individual instance of MappedPDK so it should not be modified. +It is up to the programmer to decide which technology layer should be used for each generic layer. For example, the Skywater 130nm technology provides a layer called “local interconnect” which is a Titanium Nitride layer used for local routing. Local interconnect has similar (on order of magnitude) conductivity to the metal layers. The glayout provided sky130 MappedPDK object maps: the generic “metal 1” to the sky130 local interconnect layer, the generic “metal contact” to the sky130 local interconnect contact layer, and the generic “via 1” to the sky130 metal contact layer. Progressing up the BEOL, the sky130 MappedPDK generic metals are actually 1 metal ahead of the real layers that are being used; for example, the generic “metal 2” is actually the sky130 metal 1 layer. +Because there are less than 20 generic layers, MappedPDK requires the programmer to manually define the generic layer python map and pass it to the constructor. However, the generic rules are much more numerous. Glayout provides a utility tool to assist in creating the MappedPDK rule deck. There is a spreadsheet to rule representation conversion program which assists with this. + +## PDK Agnostic Layout, Basics +The python layout generators (known as “cell factories”, but sometimes referred to as “cells” or "components" or "component factories") are built on the MappedPDK framework. All cell factories should have the `@cell` decorator which can be imported with +`from gdsfactory.cell import cell` +The MappedPDK.get_glayer and MappedPDK.get_grule methods enable the construction of DRC clean layouts programmatically. However, it is the responsibility of the Cell factory programmer to ensure that the proper rules and layer checks are executed. **The quality of the programmer is the quality of the cell.** +### Via Stack Generator +The only stand alone cell (cell factory which does not call other cell factories) in the glayout package is the via stack. Cell factories generally follow a similar programming procedure, so via stack provides a good introduction to the cell factory structure. +Like all cells, via stack takes as the first argument a MappedPDK object. There are two other required arguments which specify the generic layers to create the via stack between; the order in which these “glayers” (another name for generic layers) are provided does not matter. There are also several optional arguments providing more specific layout control. To explain this cell, the following function call will be assumed: +`via_stack(GF180_MappedPDK, “active”, “metal 3”)` OR `via_stack(GF180_MappedPDK, “metal 3”, “active”)` +Most cells start by running layer error checking. The via stack must verify that the provided MappedPDK contains both glayers provided and both glayers provided can be routed between. For example, it is usually not possible to route from “nwell” without an “n+s/d” implant, so if one of the layers provided is “nwell”, via stack raises an exception. Additionally, via stack must verify that all layers in between the provided glayers are available in the pdk. In this case, the required glayers are: “active”, “metal contact”, “metal 1”, “via 1”, “metal 2”, via 2”, and “metal 3”. For the passed MappedPDK (GF180), all required glayers are present, but in the case that a glayer is not present, via stack raises an exception. +layer error checking is done with [`pdk.has_required_glayers(glayers_list)`](https://github.com/alibillalhammoud/OpenFASOC/blob/main/openfasoc/generators/gdsfactory-gen/glayout/pdk/mappedpdk.py#L142). +The via stack then loops through these layers, placing them one at a time. To legally size and place each layer, via stack must consider “min_enclosure” and “width” rules for vias and metals. For example, to lay the “active” layer, the “metal contact” “width” and the “metal contact” to “active” “min_enclosure” rules must be considered. To lay the “metal 1” layer, the “min_enclosure” and “width” rules of both the via above and the via below “metal 1” must be considered. The programmer of the generic cells must consider all relevant rules to produce a legal layout. Rules are accessed in cell code using the `MappedPDK.get_grule` method. +### Routing +Routing utilities are required to create complicated hierarchical designs. At the backend of routing is the gdsfactory “Port” object. Fundamentally, ports describe a polygon edge. Ports include center, width, and orientation of the edge, along with other attributes and utility methods. The glayout routing functions operate to create paths between ports. +As described with the via stack example above, the checks and sizings necessary for legal layout are executed in the cell generator. Glayout routing functions do not need to understand cell context; for this reason, routing functions are called “dumb routes”. There are three “dumb route” utilities: straight route, L route, and C route. Dumb routes are simple, but contain optional arguments which allow for precise control over created paths. The default path behavior is easy to predict and will generally make the most reasonable decisions if no direction is provided. +For example, Straight route creates a straight path directly between two ports. If the two provided ports are not collinear or have different orientations, the function will by default route from the first port to the imaginary line stretching perpendicularly from the second port. By default, the route will begin on the same layer as the first port and will lay a via stack if necessary at the second port. If two ports are parallel, Straight route will raise an exception. + +**Straight Route Default Behavoir:** +![straight route default behavoir](docs/straight_route_def_beh.png) + +L route and C route also create simple paths. L route creates an L shaped route (two straight paths perpendicular) and C route creates a C shaped route (two parallel paths connected by a straight path). +### PDK Agnostic Hierarchical Cells +All cells other than the via stack contain hierarchy. Combining hierarchy and careful routing allows for clean layouts while increasing complexity. +#### Example 1: [via_array](https://github.com/alibillalhammoud/OpenFASOC/blob/main/openfasoc/generators/gdsfactory-gen/glayout/via_gen.py#L180) +The most basic hierarchical cell is the [via_array](https://github.com/alibillalhammoud/OpenFASOC/blob/main/openfasoc/generators/gdsfactory-gen/glayout/via_gen.py#L180). Via array is composed of via stacks and has a similar interface to the via stack generator, but additionally accepts a size argument. The array spacing computation is another example of the programmers role in creating DRC clean layout. After error checking, the via array program creates the via stack single element that will be copied to create the array. Then, the generator loops through each layer and uses the gdsfactory component.extract method to get the dimension of that layer in the via stack; The min spacing for that layer is `pdk.get_grule(layer)["min_separation"] + 2*layer_dim`. After looping through the entire array, The maximum seperation is the correct spacing to use. +#### Example 2: [tapring](https://github.com/alibillalhammoud/OpenFASOC/blob/main/openfasoc/generators/gdsfactory-gen/glayout/guardring.py) +tapring produces a substrate / well tap rectanglular ring that legally enclose a rectangular shape. `gdsfactory.component.rectangular_ring` is used along with glayout [via_array](https://github.com/alibillalhammoud/OpenFASOC/blob/main/openfasoc/generators/gdsfactory-gen/glayout/via_gen.py#L180). The ring is always of minimum width and legalizing the ring is easy because via_array does most of the work. Special care is taken at the corners to ensure min spacing between adjacent metal layers is not below min_separation. Although not currently implemented, error checking for this ring should check the size is not too small (separation between edges is not legal). +Generators should be made as generic as possible. In this case, tapring can produce either a p-tap or n-tap ring. Glayers are just strings and they can be passed to functions as arguments. Also, you glayer variables can be passed directly to `pdk.get_grule(glayer_var)`. +#### Example 3: [fet](https://github.com/alibillalhammoud/OpenFASOC/blob/main/openfasoc/generators/gdsfactory-gen/glayout/fet.py) +The most important component factory in glayout is the [multiplier](https://github.com/alibillalhammoud/OpenFASOC/blob/main/openfasoc/generators/gdsfactory-gen/glayout/fet.py#L61) because it handles the difficult task of creating legal transistors. By passing the source/drain layer (either "p+s/d" or "n+s/d") multiplier code is reused to create nmos and pmos transistors. arrays of multipliers can be created to allow for transistors with several multipliers. read the help docustring for all functions in [fet.py](https://github.com/alibillalhammoud/OpenFASOC/blob/main/openfasoc/generators/gdsfactory-gen/glayout/fet.py) + +## Advanced Topics +The following topics are only neccessary if you want to code with glayout, but are not neccessary for a basic understanding of glayout. +### Cells and PDK.activate() +All cell factories should be decorated with the `@cell` decorator which can be imported from gdsfactory with `from gdsfactory.cell import cell`. You must also call pdk.activate() for cells to correctly work. This is related to caching, gds/oasis write settings, default decorators, etc. +### Important GDSFactory Notes and Glayout Utilities +The GDSFactory API is extremely versatile and there are many useful features. It takes some experience to learn about all features and identify the most useful tools from GDSFactory. GDSFactory serves as the backend GDS manipulation library and as an object oriented tool kit with several useful classes including: Components, Component References, and Ports. There are also common shapes as Components in GDSFactory such as rectangles, circles, rectangular_rings, etc. To automate common tasks that do not fit into GDSFactory, Glayout includes many utility functions. The most important of these functions are also addressed here. +- Components are the GDSFactory implementation of GDS cells. Components contain references to other components (Component Reference). Important methods are included below. + - Component.name: get or set the name of a Component + - Component.flatten(): flattens all references in the components + - Component.remove_layers(): removes some layers from the component and return the modified component + - Component.extract(): extract some layers from a component and return the modified component + - Component.ports: dictionary of ports in the component + - Component.add_ports(): add ports to the component + - Component.add_padding(): add a layer surrounding the component + - Component booleans: see the gdsfactory documentation for how to run boolean operations of components. + - Component.write_gds(): write the gds to disk + - Component.bbox: return bounding box of the component (xmin,ymin),(xmax,ymax). Glayout has an evaluate_bbox function which return the x and y dimensions of the bbox + - insertion operator: `ref = Component << Component_to_add` + - Component.add(): add an one of several types to a Component. (more flexible than << operator) + - Component.ref()/.ref_center(): return a reference to a component + +It is not possible to move Components in GDSFactory. GDSFactory has a Component cache, so moving a component may invalidate the cache, but there are situations where you want to move a component; For these situations, use the glayout [move](https://github.com/alibillalhammoud/OpenFASOC/blob/main/openfasoc/generators/gdsfactory-gen/glayout/pdk/util/comp_utils.py#L24), [movex](https://github.com/alibillalhammoud/OpenFASOC/blob/main/openfasoc/generators/gdsfactory-gen/glayout/pdk/util/comp_utils.py#L63), [movey](https://github.com/alibillalhammoud/OpenFASOC/blob/main/openfasoc/generators/gdsfactory-gen/glayout/pdk/util/comp_utils.py#L73) functions. + +- Component references are pointers to components. They have many of the same methods as Components with some additions. + - ComponentReference.parent: the Component which this component reference points to + - ComponentReference.movex, movey, move: you can move ComponentReferences + - ComponentReference.get_ports_list(): get a list of ports in the component. +Ports are edge descriptions. + +To add a ComponentReference to a Component, you cannot use the insertion operator. Use the Component.add() method. + +- A port describes a single edge of a polygon. The most useful port attributes are **width, center tuple(x,y), orientation (degrees), and layer of the edge**. + - For example, the rectangle cell factory provided in gdsfactory.components.rectangle returns a Component type with the following port names: e1, e2, e3, e4. + - e1=West, e2=North, e3=East, e4=South. The default naming scheme of ports in GDSFactory is not descriptive + - use glayout [rename_ports_by_orientation](https://github.com/alibillalhammoud/OpenFASOC/blob/main/openfasoc/generators/gdsfactory-gen/glayout/pdk/util/port_utils.py#L67), [rename_ports_by_list](https://github.com/alibillalhammoud/OpenFASOC/blob/main/openfasoc/generators/gdsfactory-gen/glayout/pdk/util/port_utils.py#L91) functions and see below for port naming best practices guide + - glayout [get_orientation](https://github.com/alibillalhammoud/OpenFASOC/blob/main/openfasoc/generators/gdsfactory-gen/glayout/pdk/util/port_utils.py#L124): returns the letter (N,E,S,W) or degrees of orientation of port. by default returns the one you do not have. see help. + - glayout [assert_port_manhattan](https://github.com/alibillalhammoud/OpenFASOC/blob/main/openfasoc/generators/gdsfactory-gen/glayout/pdk/util/port_utils.py#L159): assert that a port or list or ports have orientation N, E, S, or W + - glayout [assert_ports_perpindicular](https://github.com/alibillalhammoud/OpenFASOC/blob/main/openfasoc/generators/gdsfactory-gen/glayout/pdk/util/port_utils.py#L181): assert two ports are perpindicular + - glayout [set_port_orientation](https://github.com/alibillalhammoud/OpenFASOC/blob/main/openfasoc/generators/gdsfactory-gen/glayout/pdk/util/port_utils.py#L181): return new port which is copy of old port but with new orientation + - glayout [set_port_width](https://github.com/alibillalhammoud/OpenFASOC/blob/main/openfasoc/generators/gdsfactory-gen/glayout/pdk/util/port_utils.py#L202): return a new port which is a copy of the old one, but with new width + +A very important utility is [align_comp_to_port](https://github.com/alibillalhammoud/OpenFASOC/blob/main/openfasoc/generators/gdsfactory-gen/glayout/pdk/util/comp_utils.py#L83): pass a component or componentReference and a port, and align the component to any edge of the port. + +### Port Naming Best Practices Guide +As previously pointed out, the default naming of ports in GDSFactory is not descriptive. By default gdsfactory.components.rectangle returns ports e1 (West port), e2 (North port), e3 (East port), e4 (South port). Additionally, complicated hiearchies can result in thousands of ports, so organizing ports is a neccessity. The below best practices guide should be used to organize ports +- Ports use the "\_" syntax. Think of this like a directory tree for files. Each time you introduce a new level of hiearchy, you should add a prefix + "\_" describing the cell. + - For example, adding a via_array to the edge of a tapring, you should call +`tapring.add_ports(via_array.get_ports_list(),prefix="topviaarray_")` + - The port rename functions look for the "\_" syntax. You can NOT use the port rename functions without this syntax. +- The last 2 characters of a port name should "\_" followed by the orientation (N, E, S, or W) + - you can easily achieve this by calling glayout [`rename_ports_by_orientation`](https://github.com/alibillalhammoud/OpenFASOC/blob/main/openfasoc/generators/gdsfactory-gen/glayout/pdk/util/port_utils.py#L67) before returning a component (just the names end with "\_" before calling this function) +- **USE PORTS**: be sure to correctly add and label ports to components you make because you do not know when they will be used in other cells. + +#### PortTree +The [PortTree](https://github.com/alibillalhammoud/OpenFASOC/blob/main/openfasoc/generators/gdsfactory-gen/glayout/pdk/util/port_utils.py#L232) class is designed to assist in finding ports and understanding port structure. Initialize a PortTree by calling [`PortTree(Component or ComponentReference)`](https://github.com/alibillalhammoud/OpenFASOC/blob/main/openfasoc/generators/gdsfactory-gen/glayout/pdk/util/port_utils.py#L245). The PortTree will internally construct a directory tree structure from the Component's ports. You can use [`PortTree.print()`](https://github.com/alibillalhammoud/OpenFASOC/blob/main/openfasoc/generators/gdsfactory-gen/glayout/pdk/util/port_utils.py#L304) to print this whole structure for a nice figure explaining a Component's ports. See the example print output from a via_stack component below: + +**PortTree of a via_stack:** +![PortTree example](docs/PortTreeExample.png) + +### Snap to 2x grid +All rules (when creating a MappedPDK) and all user provided float arguments must be snapped to 2*grid size. This is because it is possible to center a component. Centering a component which has a dimension on grid may result in off grid polygons. You can snap floating point values to grid easily by calling `pdk.snap_to_2x_grid()`. You should also take care to snap to 2xgrid whenever you see it is neccessary while writing generator code. For example, most generators which take a size(xdim: float, ydim: float) argument should snap to 2xgrid. +### Mimcaps Implementation +Although many technolgies have 2 or more mimcap options, there is currently only 1 mimcap option supported. When creating a mapped pdk, you specify the cap metal layer as a generic layer, but you specify the metal above and metal below the cap met as part of the DRC rule set for `pdk.get_grule("capmet")`. You can access the metal above capmet with `pdk.get_grule(capmet)["capmettop"]`. +### DRC +If the system has klayout installed and you provide a klayout lydrc script for your MappedPDK, you can run DRC from python by calling pdk.drc(Component or GDS). The return value is a boolean (legal or not legal) and a lyrdb (xml format) file is written describing each DRC error. This file can be opened graphically in klayout with the following syntax `klayout layout.gds -m drc.lyrdb` +### LVS, and Labeling Issues +There are no glayers for labeling or pins, all cells are generated without any labels. You can easily add pins to your component manually after glayout write the gds, or by using ports, you can write a function for adding labels and pins. See [sky130_nist_tapeout example function](https://github.com/alibillalhammoud/OpenFASOC/blob/main/openfasoc/generators/gdsfactory-gen/tapeout_and_RL/sky130_nist_tapeout.py#L97). +### Addressing Complicated Requirments with Default Decorators +A python decorator is a function (the decorator) is a function which is called on another function. It can be used to enhance the features of a function. With GDSFactory Pdk (and MappedPDK objects) you can define a default decorator which runs on any cell factory (cell factories must be decorated with the `@cell` decorator). The default decorator you define runs in addition to the `@cell` decorator. The defined default_decorator should accept as argument a Component and return a Component. +This should be used when dealing with PDK specfic requirments that do not fit into the MappedPDK framework. For example, sky130 has a NPC (nitride poly cut) layer which **must** be used wherever licon (local interconnect contact) is laid over poly. It does not make sense to modify MappedPDK to add a generic NPC layer AND modify all cell factories; sky130 is unqiue in this requirment, so modifying MappedPDK/all cell factories would make glayout less generic. Instead, we define a default_decorator [`sky130_add_npc(Component) -> Component`](https://github.com/alibillalhammoud/OpenFASOC/blob/main/openfasoc/generators/gdsfactory-gen/glayout/pdk/sky130_mapped/sky130_add_npc.py). This function uses booleans to add npc anywhere licon is laid over poly (it also joins NPC polygons if they are closer than the NPC min separation rule). Layers and rules in this technology specific function are hard coded because this decorator will only run for sky130 is the active pdk (this is one reason why you must be sure that pdk is activated). + +## API overview +This section provides a high-level overview of all functions in glayout. See **docs** (TODO) printed docustrings of all files. + + +- glayout: + - generators + - via_gen.py + - via_stack: via between any two 'routable' layers + - via_array: array of via stacks. specify area or num vias desired + - guardring.py: create a tapring around an enclosed area + - fet.py + - multiplier: the basic building block for both n/pfets + - pfet + - nfet + - diff_pair.py: create a common centroid ab ba place diff pair (either n or pfet) + - opamp.py: create an opamp (TODO: see docs for netlist and general layout plan) + - mimcap.py + - mimcap + - mimcap_array + - common + - two_transistor_place.py: two_transistor_place, place two devices in any configuration specified by a string (e.g. aba bab aba) + - two_transistor_interdigitized.py + - two_transistor_interdigitized: place two transistor interdigitized + - two_nfet_interdigitized: a specialization of two_transistor_interdigitized to place specifically nfet + - routing + - straight_route: route in a straight line + - L_route: route in an L shape + - c_route: rout in a C shape + - pdk + - mappedpdk.py: MappedPDK class + - sky130_mapped_pdk: MappedPDK object for sky130 + - `from glayout.pdk.sky130_mapped import sky130_mapped_pdk` + - gf180_mapped_pdk: MappedPDK object for gf180 + - `from glayout.pdk.gf180_mapped import gf180_mapped_pdk` + - util + - comp_utils.py + - evaluate_bbox: returns [width, hieght] of a component + - move: move Component, compref, or Port + - movex: movex Component, compref, or Port + - movey: movey Component, compref, or Port + - align_comp_to_port: move a compref or Component such that it is aligned to a port (also specify how you want to align with `alignment` option). + - prec_array: create an array of components + - prec_center: return the amount of x,y translation required to center a component + - prec_ref_center: return a centered ref of a component + - get_padding_points_cc: get points of a rectangle which pads (with some extra space optionally) a component. (e.g. lay p+s/d over diffusion with padding=0.2um) + - to_decimal: convert a float or list of float (or decimal) to python decimal + - to_float: convert decimal or list of decimal (or float) to python float + - port_utils.py + - print_rules.py + - snap_to_grid.py + - standard_main.py + - opamp_array_create.py + + diff --git a/openfasoc/generators/gdsfactory-gen/glayout/__init__.py b/openfasoc/generators/gdsfactory-gen/glayout/__init__.py new file mode 100644 index 000000000..8c77e8aff --- /dev/null +++ b/openfasoc/generators/gdsfactory-gen/glayout/__init__.py @@ -0,0 +1 @@ +__version__ = "1" \ No newline at end of file diff --git a/openfasoc/generators/gdsfactory-gen/glayout/common/two_transistor_interdigitized.py b/openfasoc/generators/gdsfactory-gen/glayout/common/two_transistor_interdigitized.py new file mode 100644 index 000000000..691a96aa9 --- /dev/null +++ b/openfasoc/generators/gdsfactory-gen/glayout/common/two_transistor_interdigitized.py @@ -0,0 +1,157 @@ +from glayout.pdk.mappedpdk import MappedPDK +from pydantic import validate_arguments +from gdsfactory.component import Component +from glayout.fet import nmos, pmos, multiplier +from glayout.pdk.util.comp_utils import evaluate_bbox +from typing import Literal, Union +from glayout.pdk.util.port_utils import rename_ports_by_orientation, rename_ports_by_list +from glayout.pdk.util.comp_utils import prec_ref_center +from glayout.routing.straight_route import straight_route +from gdsfactory.functions import transformed +from glayout.guardring import tapring +from glayout.pdk.util.port_utils import add_ports_perimeter +from gdsfactory.cell import clear_cache + + +#from glayout.common.two_transistor_interdigitized import two_nfet_interdigitized; from glayout.pdk.sky130_mapped import sky130_mapped_pdk as pdk; biasParams=[6,2,4]; rmult=2 + +@validate_arguments +def two_transistor_interdigitized( + pdk: MappedPDK, + numcols: int, + deviceA_and_B: Literal["nfet", "pfet"], + dummy: Union[bool, tuple[bool, bool]] = True, + **kwargs +) -> Component: + """place two transistors in a single row with interdigitized placement + Currently only supports two of the same transistor (same devices) + Place follows an ABABAB... pattern + args: + pdk = MappedPDK to use + numcols = a single col is actually one col for both transistors (so AB). 2 cols = ABAB ... so on + deviceA_and_B = the device to place for both transistors (either nfet or pfet) + dummy = place dummy at the edges of the interdigitized place (true by default). you can specify tuple to place only on one side + kwargs = key word arguments for device. + ****NOTE: These are the same as glayout.fet.multiplier arguments EXCLUDING dummy, sd_route_extension, and pdk options + """ + if isinstance(dummy, bool): + dummy = (dummy, dummy) + # override kwargs for needed options + kwargs["sd_route_extension"] = 0 + kwargs["gate_route_extension"] = 0 + kwargs["sdlayer"] = "n+s/d" if deviceA_and_B == "nfet" else "p+s/d" + kwargs["pdk"] = pdk + # create devices dummy l/r and A/B (change extension options) + kwargs["dummy"] = (True,False) if dummy[0] else False + lefttmost_devA = multiplier(**kwargs) + kwargs["dummy"] = False + center_devA = multiplier(**kwargs) + devB_sd_extension = pdk.util_max_metal_seperation() + abs(center_devA.ports["drain_N"].center[1]-center_devA.ports["diff_N"].center[1]) + devB_gate_extension = pdk.util_max_metal_seperation() + abs(center_devA.ports["row0_col0_gate_S"].center[1]-center_devA.ports["gate_S"].center[1]) + kwargs["sd_route_extension"] = pdk.snap_to_2xgrid(devB_sd_extension) + kwargs["gate_route_extension"] = pdk.snap_to_2xgrid(devB_gate_extension) + center_devB = multiplier(**kwargs) + kwargs["dummy"] = (False,True) if dummy[1] else False + rightmost_devB = multiplier(**kwargs) + # place devices + idplace = Component() + dims = evaluate_bbox(center_devA) + xdisp = pdk.snap_to_2xgrid(dims[0]+pdk.get_grule("active_diff")["min_separation"]) + refs = list() + for i in range(2*numcols): + if i==0: + refs.append(idplace << lefttmost_devA) + elif i==((2*numcols)-1): + refs.append(idplace << rightmost_devB) + elif i%2: # is odd (so device B) + refs.append(idplace << center_devB) + else: # not i%2 == i is even (so device A) + refs.append(idplace << center_devA) + refs[-1].movex(i*(xdisp)) + devletter = "B" if i%2 else "A" + prefix=devletter+"_"+str(int(i/2))+"_" + idplace.add_ports(refs[-1].get_ports_list(), prefix=prefix) + #issue somehwere before line 89 + # merge s/d layer for all transistors + idplace << straight_route(pdk, refs[0].ports["plusdoped_W"],refs[-1].ports["plusdoped_E"]) + # create s/d/gate connections extending over entire row + A_src = idplace << rename_ports_by_orientation(rename_ports_by_list(straight_route(pdk, refs[0].ports["source_W"], refs[-1].ports["source_E"]), [("route_","_")])) + B_src = idplace << rename_ports_by_orientation(rename_ports_by_list(straight_route(pdk, refs[-1].ports["source_E"], refs[0].ports["source_W"]), [("route_","_")])) + A_drain = idplace << rename_ports_by_orientation(rename_ports_by_list(straight_route(pdk, refs[0].ports["drain_W"], refs[-1].ports["drain_E"]), [("route_","_")])) + B_drain = idplace << rename_ports_by_orientation(rename_ports_by_list(straight_route(pdk, refs[-1].ports["drain_E"], refs[0].ports["drain_W"]), [("route_","_")])) + A_gate = idplace << rename_ports_by_orientation(rename_ports_by_list(straight_route(pdk, refs[0].ports["gate_W"], refs[-1].ports["gate_E"]), [("route_","_")])) + B_gate = idplace << rename_ports_by_orientation(rename_ports_by_list(straight_route(pdk, refs[-1].ports["gate_E"], refs[0].ports["gate_W"]), [("route_","_")])) + # add route ports and return + prefixes = ["A_source","B_source","A_drain","B_drain","A_gate","B_gate"] + for i, ref in enumerate([A_src, B_src, A_drain, B_drain, A_gate, B_gate]): + idplace.add_ports(ref.get_ports_list(),prefix=prefixes[i]) + idplace = transformed(prec_ref_center(idplace)) + idplace.unlock() + return idplace + + +@validate_arguments +def two_nfet_interdigitized( + pdk: MappedPDK, + numcols: int, + dummy: Union[bool, tuple[bool, bool]] = True, + with_substrate_tap: bool = True, + with_tie: bool = True, + **kwargs +) -> Component: + """Currently only supports two of the same nfet instances. does NOT support multipliers (currently) + Place follows an ABABAB... pattern + args: + pdk = MappedPDK to use + numcols = a single col is actually one col for both nfets (so AB). 2 cols = ABAB ... so on + dummy = place dummy at the edges of the interdigitized place (true by default). you can specify tuple to place only on one side + kwargs = key word arguments for multiplier. + ****NOTE: These are the same as glayout.fet.multiplier arguments EXCLUDING dummy, sd_route_extension, and pdk options + """ + base_multiplier = two_transistor_interdigitized(pdk, numcols, "nfet", dummy, **kwargs) + # tie + if with_tie: + tap_separation = max( + pdk.get_grule("met2")["min_separation"], + pdk.get_grule("met1")["min_separation"], + pdk.get_grule("active_diff", "active_tap")["min_separation"], + ) + tap_separation += pdk.get_grule("p+s/d", "active_tap")["min_enclosure"] + tap_encloses = ( + 2 * (tap_separation + base_multiplier.xmax), + 2 * (tap_separation + base_multiplier.ymax), + ) + tiering_ref = base_multiplier << tapring( + pdk, + enclosed_rectangle=tap_encloses, + sdlayer="p+s/d", + horizontal_glayer="met2", + vertical_glayer="met1", + ) + base_multiplier.add_ports(tiering_ref.get_ports_list(), prefix="welltie_") + # add pwell + base_multiplier.add_padding( + layers=(pdk.get_glayer("pwell"),), + default=pdk.get_grule("pwell", "active_tap")["min_enclosure"], + ) + # add substrate tap + base_multiplier = add_ports_perimeter(base_multiplier,layer=pdk.get_glayer("pwell"),prefix="well_") + # add substrate tap if with_substrate_tap + if with_substrate_tap: + substrate_tap_separation = pdk.get_grule("dnwell", "active_tap")[ + "min_separation" + ] + substrate_tap_encloses = ( + 2 * (substrate_tap_separation + base_multiplier.xmax), + 2 * (substrate_tap_separation + base_multiplier.ymax), + ) + ringtoadd = tapring( + pdk, + enclosed_rectangle=substrate_tap_encloses, + sdlayer="p+s/d", + horizontal_glayer="met2", + vertical_glayer="met1", + ) + tapring_ref = base_multiplier << ringtoadd + base_multiplier.add_ports(tapring_ref.get_ports_list(),prefix="substratetap_") + return base_multiplier diff --git a/openfasoc/generators/gdsfactory-gen/glayout/common/two_transistor_place.py b/openfasoc/generators/gdsfactory-gen/glayout/common/two_transistor_place.py new file mode 100644 index 000000000..36e2ad708 --- /dev/null +++ b/openfasoc/generators/gdsfactory-gen/glayout/common/two_transistor_place.py @@ -0,0 +1,51 @@ +from glayout.pdk.mappedpdk import MappedPDK +from pydantic import validate_arguments +from gdsfactory.component import Component +from typing import Callable +from glayout.fet import nmos, pmos +from glayout.pdk.util.comp_utils import evaluate_bbox + +@validate_arguments +def two_transistor_place(pdk: MappedPDK, pattern: str, deviceA: tuple[Callable, dict], deviceB: tuple[Callable, dict]) -> Component: + """Place two transitors according to the patter provided + args: + pdk = MappedPDK to use + pattern = placement pattern. This string must contain only white space, the char a, the char b. + **** any other chars result in error. White space indicates a new row in the place + **** all rows must have same number of cols + deviceA/deviceB = tuple(function to call, kwargs for function) kwargs must include pdk + """ + toplvlcomp = Component("2tranplace") + # create the transistors + tranA = deviceA[0](**deviceA[1]) + tranA_dims = evaluate_bbox(tranA) + tranB = deviceB[0](**deviceB[1]) + tranB_dims = evaluate_bbox(tranB) + # parse pattern into a matrix + pattern = pattern.lower().split() + parsed_pattern = list() + for i, row in enumerate(pattern): + parsed_pattern.append(list()) + if i==0: + num_cols = len(row) + elif len(row)!=num_cols: + raise ValueError("all rows should have same number of devices") + for char in row: + if char=="a": + parsed_pattern[i].append(tranA) + elif char=="b": + parsed_pattern[i].append(tranB) + else: + raise ValueError("pattern should only contain a,b, or whitespace") + # run place (center, then right, then left, ...) + extra_sep = 2*pdk.util_max_metal_seperation() + yspace = extra_sep + max(tranA_dims[1], tranB_dims[1]) + xspace = extra_sep + max(tranA_dims[0], tranB_dims[0]) + for i, row in enumerate(parsed_pattern): + for j, tran in enumerate(row): + tranref = toplvlcomp << tran + ymov = i * yspace + xmov = j * xspace * (-1**(j%2)) + tranref.movex(xmov).movey(ymov) + toplvlcomp.add_ports(tranref.get_ports_list(), prefix=f"place{i}_{j}_") + return toplvlcomp diff --git a/openfasoc/generators/gdsfactory-gen/glayout/diff_pair.py b/openfasoc/generators/gdsfactory-gen/glayout/diff_pair.py new file mode 100644 index 000000000..b6aaa4d63 --- /dev/null +++ b/openfasoc/generators/gdsfactory-gen/glayout/diff_pair.py @@ -0,0 +1,128 @@ +from gdsfactory.cell import cell +from gdsfactory.component import Component, copy +from gdsfactory.components.rectangle import rectangle +from glayout.fet import nmos, pmos +from glayout.pdk.mappedpdk import MappedPDK +from typing import Optional, Union +from gdsfactory.routing.route_quad import route_quad +from gdsfactory.routing.route_sharp import route_sharp +from glayout.routing.c_route import c_route +from glayout.pdk.util.comp_utils import movex, movey, evaluate_bbox, align_comp_to_port +from glayout.pdk.util.port_utils import rename_ports_by_orientation, rename_ports_by_list, add_ports_perimeter, print_ports, get_orientation, set_port_orientation +from glayout.via_gen import via_stack +from glayout.pdk.util.snap_to_grid import component_snap_to_grid + + +@cell +def diff_pair( + pdk: MappedPDK, + width: float = 3, + fingers: int = 4, + length: Optional[float] = None, + n_or_p_fet: bool = True, + plus_minus_seperation: float = 0, + rmult: int = 1, + dummy: Union[bool, tuple[bool, bool]] = True +) -> Component: + """create a diffpair with 2 transistors placed in two rows with common centroid place. Sources are shorted + width = width of the transistors + fingers = number of fingers in the transistors (must be 2 or more) + length = length of the transistors, None or 0 means use min length + short_source = if true connects source of both transistors + n_or_p_fet = if true the diffpair is made of nfets else it is made of pfets + """ + # TODO: error checking + pdk.activate() + diffpair = Component() + # create transistors + well = None + if isinstance(dummy, bool): + dummy = (dummy, dummy) + if n_or_p_fet: + fetL = nmos(pdk, width=width, fingers=fingers,length=length,multipliers=1,with_tie=False,with_dummy=(dummy[0], False),with_dnwell=False,with_substrate_tap=False,rmult=rmult) + fetR = nmos(pdk, width=width, fingers=fingers,length=length,multipliers=1,with_tie=False,with_dummy=(False,dummy[1]),with_dnwell=False,with_substrate_tap=False,rmult=rmult) + min_spacing_x = pdk.get_grule("n+s/d")["min_separation"] - 2*(fetL.xmax - fetL.ports["multiplier_0_plusdoped_E"].center[0]) + well = "pwell" + else: + fetL = pmos(pdk, width=width, fingers=fingers,length=length,multipliers=1,with_tie=False,with_dummy=(dummy[0], False),dnwell=False,with_substrate_tap=False,rmult=rmult) + fetR = pmos(pdk, width=width, fingers=fingers,length=length,multipliers=1,with_tie=False,with_dummy=(False,dummy[1]),dnwell=False,with_substrate_tap=False,rmult=rmult) + min_spacing_x = pdk.get_grule("p+s/d")["min_separation"] - 2*(fetL.xmax - fetL.ports["multiplier_0_plusdoped_E"].center[0]) + well = "nwell" + # place transistors + viam2m3 = via_stack(pdk,"met2","met3",centered=True) + metal_min_dim = max(pdk.get_grule("met2")["min_width"],pdk.get_grule("met3")["min_width"]) + metal_space = max(pdk.get_grule("met2")["min_separation"],pdk.get_grule("met3")["min_separation"],metal_min_dim) + gate_route_os = evaluate_bbox(viam2m3)[0] - fetL.ports["multiplier_0_gate_W"].width + metal_space + min_spacing_y = metal_space + 2*gate_route_os + min_spacing_y = min_spacing_y - 2*abs(fetL.ports["well_S"].center[1] - fetL.ports["multiplier_0_gate_S"].center[1]) + # TODO: fix spacing where you see +-0.5 + a_topl = (diffpair << fetL).movey(fetL.ymax+min_spacing_y/2+0.5).movex(0-fetL.xmax-min_spacing_x/2) + b_topr = (diffpair << fetR).movey(fetR.ymax+min_spacing_y/2+0.5).movex(fetL.xmax+min_spacing_x/2) + a_botr = (diffpair << fetR) + a_botr.mirror_y().movey(0-0.5-fetL.ymax-min_spacing_y/2).movex(fetL.xmax+min_spacing_x/2) + b_botl = (diffpair << fetL) + b_botl.mirror_y().movey(0-0.5-fetR.ymax-min_spacing_y/2).movex(0-fetL.xmax-min_spacing_x/2) + # route sources (short sources) + diffpair << route_quad(a_topl.ports["multiplier_0_source_E"], b_topr.ports["multiplier_0_source_W"], layer=pdk.get_glayer("met2")) + diffpair << route_quad(b_botl.ports["multiplier_0_source_E"], a_botr.ports["multiplier_0_source_W"], layer=pdk.get_glayer("met2")) + sextension = b_topr.ports["well_E"].center[0] - b_topr.ports["multiplier_0_source_E"].center[0] + source_routeE = diffpair << c_route(pdk, b_topr.ports["multiplier_0_source_E"], a_botr.ports["multiplier_0_source_E"],extension=sextension) + source_routeW = diffpair << c_route(pdk, a_topl.ports["multiplier_0_source_W"], b_botl.ports["multiplier_0_source_W"],extension=sextension) + # route drains + # place via at the drain + drain_br_via = diffpair << viam2m3 + drain_bl_via = diffpair << viam2m3 + drain_br_via.move(a_botr.ports["multiplier_0_drain_N"].center).movey(viam2m3.ymin) + drain_bl_via.move(b_botl.ports["multiplier_0_drain_N"].center).movey(viam2m3.ymin) + drain_br_viatm = diffpair << viam2m3 + drain_bl_viatm = diffpair << viam2m3 + drain_br_viatm.move(a_botr.ports["multiplier_0_drain_N"].center).movey(viam2m3.ymin) + drain_bl_viatm.move(b_botl.ports["multiplier_0_drain_N"].center).movey(-1.5 * evaluate_bbox(viam2m3)[1] - metal_space) + # create route to drain via + width_drain_route = b_topr.ports["multiplier_0_drain_E"].width + dextension = source_routeE.xmax - b_topr.ports["multiplier_0_drain_E"].center[0] + metal_space + bottom_extension = viam2m3.ymax + width_drain_route/2 + 2*metal_space + drain_br_viatm.movey(0-bottom_extension - metal_space - width_drain_route/2 - viam2m3.ymax) + diffpair << route_quad(drain_br_viatm.ports["top_met_N"], drain_br_via.ports["top_met_S"], layer=pdk.get_glayer("met3")) + diffpair << route_quad(drain_bl_viatm.ports["top_met_N"], drain_bl_via.ports["top_met_S"], layer=pdk.get_glayer("met3")) + floating_port_drain_bottom_L = set_port_orientation(movey(drain_bl_via.ports["bottom_met_W"],0-bottom_extension), get_orientation("E")) + floating_port_drain_bottom_R = set_port_orientation(movey(drain_br_via.ports["bottom_met_E"],0-bottom_extension - metal_space - width_drain_route), get_orientation("W")) + drain_routeTR_BL = diffpair << c_route(pdk, floating_port_drain_bottom_L, b_topr.ports["multiplier_0_drain_E"],extension=dextension, width1=width_drain_route,width2=width_drain_route) + drain_routeTL_BR = diffpair << c_route(pdk, floating_port_drain_bottom_R, a_topl.ports["multiplier_0_drain_W"],extension=dextension, width1=width_drain_route,width2=width_drain_route) + # cross gate route top with c_route. bar_minus ABOVE bar_plus + get_left_extension = lambda bar, a_topl=a_topl, diffpair=diffpair, pdk=pdk : (abs(diffpair.xmin-min(a_topl.ports["multiplier_0_gate_W"].center[0],bar.ports["e1"].center[0])) + pdk.get_grule("met2")["min_separation"]) + get_right_extension = lambda bar, b_topr=b_topr, diffpair=diffpair, pdk=pdk : (abs(diffpair.xmax-max(b_topr.ports["multiplier_0_gate_E"].center[0],bar.ports["e3"].center[0])) + pdk.get_grule("met2")["min_separation"]) + # lay bar plus and PLUSgate_routeW + bar_comp = rectangle(centered=True,size=(abs(b_topr.xmax-a_topl.xmin), b_topr.ports["multiplier_0_gate_E"].width),layer=pdk.get_glayer("met2")) + bar_plus = (diffpair << bar_comp).movey(diffpair.ymax + bar_comp.ymax + pdk.get_grule("met2")["min_separation"]) + PLUSgate_routeW = diffpair << c_route(pdk, a_topl.ports["multiplier_0_gate_W"], bar_plus.ports["e1"], extension=get_left_extension(bar_plus)) + #lay bar minus and MINUSgate_routeE + plus_minus_seperation = max(pdk.get_grule("met2")["min_separation"], plus_minus_seperation) + bar_minus = (diffpair << bar_comp).movey(diffpair.ymax +bar_comp.ymax + plus_minus_seperation) + MINUSgate_routeE = diffpair << c_route(pdk, b_topr.ports["multiplier_0_gate_E"], bar_minus.ports["e3"], extension=get_right_extension(bar_minus)) + # lay MINUSgate_routeW and PLUSgate_routeE + MINUSgate_routeW = diffpair << c_route(pdk, set_port_orientation(b_botl.ports["multiplier_0_gate_E"],"W"), bar_minus.ports["e1"], extension=get_left_extension(bar_minus)) + PLUSgate_routeE = diffpair << c_route(pdk, set_port_orientation(a_botr.ports["multiplier_0_gate_W"],"E"), bar_plus.ports["e3"], extension=get_right_extension(bar_plus)) + # correct pwell place, add ports, flatten, and return + diffpair.add_ports(a_topl.get_ports_list(),prefix="tl_") + diffpair.add_ports(b_topr.get_ports_list(),prefix="tr_") + diffpair.add_ports(b_botl.get_ports_list(),prefix="bl_") + diffpair.add_ports(a_botr.get_ports_list(),prefix="br_") + diffpair.add_ports(source_routeE.get_ports_list(),prefix="source_routeE_") + diffpair.add_ports(source_routeW.get_ports_list(),prefix="source_routeW_") + diffpair.add_ports(drain_routeTR_BL.get_ports_list(),prefix="drain_routeTR_BL_") + diffpair.add_ports(drain_routeTL_BR.get_ports_list(),prefix="drain_routeTL_BR_") + diffpair.add_ports(MINUSgate_routeW.get_ports_list(),prefix="MINUSgateroute_W_") + diffpair.add_ports(MINUSgate_routeE.get_ports_list(),prefix="MINUSgateroute_E_") + diffpair.add_ports(PLUSgate_routeW.get_ports_list(),prefix="PLUSgateroute_W_") + diffpair.add_ports(PLUSgate_routeE.get_ports_list(),prefix="PLUSgateroute_E_") + diffpair.add_padding(layers=(pdk.get_glayer(well),), default=0) + return component_snap_to_grid(rename_ports_by_orientation(diffpair)) + + +if __name__ == "__main__": + from .pdk.util.standard_main import pdk + mycomp = diff_pair(pdk,length=1,width=6,fingers=4,rmult=2) + mycomp.show() + print_ports(mycomp) + diff --git a/openfasoc/generators/gdsfactory-gen/glayout/docs/PortTreeExample.png b/openfasoc/generators/gdsfactory-gen/glayout/docs/PortTreeExample.png new file mode 100644 index 000000000..865c77a3a Binary files /dev/null and b/openfasoc/generators/gdsfactory-gen/glayout/docs/PortTreeExample.png differ diff --git a/openfasoc/generators/gdsfactory-gen/glayout/docs/straight_route_def_beh.png b/openfasoc/generators/gdsfactory-gen/glayout/docs/straight_route_def_beh.png new file mode 100644 index 000000000..0ba6bf35f Binary files /dev/null and b/openfasoc/generators/gdsfactory-gen/glayout/docs/straight_route_def_beh.png differ diff --git a/openfasoc/generators/gdsfactory-gen/glayout/docs/txt_port_trees/L_route_v1_tree.txt b/openfasoc/generators/gdsfactory-gen/glayout/docs/txt_port_trees/L_route_v1_tree.txt new file mode 100644 index 000000000..85bb4687b --- /dev/null +++ b/openfasoc/generators/gdsfactory-gen/glayout/docs/txt_port_trees/L_route_v1_tree.txt @@ -0,0 +1,19 @@ + ┌[bottom] + ┌[array]─[row0]─[col0]┤ + │ └[top] + │ + │ ┌[W] + │ │ + │ ├[N] + ├[bottom]─[lay]┤ + │ ├[E] +-[L_route]┤ │ + │ └[S] + │ + │ ┌[W] + │ │ + │ ├[N] + └[top]─[met]┤ + ├[E] + │ + └[S] \ No newline at end of file diff --git a/openfasoc/generators/gdsfactory-gen/glayout/docs/txt_port_trees/c_route_v1_tree.txt b/openfasoc/generators/gdsfactory-gen/glayout/docs/txt_port_trees/c_route_v1_tree.txt new file mode 100644 index 000000000..3a8676996 --- /dev/null +++ b/openfasoc/generators/gdsfactory-gen/glayout/docs/txt_port_trees/c_route_v1_tree.txt @@ -0,0 +1,3 @@ + ┌[E] +-[c_route]─[con]┤ + └[W] \ No newline at end of file diff --git a/openfasoc/generators/gdsfactory-gen/glayout/docs/txt_port_trees/diff_pair_v1_tree.txt b/openfasoc/generators/gdsfactory-gen/glayout/docs/txt_port_trees/diff_pair_v1_tree.txt new file mode 100644 index 000000000..461a75168 --- /dev/null +++ b/openfasoc/generators/gdsfactory-gen/glayout/docs/txt_port_trees/diff_pair_v1_tree.txt @@ -0,0 +1,111 @@ + ┌[drain] + │ + ├[source] + │ + ├[row0] + │ + ┌[multiplier]─[0]┼[leftsd] + │ │ + │ ├[plusdoped] + ┌[bl]┤ │ + │ │ ├[diff] + │ │ │ + │ │ └[gate] + │ │ + │ │ ┌[W] + │ │ │ + │ └[well]┼[S] + │ │ + │ └[E] + │ + │ ┌[drain] + │ │ + │ ├[source] + │ │ + │ ├[row0] + │ │ + │ ┌[multiplier]─[0]┼[leftsd] + │ │ │ + │ │ ├[plusdoped] + ├[br]┤ │ + │ │ ├[diff] + │ │ │ + │ │ └[gate] + │ │ + │ │ ┌[W] + │ │ │ + │ └[well]┼[S] + │ │ + │ └[E] + │ + │ ┌[gate] + │ │ + │ ├[row0] + │ │ + │ ├[leftsd] + │ │ + │ ┌[multiplier]─[0]┼[plusdoped] + │ │ │ + │ │ ├[diff] + │ │ │ + ├[tl]┤ ├[source] + │ │ │ + │ │ └[drain] + │ │ + │ │ ┌[W] +-[diff_pair]┤ │ │ + │ │ ├[N] + │ └[well]┤ + │ ├[E] + │ │ + │ └[S] + │ + │ ┌[gate] + │ │ + │ ├[row0] + │ │ + │ ├[leftsd] + │ │ + │ ┌[multiplier]─[0]┼[plusdoped] + │ │ │ + │ │ ├[diff] + │ │ │ + ├[tr]┤ ├[source] + │ │ │ + │ │ └[drain] + │ │ + │ │ ┌[W] + │ │ │ + │ │ ├[N] + │ └[well]┤ + │ ├[E] + │ │ + │ └[S] + │ + │ ┌[N] + │ ┌[W]─[con]┤ + │ │ └[S] + ├[MINUSgateroute]┤ + │ │ ┌[N] + │ └[E]─[con]┤ + │ └[S] + │ + │ ┌[N] + │ ┌[W]─[con]┤ + │ │ └[S] + ├[PLUSgateroute]┤ + │ │ ┌[N] + │ └[E]─[con]┤ + │ └[S] + │ + │ ┌[routeTL]─[BR]─[con] + ├[drain]┤ + │ └[routeTR]─[BL]─[con] + │ + │ ┌[N] + │ ┌[routeW]─[con]┤ + │ │ └[S] + └[source]┤ + │ ┌[N] + └[routeE]─[con]┤ + └[S] \ No newline at end of file diff --git a/openfasoc/generators/gdsfactory-gen/glayout/docs/txt_port_trees/mimcap_array_v1_tree.txt b/openfasoc/generators/gdsfactory-gen/glayout/docs/txt_port_trees/mimcap_array_v1_tree.txt new file mode 100644 index 000000000..e83209c15 --- /dev/null +++ b/openfasoc/generators/gdsfactory-gen/glayout/docs/txt_port_trees/mimcap_array_v1_tree.txt @@ -0,0 +1,83 @@ + ┌[row0] + │ + ├[row1] + │ + ┌[array]┼[row2] + │ │ + ┌[col0]┤ ├[row3] + │ │ │ + │ │ └[row4] + │ │ + │ ├[bottom]─[met] + │ │ + │ └[top]─[met] + │ + │ ┌[row0] + │ │ + │ ├[row1] + │ │ + │ ┌[array]┼[row2] + │ │ │ + ┌[row0]┼[col1]┤ ├[row3] + │ │ │ │ + │ │ │ └[row4] + │ │ │ + │ │ ├[bottom]─[met] + │ │ │ + │ │ └[top]─[met] + │ │ + │ │ ┌[row0] + │ │ │ + │ │ ├[row1] + │ │ │ + │ │ ┌[array]┼[row2] + │ │ │ │ + │ └[col2]┤ ├[row3] + │ │ │ + │ │ └[row4] + │ │ + │ ├[bottom]─[met] + │ │ + │ └[top]─[met] +-[mimcap_array]┤ + │ ┌[row0] + │ │ + │ ├[row1] + │ │ + │ ┌[array]┼[row2] + │ │ │ + │ ┌[col0]┤ ├[row3] + │ │ │ │ + │ │ │ └[row4] + │ │ │ + │ │ ├[bottom]─[met] + │ │ │ + │ │ └[top]─[met] + │ │ + │ │ ┌[row0] + │ │ │ + │ │ ├[row1] + │ │ │ + │ │ ┌[array]┼[row2] + │ │ │ │ + └[row1]┼[col1]┤ ├[row3] + │ │ │ + │ │ └[row4] + │ │ + │ ├[bottom]─[met] + │ │ + │ └[top]─[met] + │ + │ ┌[row0] + │ │ + │ ├[row1] + │ │ + │ ┌[array]┼[row2] + │ │ │ + └[col2]┤ ├[row3] + │ │ + │ └[row4] + │ + ├[bottom]─[met] + │ + └[top]─[met] \ No newline at end of file diff --git a/openfasoc/generators/gdsfactory-gen/glayout/docs/txt_port_trees/mimcap_v1_tree.txt b/openfasoc/generators/gdsfactory-gen/glayout/docs/txt_port_trees/mimcap_v1_tree.txt new file mode 100644 index 000000000..e24668aa9 --- /dev/null +++ b/openfasoc/generators/gdsfactory-gen/glayout/docs/txt_port_trees/mimcap_v1_tree.txt @@ -0,0 +1,115 @@ + ┌[W] + │ + ├[N] + ┌[bottom]─[met]┤ + │ ├[E] + │ │ + │ └[S] + │ + │ ┌[bottom] + │ ┌[col0]┤ + │ │ └[top] + │ │ + │ │ ┌[bottom] + │ ├[col1]┤ + │ │ └[top] + │ │ + │ │ ┌[bottom] + │ ┌[row0]┼[col2]┤ + │ │ │ └[top] + │ │ │ + │ │ │ ┌[bottom] + │ │ ├[col3]┤ + │ │ │ └[top] + │ │ │ + │ │ │ ┌[bottom] + │ │ └[col4]┤ + │ │ └[top] + │ │ + │ │ ┌[bottom] + │ │ ┌[col0]┤ + │ │ │ └[top] + │ │ │ + │ │ │ ┌[bottom] + │ │ ├[col1]┤ + │ │ │ └[top] + │ │ │ + │ │ │ ┌[bottom] + │ ├[row1]┼[col2]┤ + │ │ │ └[top] + │ │ │ + │ │ │ ┌[bottom] + │ │ ├[col3]┤ + │ │ │ └[top] + │ │ │ + │ │ │ ┌[bottom] + │ │ └[col4]┤ + │ │ └[top] + │ │ + │ │ ┌[bottom] + │ │ ┌[col0]┤ + │ │ │ └[top] + │ │ │ + │ │ │ ┌[bottom] + │ │ ├[col1]┤ + │ │ │ └[top] + │ │ │ + │ │ │ ┌[bottom] +-[mimcap]┼[array]┼[row2]┼[col2]┤ + │ │ │ └[top] + │ │ │ + │ │ │ ┌[bottom] + │ │ ├[col3]┤ + │ │ │ └[top] + │ │ │ + │ │ │ ┌[bottom] + │ │ └[col4]┤ + │ │ └[top] + │ │ + │ │ ┌[bottom] + │ │ ┌[col0]┤ + │ │ │ └[top] + │ │ │ + │ │ │ ┌[bottom] + │ │ ├[col1]┤ + │ │ │ └[top] + │ │ │ + │ │ │ ┌[bottom] + │ ├[row3]┼[col2]┤ + │ │ │ └[top] + │ │ │ + │ │ │ ┌[bottom] + │ │ ├[col3]┤ + │ │ │ └[top] + │ │ │ + │ │ │ ┌[bottom] + │ │ └[col4]┤ + │ │ └[top] + │ │ + │ │ ┌[bottom] + │ │ ┌[col0]┤ + │ │ │ └[top] + │ │ │ + │ │ │ ┌[bottom] + │ │ ├[col1]┤ + │ │ │ └[top] + │ │ │ + │ │ │ ┌[bottom] + │ └[row4]┼[col2]┤ + │ │ └[top] + │ │ + │ │ ┌[bottom] + │ ├[col3]┤ + │ │ └[top] + │ │ + │ │ ┌[bottom] + │ └[col4]┤ + │ └[top] + │ + │ ┌[W] + │ │ + │ ├[N] + └[top]─[met]┤ + ├[E] + │ + └[S] \ No newline at end of file diff --git a/openfasoc/generators/gdsfactory-gen/glayout/docs/txt_port_trees/multiplier_v1_tree.txt b/openfasoc/generators/gdsfactory-gen/glayout/docs/txt_port_trees/multiplier_v1_tree.txt new file mode 100644 index 000000000..d7f5194f2 --- /dev/null +++ b/openfasoc/generators/gdsfactory-gen/glayout/docs/txt_port_trees/multiplier_v1_tree.txt @@ -0,0 +1,73 @@ + ┌[W] + │ + ├[N] + ┌[gate]┤ + │ ├[E] + │ │ + │ └[S] + │ + │ ┌[array] + │ ┌[rightsd]┤ + │ │ └[top] + │ │ + │ │ ┌[W] + ├[row0]─[col0]┤ │ + │ │ ├[N] + │ └[gate]┤ + │ ├[E] + │ │ + │ └[S] + │ + │ ┌[row0]─[col0] + │ │ + │ ├[row1]─[col0] + │ │ + │ ├[row2]─[col0] + │ │ + │ ┌[array]┼[row3]─[col0] + │ │ │ + │ │ ├[row4]─[col0] + │ │ │ + ├[leftsd]┤ ├[row5]─[col0] + │ │ │ + │ │ └[row6]─[col0] + │ │ + │ │ ┌[W] + │ │ │ +-[multiplier]┤ │ ├[N] + │ └[top]─[met]┤ + │ ├[E] + │ │ + │ └[S] + │ + │ ┌[W] + │ │ + │ ├[N] + ├[plusdoped]┤ + │ ├[E] + │ │ + │ └[S] + │ + │ ┌[W] + │ │ + │ ├[N] + ├[diff]┤ + │ ├[E] + │ │ + │ └[S] + │ + │ ┌[W] + │ │ + │ ├[N] + ├[source]┤ + │ ├[E] + │ │ + │ └[S] + │ + │ ┌[W] + │ │ + │ ├[N] + └[drain]┤ + ├[E] + │ + └[S] \ No newline at end of file diff --git a/openfasoc/generators/gdsfactory-gen/glayout/docs/txt_port_trees/nmos_v1_tree.txt b/openfasoc/generators/gdsfactory-gen/glayout/docs/txt_port_trees/nmos_v1_tree.txt new file mode 100644 index 000000000..702ff14c8 --- /dev/null +++ b/openfasoc/generators/gdsfactory-gen/glayout/docs/txt_port_trees/nmos_v1_tree.txt @@ -0,0 +1,469 @@ + ┌[W] + │ + ├[N] + │ + ┌[gate]┼[con] + │ │ + │ ├[E] + │ │ + │ └[S] + │ + │ ┌[col0] + ├[row0]┤ + │ └[col1] + │ + │ ┌[array] + ├[leftsd]┤ + │ └[top] + │ + │ ┌[W] + │ │ + │ ├[N] + ├[plusdoped]┤ + │ ├[E] + │ │ + │ └[S] + │ + ┌[0]┤ ┌[W] + │ │ │ + │ │ ├[N] + │ ├[diff]┤ + │ │ ├[E] + │ │ │ + │ │ └[S] + │ │ + │ │ ┌[W] + │ │ │ + │ │ ├[con] + │ │ │ + │ ├[source]┼[N] + │ │ │ + │ │ ├[E] + │ │ │ + │ │ └[S] + │ │ + │ │ ┌[W] + │ │ │ + │ │ ├[con] + │ │ │ + │ └[drain]┼[N] + │ │ + ┌[multiplier]┤ ├[E] + │ │ │ + │ │ └[S] + │ │ + │ │ ┌[W] + │ │ │ + │ │ ├[N] + │ │ ┌[gate]┤ + │ │ │ ├[E] + │ │ │ │ + │ │ │ └[S] + │ │ │ + │ │ │ ┌[col0] + │ │ ├[row0]┤ + │ │ │ └[col1] + │ │ │ + │ │ │ ┌[array] + │ │ ├[leftsd]┤ + │ │ │ └[top] + │ │ │ + │ │ │ ┌[W] + │ │ │ │ + │ │ │ ├[N] + │ │ ├[plusdoped]┤ + │ │ │ ├[E] + │ │ │ │ + │ │ │ └[S] + │ └[1]┤ + │ │ ┌[W] + │ │ │ + │ │ ├[N] + │ ├[diff]┤ + │ │ ├[E] + │ │ │ + │ │ └[S] + │ │ + │ │ ┌[W] + │ │ │ + │ │ ├[N] + │ ├[source]┤ + │ │ ├[E] + │ │ │ + │ │ └[S] + │ │ + │ │ ┌[W] + │ │ │ + │ │ ├[N] + │ └[drain]┤ + │ ├[E] + │ │ + │ └[S] + │ + │ ┌[array]─[row0] + │ │ + │ ┌[S]┼[bottom]─[lay] + │ │ │ + │ │ └[top]─[met] + │ │ + │ │ ┌[array]─[row0] + │ │ │ + │ ├[bl]┼[bottom]─[lay] + │ │ │ + │ │ └[top]─[met] + │ │ + │ │ ┌[array]─[row0] + │ │ │ + │ ├[br]┼[bottom]─[lay] + │ │ │ + │ │ └[top]─[met] + │ │ + │ │ ┌[row0] + │ │ │ + │ │ ├[row1] + │ │ │ + │ │ ├[row2] + │ │ │ + │ │ ├[row3] + │ │ │ + │ │ ├[row4] + │ │ │ + │ │ ├[row5] + │ │ │ + │ │ ├[row6] + │ │ │ + │ │ ├[row7] + │ │ │ + │ │ ├[row8] + │ │ │ + │ │ ├[row9] + │ │ │ + │ │ ├[row10] + │ │ │ + │ │ ├[row11] + │ │ │ + │ │ ├[row12] + │ │ │ + │ │ ├[row13] + │ │ │ + │ │ ├[row14] + │ │ │ + │ │ ┌[array]┼[row15] + │ │ │ │ + │ ├[E]┤ ├[row16] + │ │ │ │ + │ │ │ ├[row17] + │ │ │ │ + │ │ │ ├[row18] + │ │ │ │ + │ │ │ ├[row19] + │ │ │ │ + │ │ │ ├[row20] + │ │ │ │ + │ │ │ ├[row21] + │ │ │ │ + │ │ │ ├[row22] + │ │ │ │ + │ │ │ ├[row23] + │ │ │ │ + │ │ │ ├[row24] + │ │ │ │ + │ │ │ ├[row25] + │ │ │ │ + │ │ │ ├[row26] + │ │ │ │ + │ │ │ ├[row27] + │ │ │ │ + │ │ │ ├[row28] + │ │ │ │ + │ │ │ ├[row29] + │ │ │ │ + │ │ │ └[row30] + │ │ │ + │ │ ├[bottom]─[lay] + │ │ │ + │ │ └[top]─[met] + ├[tie]┤ + │ │ ┌[row0] + │ │ │ + │ │ ├[row1] + │ │ │ + │ │ ├[row2] + │ │ │ + │ │ ├[row3] + │ │ │ + │ │ ├[row4] + │ │ │ + │ │ ├[row5] + │ │ │ + │ │ ├[row6] + │ │ │ + │ │ ├[row7] + │ │ │ + │ │ ├[row8] + │ │ │ + │ │ ├[row9] + │ │ │ + │ │ ├[row10] + │ │ │ + │ │ ├[row11] + │ │ │ + │ │ ├[row12] + │ │ │ + │ │ ├[row13] + │ │ │ + │ │ ├[row14] + │ │ │ + │ │ ┌[array]┼[row15] + │ │ │ │ + │ ├[W]┤ ├[row16] + │ │ │ │ + │ │ │ ├[row17] + │ │ │ │ + │ │ │ ├[row18] + │ │ │ │ + │ │ │ ├[row19] + │ │ │ │ + │ │ │ ├[row20] + │ │ │ │ + │ │ │ ├[row21] + │ │ │ │ + │ │ │ ├[row22] + │ │ │ │ + │ │ │ ├[row23] + │ │ │ │ +-[nmos]┤ │ │ ├[row24] + │ │ │ │ + │ │ │ ├[row25] + │ │ │ │ + │ │ │ ├[row26] + │ │ │ │ + │ │ │ ├[row27] + │ │ │ │ + │ │ │ ├[row28] + │ │ │ │ + │ │ │ ├[row29] + │ │ │ │ + │ │ │ └[row30] + │ │ │ + │ │ ├[bottom]─[lay] + │ │ │ + │ │ └[top]─[met] + │ │ + │ │ ┌[array]─[row0] + │ │ │ + │ ├[N]┼[bottom]─[lay] + │ │ │ + │ │ └[top]─[met] + │ │ + │ │ ┌[array]─[row0] + │ │ │ + │ ├[tl]┼[bottom]─[lay] + │ │ │ + │ │ └[top]─[met] + │ │ + │ │ ┌[array]─[row0] + │ │ │ + │ └[tr]┼[bottom]─[lay] + │ │ + │ └[top]─[met] + │ + │ ┌[W] + │ │ + │ ├[N] + ├[well]┤ + │ ├[E] + │ │ + │ └[S] + │ + │ ┌[array]─[row0] + │ │ + │ ┌[S]┼[bottom]─[lay] + │ │ │ + │ │ └[top]─[met] + │ │ + │ │ ┌[array]─[row0] + │ │ │ + │ ├[bl]┼[bottom]─[lay] + │ │ │ + │ │ └[top]─[met] + │ │ + │ │ ┌[array]─[row0] + │ │ │ + │ ├[br]┼[bottom]─[lay] + │ │ │ + │ │ └[top]─[met] + │ │ + │ │ ┌[row0] + │ │ │ + │ │ ├[row1] + │ │ │ + │ │ ├[row2] + │ │ │ + │ │ ├[row3] + │ │ │ + │ │ ├[row4] + │ │ │ + │ │ ├[row5] + │ │ │ + │ │ ├[row6] + │ │ │ + │ │ ├[row7] + │ │ │ + │ │ ├[row8] + │ │ │ + │ │ ├[row9] + │ │ │ + │ │ ├[row10] + │ │ │ + │ │ ├[row11] + │ │ │ + │ │ ├[row12] + │ │ │ + │ │ ├[row13] + │ │ │ + │ │ ├[row14] + │ │ │ + │ │ ├[row15] + │ │ │ + │ │ ├[row16] + │ │ │ + │ │ ├[row17] + │ │ │ + │ │ ┌[array]┼[row18] + │ │ │ │ + │ ├[E]┤ ├[row19] + │ │ │ │ + │ │ │ ├[row20] + │ │ │ │ + │ │ │ ├[row21] + │ │ │ │ + │ │ │ ├[row22] + │ │ │ │ + │ │ │ ├[row23] + │ │ │ │ + │ │ │ ├[row24] + │ │ │ │ + │ │ │ ├[row25] + │ │ │ │ + │ │ │ ├[row26] + │ │ │ │ + │ │ │ ├[row27] + │ │ │ │ + │ │ │ ├[row28] + │ │ │ │ + │ │ │ ├[row29] + │ │ │ │ + │ │ │ ├[row30] + │ │ │ │ + │ │ │ ├[row31] + │ │ │ │ + │ │ │ ├[row32] + │ │ │ │ + │ │ │ ├[row33] + │ │ │ │ + │ │ │ ├[row34] + │ │ │ │ + │ │ │ ├[row35] + │ │ │ │ + │ │ │ └[row36] + │ │ │ + │ │ ├[bottom]─[lay] + │ │ │ + │ │ └[top]─[met] + └[guardring]┤ + │ ┌[row0] + │ │ + │ ├[row1] + │ │ + │ ├[row2] + │ │ + │ ├[row3] + │ │ + │ ├[row4] + │ │ + │ ├[row5] + │ │ + │ ├[row6] + │ │ + │ ├[row7] + │ │ + │ ├[row8] + │ │ + │ ├[row9] + │ │ + │ ├[row10] + │ │ + │ ├[row11] + │ │ + │ ├[row12] + │ │ + │ ├[row13] + │ │ + │ ├[row14] + │ │ + │ ├[row15] + │ │ + │ ├[row16] + │ │ + │ ├[row17] + │ │ + │ ┌[array]┼[row18] + │ │ │ + ├[W]┤ ├[row19] + │ │ │ + │ │ ├[row20] + │ │ │ + │ │ ├[row21] + │ │ │ + │ │ ├[row22] + │ │ │ + │ │ ├[row23] + │ │ │ + │ │ ├[row24] + │ │ │ + │ │ ├[row25] + │ │ │ + │ │ ├[row26] + │ │ │ + │ │ ├[row27] + │ │ │ + │ │ ├[row28] + │ │ │ + │ │ ├[row29] + │ │ │ + │ │ ├[row30] + │ │ │ + │ │ ├[row31] + │ │ │ + │ │ ├[row32] + │ │ │ + │ │ ├[row33] + │ │ │ + │ │ ├[row34] + │ │ │ + │ │ ├[row35] + │ │ │ + │ │ └[row36] + │ │ + │ ├[bottom]─[lay] + │ │ + │ └[top]─[met] + │ + │ ┌[array]─[row0] + │ │ + ├[N]┼[bottom]─[lay] + │ │ + │ └[top]─[met] + │ + │ ┌[array]─[row0] + │ │ + ├[tl]┼[bottom]─[lay] + │ │ + │ └[top]─[met] + │ + │ ┌[array]─[row0] + │ │ + └[tr]┼[bottom]─[lay] + │ + └[top]─[met] \ No newline at end of file diff --git a/openfasoc/generators/gdsfactory-gen/glayout/docs/txt_port_trees/opamp_v1_tree.txt b/openfasoc/generators/gdsfactory-gen/glayout/docs/txt_port_trees/opamp_v1_tree.txt new file mode 100644 index 000000000..bf471cedc --- /dev/null +++ b/openfasoc/generators/gdsfactory-gen/glayout/docs/txt_port_trees/opamp_v1_tree.txt @@ -0,0 +1,420 @@ + ┌[W] + │ + ├[N] + ┌[diffpairibias]┤ + │ ├[E] + │ │ + │ └[S] + │ + │ ┌[W] + │ │ + │ ├[N] + ├[commonsourceibias]┤ + │ ├[E] + │ │ + │ └[S] + │ + │ ┌[W] + │ │ + │ ├[N] + ├[gnd]┤ + │ ├[E] + │ │ + │ └[S] + │ + │ ┌[W] + │ │ + │ ├[N] + ├[outputibias]┤ + │ ├[E] + │ │ + │ └[S] + ┌[pin]┤ + │ │ ┌[W] + │ │ │ + │ │ ├[N] + │ ├[output]─[route]┤ + │ │ ├[E] + │ │ │ + │ │ └[S] + │ │ + │ │ ┌[W] + │ │ │ + │ │ ├[N] + │ ├[plus]┤ + │ │ ├[E] + │ │ │ + │ │ └[S] + │ │ + │ │ ┌[W] + │ │ │ + │ │ ├[N] + │ ├[minus]┤ + │ │ ├[E] + │ │ │ + │ │ └[S] + │ │ + │ │ ┌[W] + │ │ │ + │ │ ├[N] + │ └[vdd]┤ + │ ├[E] + │ │ + │ └[S] + │ + │ ┌[W] + ├[gnd]─[route]─[con]┤ + │ └[E] + │ + │ ┌[0] + │ │ + │ ├[1] + │ │ + │ ├[2] + │ │ + │ ┌[B]┼[3] + │ │ │ + │ │ ├[gate] + │ │ │ + │ │ ├[source] + │ │ │ + │ │ └[drain] + │ │ + │ │ ┌[0] + │ │ │ + │ │ ├[1] + │ │ │ + │ │ ├[2] + │ │ │ + │ ┌[ibias]┼[A]┼[3] + │ │ │ │ + │ │ │ ├[gate] + │ │ │ │ + │ │ │ ├[source] + │ │ │ │ + │ │ │ └[drain] + │ │ │ + │ │ │ ┌[W] + │ │ │ │ + │ │ │ ├[N] + │ │ ├[well]┤ + │ │ │ ├[E] + │ │ │ │ + │ │ │ └[S] + │ │ │ + │ │ │ ┌[N] + │ │ ├[purposegndportscon]┤ + │ │ │ └[S] + │ │ │ + │ │ └[S] + │ │ + │ │ ┌[multiplier]─[0] + │ │ │ + │ │ │ ┌[W] + │ ├[bl]┤ │ + ├[diffpair]┤ └[well]┼[N] + │ │ │ + │ │ └[E] + │ │ + │ │ ┌[multiplier]─[0] + │ │ │ + │ │ │ ┌[W] + │ ├[br]┤ │ + │ │ └[well]┼[N] + │ │ │ + │ │ └[E] + │ │ + │ │ ┌[multiplier]─[0] + │ │ │ + │ │ │ ┌[W] + │ │ │ │ + │ ├[tl]┤ ├[N] + │ │ └[well]┤ + │ │ ├[E] + │ │ │ + │ │ └[S] + │ │ + │ │ ┌[multiplier]─[0] + │ │ │ + │ │ │ ┌[W] + │ │ │ │ + │ ├[tr]┤ ├[N] + │ │ └[well]┤ + │ │ ├[E] + │ │ │ + │ │ └[S] + │ │ + │ │ ┌[W]─[con] + │ ├[MINUSgateroute]┤ + │ │ └[E]─[con] + │ │ + │ │ ┌[W]─[con] + │ ├[PLUSgateroute]┤ + │ │ └[E]─[con] + │ │ + │ │ ┌[routeTL]─[BR] + │ ├[drain]┤ + │ │ └[routeTR]─[BL] + │ │ + │ │ ┌[routeW]─[con] + │ └[source]┤ + │ └[routeE]─[con] + │ + │ + │ ┌[output]-[E] + │ │ + │ │ ┌[R] + │ │ ┌[ref]┤ + │ │ │ └[L] + │ ├[cmirror]┤ + │ │ │ ┌[R] + │ │ └[output]┤ + │ │ └[L] + │ │ + │ │ ┌[tie] + ├[commonsource]┤ │ + │ │ ┌[L]┼[multiplier] + │ │ │ │ + │ │ │ └[well] + │ └[Pamp]┤ + │ │ ┌[tie] + │ │ │ + │ └[R]┼[multiplier] + │ │ + │ └[well] + │ + │ ┌[S] + │ │ + │ ├[bl] + │ │ + │ ├[br] + │ │ + │ ├[E] + │ ┌[substratetap]┤ + │ │ ├[W] + │ │ │ + │ │ ├[N] + │ │ │ + │ │ ├[tl] + │ │ │ + │ │ └[tr] + │ │ + │ │ ┌[S] + │ │ │ + │ │ ├[bl] + │ │ │ + │ │ ├[br] + │ │ │ + │ │ ├[E] + │ ├[welltie]┤ + │ │ ├[W] + │ │ │ +-[opamp]┤ │ ├[N] + │ │ │ + │ │ ├[tl] + │ │ │ + │ │ └[tr] + │ │ + │ │ ┌[0] + │ ┌[bias]┤ │ + │ │ │ ├[1] + │ │ │ │ + │ │ │ ├[2] + │ │ │ │ + │ │ ├[B]┼[3] + │ │ │ │ + │ │ │ ├[gate] + │ │ │ │ + │ │ │ ├[source] + │ │ │ │ + │ │ │ └[drain] + │ │ │ + │ │ │ ┌[0] + │ │ │ │ + │ │ │ ├[1] + │ │ │ │ + │ │ │ ├[2] + │ │ │ │ + │ │ ├[A]┼[3] + │ │ │ │ + ├[outputstage]┤ │ ├[gate] + │ │ │ │ + │ │ │ ├[source] + │ │ │ │ + │ │ │ └[drain] + │ │ │ + │ │ │ ┌[W] + │ │ │ │ + │ │ │ ├[N] + │ │ └[well]┤ + │ │ ├[E] + │ │ │ + │ │ └[S] + │ │ + │ │ ┌[S] + │ │ │ + │ │ ├[bl] + │ │ │ + │ │ ├[br] + │ │ │ + │ │ ├[E] + │ │ ┌[guardring]┤ + │ │ │ ├[W] + │ │ │ │ + │ │ │ ├[N] + │ │ │ │ + │ │ │ ├[tl] + │ │ │ │ + │ │ │ └[tr] + │ │ │ + │ │ │ ┌[S] + │ │ │ │ + │ │ │ ├[bl] + │ │ │ │ + │ └[amp]┤ ├[br] + │ │ │ + │ │ ├[E] + │ ├[tie]┤ + │ │ ├[W] + │ │ │ + │ │ ├[N] + │ │ │ + │ │ ├[tl] + │ │ │ + │ │ └[tr] + │ │ + │ ├[multiplier]─[0] + │ │ + │ │ ┌[W] + │ │ │ + │ │ ├[N] + │ └[well]┤ + │ ├[E] + │ │ + │ └[S] + │ + │ ┌[S] + │ │ + │ ├[bl] + │ │ + │ ├[br] + │ │ + │ ├[E] + │ ┌[top]─[ptap]┤ + │ │ ├[W] + │ │ │ + │ │ ├[N] + │ │ │ + │ │ ├[tl] + │ │ │ + │ │ └[tr] + │ │ + │ │ ┌[layer] + │ │ │ + │ │ ┌[bottom]┼[via] + │ ├[minusvia]┤ │ + │ │ │ └[met] + │ │ │ + │ │ └[top]─[met] + │ │ + │ │ ┌[gate] + │ │ │ + │ │ ├[row0] + │ │ │ + │ │ ├[leftsd] + │ │ │ + │ │ ┌[L]┼[plusdoped] + │ │ │ │ + │ │ │ ├[diff] + │ │ │ │ + │ │ │ ├[source] + │ │ │ │ + │ │ │ └[drain] + │ ├[pbottomAB]┤ + │ │ │ ┌[gate] + │ │ │ │ + │ │ │ ├[row0] + │ │ │ │ + │ │ │ ├[leftsd] + │ │ │ │ + ├[pcomps]┤ └[R]┼[plusdoped] + │ │ │ + │ │ ├[diff] + │ │ │ + │ │ ├[source] + │ │ │ + │ │ └[drain] + │ │ + │ │ ┌[layer] + │ │ │ + │ │ ┌[bottom]┼[via] + │ ├[2L2Rsrcvia]┤ │ + │ │ │ └[met] + │ │ │ + │ │ └[top]─[met] + │ │ + │ │ ┌[gate] + │ │ │ + │ │ ├[row0] + │ │ │ + │ │ ├[leftsd] + │ │ │ + │ │ ┌[L]┼[plusdoped] + │ │ │ │ + │ │ │ ├[diff] + │ │ │ │ + │ │ │ ├[source] + │ │ │ │ + │ │ │ └[drain] + │ ├[ptopAB]┤ + │ │ │ ┌[gate] + │ │ │ │ + │ │ │ ├[row0] + │ │ │ │ + │ │ │ ├[leftsd] + │ │ │ │ + │ │ └[R]┼[plusdoped] + │ │ │ + │ │ ├[diff] + │ │ │ + │ │ ├[source] + │ │ │ + │ │ └[drain] + │ │ + │ └[mimcap]─[connection]─[con] + │ + │ ┌[array] + │ │ + │ ┌[col0]┼[bottom] + │ │ │ + │ │ └[top] + │ ┌[row0]┤ + │ │ │ ┌[array] + │ │ │ │ + │ │ └[col1]┼[bottom] + │ │ │ + │ │ └[top] + │ │ + │ │ ┌[array] + │ │ │ + │ │ ┌[col0]┼[bottom] + │ │ │ │ + │ │ │ └[top] + └[mimcap]┼[row1]┤ + │ │ ┌[array] + │ │ │ + │ └[col1]┼[bottom] + │ │ + │ └[top] + │ + │ ┌[array] + │ │ + │ ┌[col0]┼[bottom] + │ │ │ + │ │ └[top] + └[row2]┤ + │ ┌[array] + │ │ + └[col1]┼[bottom] + │ + └[top] diff --git a/openfasoc/generators/gdsfactory-gen/glayout/docs/txt_port_trees/pmos_v1_tree.txt b/openfasoc/generators/gdsfactory-gen/glayout/docs/txt_port_trees/pmos_v1_tree.txt new file mode 100644 index 000000000..b709d3e44 --- /dev/null +++ b/openfasoc/generators/gdsfactory-gen/glayout/docs/txt_port_trees/pmos_v1_tree.txt @@ -0,0 +1,277 @@ + ┌[W] + │ + ├[N] + │ + ┌[gate]┼[con] + │ │ + │ ├[E] + │ │ + │ └[S] + │ + │ ┌[col0] + ├[row0]┤ + │ └[col1] + │ + │ ┌[array] + ├[leftsd]┤ + │ └[top] + │ + │ ┌[W] + │ │ + │ ├[N] + ├[plusdoped]┤ + │ ├[E] + │ │ + │ └[S] + │ + ┌[0]┤ ┌[W] + │ │ │ + │ │ ├[N] + │ ├[diff]┤ + │ │ ├[E] + │ │ │ + │ │ └[S] + │ │ + │ │ ┌[W] + │ │ │ + │ │ ├[con] + │ │ │ + │ ├[source]┼[N] + │ │ │ + │ │ ├[E] + │ │ │ + │ │ └[S] + │ │ + │ │ ┌[W] + │ │ │ + │ │ ├[con] + │ │ │ + │ └[drain]┼[N] + │ │ + ┌[multiplier]┤ ├[E] + │ │ │ + │ │ └[S] + │ │ + │ │ ┌[W] + │ │ │ + │ │ ├[N] + │ │ ┌[gate]┤ + │ │ │ ├[E] + │ │ │ │ + │ │ │ └[S] + │ │ │ + │ │ │ ┌[col0] + │ │ ├[row0]┤ + │ │ │ └[col1] + │ │ │ + │ │ │ ┌[array] + │ │ ├[leftsd]┤ + │ │ │ └[top] + │ │ │ + │ │ │ ┌[W] + │ │ │ │ + │ │ │ ├[N] + │ │ ├[plusdoped]┤ + │ │ │ ├[E] + │ │ │ │ + │ │ │ └[S] + │ └[1]┤ + │ │ ┌[W] + │ │ │ + │ │ ├[N] + │ ├[diff]┤ + │ │ ├[E] + │ │ │ + │ │ └[S] + │ │ + │ │ ┌[W] + │ │ │ + │ │ ├[N] + │ ├[source]┤ + │ │ ├[E] + │ │ │ + │ │ └[S] + │ │ + │ │ ┌[W] + │ │ │ + │ │ ├[N] + │ └[drain]┤ + │ ├[E] + │ │ + │ └[S] + │ + │ ┌[array]─[row0] + │ │ + │ ┌[S]┼[bottom]─[lay] + │ │ │ + │ │ └[top]─[met] + │ │ + │ │ ┌[array]─[row0] + │ │ │ + │ ├[bl]┼[bottom]─[lay] + │ │ │ + │ │ └[top]─[met] + │ │ + │ │ ┌[array]─[row0] + │ │ │ + │ ├[br]┼[bottom]─[lay] + │ │ │ + │ │ └[top]─[met] + │ │ + │ │ ┌[row0] + │ │ │ + │ │ ├[row1] + │ │ │ + │ │ ├[row2] + │ │ │ + │ │ ├[row3] + │ │ │ + │ │ ├[row4] + │ │ │ + │ │ ├[row5] + │ │ │ + │ │ ├[row6] + │ │ │ + │ │ ├[row7] + │ │ │ + │ │ ├[row8] + │ │ │ +-[pmos]┤ │ ├[row9] + │ │ │ + │ │ ├[row10] + │ │ │ + │ │ ├[row11] + │ │ │ + │ │ ├[row12] + │ │ │ + │ │ ├[row13] + │ │ │ + │ │ ├[row14] + │ │ │ + │ │ ┌[array]┼[row15] + │ │ │ │ + │ ├[E]┤ ├[row16] + │ │ │ │ + │ │ │ ├[row17] + │ │ │ │ + │ │ │ ├[row18] + │ │ │ │ + │ │ │ ├[row19] + │ │ │ │ + │ │ │ ├[row20] + │ │ │ │ + │ │ │ ├[row21] + │ │ │ │ + │ │ │ ├[row22] + │ │ │ │ + │ │ │ ├[row23] + │ │ │ │ + │ │ │ ├[row24] + │ │ │ │ + │ │ │ ├[row25] + │ │ │ │ + │ │ │ ├[row26] + │ │ │ │ + │ │ │ ├[row27] + │ │ │ │ + │ │ │ ├[row28] + │ │ │ │ + │ │ │ ├[row29] + │ │ │ │ + │ │ │ └[row30] + │ │ │ + │ │ ├[bottom]─[lay] + │ │ │ + │ │ └[top]─[met] + ├[tie]┤ + │ │ ┌[row0] + │ │ │ + │ │ ├[row1] + │ │ │ + │ │ ├[row2] + │ │ │ + │ │ ├[row3] + │ │ │ + │ │ ├[row4] + │ │ │ + │ │ ├[row5] + │ │ │ + │ │ ├[row6] + │ │ │ + │ │ ├[row7] + │ │ │ + │ │ ├[row8] + │ │ │ + │ │ ├[row9] + │ │ │ + │ │ ├[row10] + │ │ │ + │ │ ├[row11] + │ │ │ + │ │ ├[row12] + │ │ │ + │ │ ├[row13] + │ │ │ + │ │ ├[row14] + │ │ │ + │ │ ┌[array]┼[row15] + │ │ │ │ + │ ├[W]┤ ├[row16] + │ │ │ │ + │ │ │ ├[row17] + │ │ │ │ + │ │ │ ├[row18] + │ │ │ │ + │ │ │ ├[row19] + │ │ │ │ + │ │ │ ├[row20] + │ │ │ │ + │ │ │ ├[row21] + │ │ │ │ + │ │ │ ├[row22] + │ │ │ │ + │ │ │ ├[row23] + │ │ │ │ + │ │ │ ├[row24] + │ │ │ │ + │ │ │ ├[row25] + │ │ │ │ + │ │ │ ├[row26] + │ │ │ │ + │ │ │ ├[row27] + │ │ │ │ + │ │ │ ├[row28] + │ │ │ │ + │ │ │ ├[row29] + │ │ │ │ + │ │ │ └[row30] + │ │ │ + │ │ ├[bottom]─[lay] + │ │ │ + │ │ └[top]─[met] + │ │ + │ │ ┌[array]─[row0] + │ │ │ + │ ├[N]┼[bottom]─[lay] + │ │ │ + │ │ └[top]─[met] + │ │ + │ │ ┌[array]─[row0] + │ │ │ + │ ├[tl]┼[bottom]─[lay] + │ │ │ + │ │ └[top]─[met] + │ │ + │ │ ┌[array]─[row0] + │ │ │ + │ └[tr]┼[bottom]─[lay] + │ │ + │ └[top]─[met] + │ + │ ┌[W] + │ │ + │ ├[N] + └[well]┤ + ├[E] + │ + └[S] \ No newline at end of file diff --git a/openfasoc/generators/gdsfactory-gen/glayout/docs/txt_port_trees/straight_route_v1_tree.txt b/openfasoc/generators/gdsfactory-gen/glayout/docs/txt_port_trees/straight_route_v1_tree.txt new file mode 100644 index 000000000..41b1bfe9c --- /dev/null +++ b/openfasoc/generators/gdsfactory-gen/glayout/docs/txt_port_trees/straight_route_v1_tree.txt @@ -0,0 +1,7 @@ + ┌[e1] + │ + ├[e2] +-[straight_route]─[route]┤ + ├[e3] + │ + └[e4] \ No newline at end of file diff --git a/openfasoc/generators/gdsfactory-gen/glayout/docs/txt_port_trees/tapring_v1_tree.txt b/openfasoc/generators/gdsfactory-gen/glayout/docs/txt_port_trees/tapring_v1_tree.txt new file mode 100644 index 000000000..4d4edd24d --- /dev/null +++ b/openfasoc/generators/gdsfactory-gen/glayout/docs/txt_port_trees/tapring_v1_tree.txt @@ -0,0 +1,183 @@ + ┌[col0] + ┌[array]─[row0]┤ + │ └[col1] + │ + │ ┌[W] + │ │ + │ ├[N] + ├[bottom]─[lay]┤ + │ ├[E] + ┌[S]┤ │ + │ │ └[S] + │ │ + │ │ ┌[W] + │ │ │ + │ │ ├[N] + │ └[top]─[met]┤ + │ ├[E] + │ │ + │ └[S] + │ + │ ┌[array]─[row0]─[col0] + │ │ + │ │ ┌[W] + │ │ │ + │ │ ├[N] + │ ├[bottom]─[lay]┤ + │ │ ├[E] + │ │ │ + ├[bl]┤ └[S] + │ │ + │ │ ┌[W] + │ │ │ + │ │ ├[N] + │ └[top]─[met]┤ + │ ├[E] + │ │ + │ └[S] + │ + │ ┌[array]─[row0]─[col0] + │ │ + │ │ ┌[W] + │ │ │ + │ │ ├[N] + │ ├[bottom]─[lay]┤ + │ │ ├[E] + │ │ │ + ├[br]┤ └[S] + │ │ + │ │ ┌[W] + │ │ │ + │ │ ├[N] + │ └[top]─[met]┤ + │ ├[E] + │ │ + │ └[S] + │ + │ ┌[row0]─[col0] + │ │ + │ ├[row1]─[col0] + │ │ + │ ├[row2]─[col0] + │ │ + │ ├[row3]─[col0] + │ │ + │ ├[row4]─[col0] + │ ┌[array]┤ + │ │ ├[row5]─[col0] + │ │ │ + │ │ ├[row6]─[col0] + │ │ │ + │ │ ├[row7]─[col0] + │ │ │ + │ │ ├[row8]─[col0] + ├[E]┤ │ + │ │ └[row9]─[col0] + │ │ + │ │ ┌[W] + │ │ │ + │ │ ├[N] + │ ├[bottom]─[lay]┤ + │ │ ├[E] + │ │ │ + │ │ └[S] + │ │ + │ │ ┌[W] + │ │ │ + │ │ ├[N] + │ └[top]─[met]┤ + │ ├[E] + │ │ + │ └[S] +-[tapring]┤ + │ ┌[row0]─[col0] + │ │ + │ ├[row1]─[col0] + │ │ + │ ├[row2]─[col0] + │ │ + │ ├[row3]─[col0] + │ │ + │ ├[row4]─[col0] + │ ┌[array]┤ + │ │ ├[row5]─[col0] + │ │ │ + │ │ ├[row6]─[col0] + │ │ │ + │ │ ├[row7]─[col0] + │ │ │ + │ │ ├[row8]─[col0] + ├[W]┤ │ + │ │ └[row9]─[col0] + │ │ + │ │ ┌[W] + │ │ │ + │ │ ├[N] + │ ├[bottom]─[lay]┤ + │ │ ├[E] + │ │ │ + │ │ └[S] + │ │ + │ │ ┌[W] + │ │ │ + │ │ ├[N] + │ └[top]─[met]┤ + │ ├[E] + │ │ + │ └[S] + │ + │ ┌[col0] + │ ┌[array]─[row0]┤ + │ │ └[col1] + │ │ + │ │ ┌[W] + │ │ │ + │ │ ├[N] + │ ├[bottom]─[lay]┤ + │ │ ├[E] + ├[N]┤ │ + │ │ └[S] + │ │ + │ │ ┌[W] + │ │ │ + │ │ ├[N] + │ └[top]─[met]┤ + │ ├[E] + │ │ + │ └[S] + │ + │ ┌[array]─[row0]─[col0] + │ │ + │ │ ┌[W] + │ │ │ + │ │ ├[N] + │ ├[bottom]─[lay]┤ + │ │ ├[E] + │ │ │ + ├[tl]┤ └[S] + │ │ + │ │ ┌[W] + │ │ │ + │ │ ├[N] + │ └[top]─[met]┤ + │ ├[E] + │ │ + │ └[S] + │ + │ ┌[array]─[row0]─[col0] + │ │ + │ │ ┌[W] + │ │ │ + │ │ ├[N] + │ ├[bottom]─[lay]┤ + │ │ ├[E] + │ │ │ + └[tr]┤ └[S] + │ + │ ┌[W] + │ │ + │ ├[N] + └[top]─[met]┤ + ├[E] + │ + └[S] \ No newline at end of file diff --git a/openfasoc/generators/gdsfactory-gen/glayout/docs/txt_port_trees/via_stack_v1_tree.txt b/openfasoc/generators/gdsfactory-gen/glayout/docs/txt_port_trees/via_stack_v1_tree.txt new file mode 100644 index 000000000..81ad8c082 --- /dev/null +++ b/openfasoc/generators/gdsfactory-gen/glayout/docs/txt_port_trees/via_stack_v1_tree.txt @@ -0,0 +1,31 @@ + ┌[W] + │ + ├[N] + ┌[layer]┤ + │ ├[E] + │ │ + │ └[S] + │ + │ ┌[W] + │ │ + │ ├[N] + ┌[bottom]┼[via]┤ + │ │ ├[E] + │ │ │ + │ │ └[S] +-[via_stack]┤ │ + │ │ ┌[W] + │ │ │ + │ │ ├[N] + │ └[met]┤ + │ ├[E] + │ │ + │ └[S] + │ + │ ┌[W] + │ │ + │ ├[N] + └[top]─[met]┤ + ├[E] + │ + └[S] \ No newline at end of file diff --git a/openfasoc/generators/gdsfactory-gen/glayout/docs/txt_port_trees/viaarray_v1_tree.txt b/openfasoc/generators/gdsfactory-gen/glayout/docs/txt_port_trees/viaarray_v1_tree.txt new file mode 100644 index 000000000..e9985bfd7 --- /dev/null +++ b/openfasoc/generators/gdsfactory-gen/glayout/docs/txt_port_trees/viaarray_v1_tree.txt @@ -0,0 +1,39 @@ + ┌[bottom] + ┌[col0]┤ + │ └[top] + ┌[row0]┤ + │ │ ┌[bottom] + │ └[col1]┤ + │ └[top] + │ + │ ┌[bottom] + │ ┌[col0]┤ + │ │ └[top] + ┌[array]┼[row1]┤ + │ │ │ ┌[bottom] + │ │ └[col1]┤ + │ │ └[top] + │ │ + │ │ ┌[bottom] + │ │ ┌[col0]┤ + │ │ │ └[top] +-[viaarray]┤ └[row2]┤ + │ │ ┌[bottom] + │ └[col1]┤ + │ └[top] + │ + │ ┌[W] + │ │ + │ ├[N] + ├[bottom]─[lay]┤ + │ ├[E] + │ │ + │ └[S] + │ + │ ┌[W] + │ │ + │ ├[N] + └[top]─[met]┤ + ├[E] + │ + └[S] \ No newline at end of file diff --git a/openfasoc/generators/gdsfactory-gen/glayout/fet.py b/openfasoc/generators/gdsfactory-gen/glayout/fet.py new file mode 100644 index 000000000..057113b32 --- /dev/null +++ b/openfasoc/generators/gdsfactory-gen/glayout/fet.py @@ -0,0 +1,537 @@ +from gdsfactory.grid import grid +from gdsfactory.cell import cell +from gdsfactory.component import Component, copy +from gdsfactory.components.rectangle import rectangle +from glayout.pdk.mappedpdk import MappedPDK +from typing import Optional, Union +from glayout.via_gen import via_array, via_stack +from glayout.guardring import tapring +from pydantic import validate_arguments +from glayout.pdk.util.comp_utils import evaluate_bbox, to_float, to_decimal, prec_array, prec_center, prec_ref_center, movey, align_comp_to_port +from glayout.pdk.util.port_utils import rename_ports_by_orientation, rename_ports_by_list, add_ports_perimeter, print_ports +from glayout.routing.c_route import c_route +from glayout.pdk.util.snap_to_grid import component_snap_to_grid +from decimal import Decimal +from glayout.routing.straight_route import straight_route + + +@validate_arguments +def __gen_fingers_macro(pdk: MappedPDK, rmult: int, fingers: int, length: float, width: float, poly_height: float, sdlayer: str) -> Component: + """internal use: returns an array of fingers""" + length = pdk.snap_to_2xgrid(length) + width = pdk.snap_to_2xgrid(width) + poly_height = pdk.snap_to_2xgrid(poly_height) + sizing_ref_viastack = via_stack(pdk, "active_diff", "met1") + # figure out poly (gate) spacing: s/d metal doesnt overlap transistor, s/d min seperation criteria is met + sd_viaxdim = rmult*evaluate_bbox(via_stack(pdk, "active_diff", "met1"))[0] + poly_spacing = 2 * pdk.get_grule("poly", "mcon")["min_separation"] + pdk.get_grule("mcon")["width"] + poly_spacing = max(sd_viaxdim, poly_spacing) + met1_minsep = pdk.get_grule("met1")["min_separation"] + poly_spacing += met1_minsep if length < met1_minsep else 0 + # create a single finger + finger = Component("finger") + gate = finger << rectangle(size=(length, poly_height), layer=pdk.get_glayer("poly"), centered=True) + sd_viaarr = via_array(pdk, "active_diff", "met1", size=(sd_viaxdim, width), minus1=True, lay_bottom=False) + sd_viaarr_ref = finger << sd_viaarr + sd_viaarr_ref.movex((poly_spacing+length) / 2) + finger.add_ports(gate.get_ports_list(),prefix="gate_") + finger.add_ports(sd_viaarr_ref.get_ports_list(),prefix="rightsd_") + # create finger array + fingerarray = prec_array(finger, columns=fingers, rows=1, spacing=(poly_spacing+length, 1),absolute_spacing=True) + sd_via_ref_left = fingerarray << sd_viaarr + sd_via_ref_left.movex(0-(poly_spacing+length)/2) + fingerarray.add_ports(sd_via_ref_left.get_ports_list(),prefix="leftsd_") + # center finger array and add ports + centered_farray = Component() + fingerarray_ref_center = prec_ref_center(fingerarray) + centered_farray.add(fingerarray_ref_center) + centered_farray.add_ports(fingerarray_ref_center.get_ports_list()) + # create diffusion and +doped region + multiplier = rename_ports_by_orientation(centered_farray) + diff_extra_enc = 2 * pdk.get_grule("mcon", "active_diff")["min_enclosure"] + diff_dims =(diff_extra_enc + evaluate_bbox(multiplier)[0], width) + diff = multiplier << rectangle(size=diff_dims,layer=pdk.get_glayer("active_diff"),centered=True) + sd_diff_ovhg = pdk.get_grule(sdlayer, "active_diff")["min_enclosure"] + sdlayer_dims = [dim + sd_diff_ovhg for dim in diff_dims] + sdlayer_ref = multiplier << rectangle(size=sdlayer_dims, layer=pdk.get_glayer(sdlayer),centered=True) + multiplier.add_ports(sdlayer_ref.get_ports_list(),prefix="plusdoped_") + multiplier.add_ports(diff.get_ports_list(),prefix="diff_") + return component_snap_to_grid(rename_ports_by_orientation(multiplier)) + + +@cell +def multiplier( + pdk: MappedPDK, + sdlayer: str, + width: Optional[float] = 3, + length: Optional[float] = None, + fingers: int = 1, + routing: bool = True, + inter_finger_topmet: str = "met1", + dummy: Union[bool, tuple[bool, bool]] = True, + sd_route_topmet: str = "met2", + gate_route_topmet: str = "met2", + rmult: Optional[int]=None, + sd_rmult: int = 1, + gate_rmult: int=1, + interfinger_rmult: int=1, + sd_route_extension: float = 0, + gate_route_extension: float = 0, +) -> Component: + """Generic poly/sd vias generator + args: + pdk = pdk to use + sdlayer = either p+s/d for pmos or n+s/d for nmos + width = expands the transistor in the y direction + length = transitor length (if left None defaults to min length) + fingers = introduces additional fingers (sharing s/d) of width=width + routing = true or false, specfies if sd should be connected + inter_finger_topmet = top metal of the via array laid on the source/drain regions + ****NOTE: routing metal is layed over the source drain regions regardless of routing option + dummy = true or false add dummy active/plus doped regions + sd_rmult = multiplies thickness of sd metal (int only) + gate_rmult = multiplies gate by adding rows to the gate via array (int only) + interfinger_rmult = multiplies thickness of source/drain routes between the gates (int only) + sd_route_extension = float, how far extra to extend the source/drain connections (default=0) + gate_route_extension = float, how far extra to extend the gate connection (default=0) + + ports (one port for each edge), + ****NOTE: source is below drain: + gate_... all edges (top met route of gate connection) + source_...all edges (top met route of source connections) + drain_...all edges (top met route of drain connections) + plusdoped_...all edges (area of p+s/d or n+s/d layer) + diff_...all edges (diffusion region) + rowx_coly_...all ports associated with finger array include gate_... and array_ (array includes all ports of the viastacks in the array) + leftsd_...all ports associated with the left most via array + """ + # error checking + if "+s/d" not in sdlayer: + raise ValueError("specify + doped region for multiplier") + if not "met" in sd_route_topmet or not "met" in gate_route_topmet: + raise ValueError("topmet specified must be metal layer") + if rmult: + if rmult<1: + raise ValueError("rmult must be positive int") + sd_rmult = rmult + gate_rmult = 1 + interfinger_rmult = ((rmult-1) or 1) + if sd_rmult<1 or interfinger_rmult<1 or gate_rmult<1: + raise ValueError("routing multipliers must be positive int") + if fingers < 1: + raise ValueError("number of fingers must be positive int") + # argument parsing and rule setup + min_length = pdk.get_grule("poly")["min_width"] + length = min_length if (length or min_length) <= min_length else length + length = pdk.snap_to_2xgrid(length) + min_width = max(min_length, pdk.get_grule("active_diff")["min_width"]) + width = min_width if (width or min_width) <= min_width else width + width = pdk.snap_to_2xgrid(width) + poly_height = width + 2 * pdk.get_grule("poly", "active_diff")["overhang"] + # call finger array + multiplier = __gen_fingers_macro(pdk, interfinger_rmult, fingers, length, width, poly_height, sdlayer) + # route all drains/ gates/ sources + if routing: + # place vias, then straight route from top port to via-botmet_N + sd_N_port = multiplier.ports["leftsd_top_met_N"] + sdvia = via_stack(pdk, "met1", sd_route_topmet) + sdmet_hieght = sd_rmult*evaluate_bbox(sdvia)[1] + sdroute_minsep = pdk.get_grule(sd_route_topmet)["min_separation"] + sdvia_ports = list() + for finger in range(fingers+1): + diff_top_port = movey(sd_N_port,destination=width/2) + # place sdvia such that metal does not overlap diffusion + big_extension = sdroute_minsep + sdmet_hieght/2 + sdmet_hieght + sdvia_extension = big_extension if finger % 2 else sdmet_hieght/2 + sdvia_ref = align_comp_to_port(sdvia,diff_top_port,alignment=('c','t')) + multiplier.add(sdvia_ref.movey(sdvia_extension + pdk.snap_to_2xgrid(sd_route_extension))) + multiplier << straight_route(pdk, diff_top_port, sdvia_ref.ports["bottom_met_N"]) + sdvia_ports += [sdvia_ref.ports["top_met_W"], sdvia_ref.ports["top_met_E"]] + # get the next port (break before this if last iteration because port D.N.E. and num gates=fingers) + if finger==fingers: + break + sd_N_port = multiplier.ports[f"row0_col{finger}_rightsd_top_met_N"] + # route gates + gate_S_port = multiplier.ports[f"row0_col{finger}_gate_S"] + metal_seperation = pdk.util_max_metal_seperation() + psuedo_Ngateroute = movey(gate_S_port.copy(),0-metal_seperation-gate_route_extension) + psuedo_Ngateroute.y = pdk.snap_to_2xgrid(psuedo_Ngateroute.y) + multiplier << straight_route(pdk,gate_S_port,psuedo_Ngateroute) + # place route met: gate + gate_width = gate_S_port.center[0] - multiplier.ports["row0_col0_gate_S"].center[0] + gate_S_port.width + gate = rename_ports_by_list(via_array(pdk,"poly",gate_route_topmet, size=(gate_width,None),num_vias=(None,gate_rmult), no_exception=True, fullbottom=True),[("top_met_","gate_")]) + gate_ref = align_comp_to_port(gate.copy(), psuedo_Ngateroute, alignment=(None,'b'),layer=pdk.get_glayer("poly")) + multiplier.add(gate_ref) + # place route met: source, drain + sd_width = sdvia_ports[-1].center[0] - sdvia_ports[0].center[0] + sd_route = rectangle(size=(sd_width,sdmet_hieght),layer=pdk.get_glayer(sd_route_topmet),centered=True) + source = align_comp_to_port(sd_route.copy(), sdvia_ports[0], alignment=(None,'c')) + drain = align_comp_to_port(sd_route.copy(), sdvia_ports[2], alignment=(None,'c')) + multiplier.add(source) + multiplier.add(drain) + # add ports + multiplier.add_ports(drain.get_ports_list(), prefix="drain_") + multiplier.add_ports(source.get_ports_list(), prefix="source_") + multiplier.add_ports(gate_ref.get_ports_list(prefix="gate_")) + # create dummy regions + if isinstance(dummy, bool): + dummyl = dummyr = dummy + else: + dummyl, dummyr = dummy + if dummyl or dummyr: + dummy = Component("temp dummy region") + size = (length, width) + dummy << rectangle( + layer=pdk.get_glayer("active_diff"), size=to_float(size), centered=True + ) + dummy_space = pdk.get_grule(sdlayer, "active_diff")["min_enclosure"] + dummy.add_padding(layers=(pdk.get_glayer(sdlayer),), default=dummy_space) + dummy_space = dummy_space + pdk.get_grule(sdlayer)["min_separation"] + float(size[0] / 2) + sides = list() + if dummyl: + sides.append(-1) + if dummyr: + sides.append(1) + for side in sides: + dummy_ref = multiplier << dummy + dummy_ref.movex(side * (dummy_space + multiplier.xmax)) + # ensure correct port names and return + return component_snap_to_grid(rename_ports_by_orientation(multiplier)) + + +@validate_arguments +def __mult_array_macro( + pdk: MappedPDK, + sdlayer: str, + width: Optional[float] = 3, + fingers: Optional[int] = 1, + multipliers: Optional[int] = 1, + routing: Optional[bool] = True, + dummy: Optional[Union[bool, tuple[bool, bool]]] = True, + length: Optional[float] = None, + sd_route_topmet: Optional[str] = "met2", + gate_route_topmet: Optional[str] = "met2", + sd_route_left: Optional[bool] = True, + sd_rmult: int = 1, + gate_rmult: int=1, + interfinger_rmult: int=1 +) -> Component: + """create a multiplier array with multiplier_0 at the bottom + The array is correctly centered + """ + # create multiplier array + pdk.activate() + # TODO: error checking + multiplier_arr = Component("temp multiplier array") + multiplier_comp = multiplier( + pdk, + sdlayer, + width=width, + fingers=fingers, + dummy=dummy, + routing=routing, + length=length, + sd_route_topmet=sd_route_topmet, + gate_route_topmet=gate_route_topmet, + sd_rmult=sd_rmult, + gate_rmult=gate_rmult, + interfinger_rmult=interfinger_rmult + ) + _max_metal_seperation_ps = max([pdk.get_grule("met"+str(i))["min_separation"] for i in range(1,5)]) + multiplier_separation = ( + to_decimal(_max_metal_seperation_ps) + + evaluate_bbox(multiplier_comp, True)[1] + ) + for rownum in range(multipliers): + row_displacment = rownum * multiplier_separation - (multiplier_separation/2 * (multipliers-1)) + row_ref = multiplier_arr << multiplier_comp + row_ref.movey(to_float(row_displacment)) + multiplier_arr.add_ports( + row_ref.get_ports_list(), prefix="multiplier_" + str(rownum) + "_" + ) + # TODO: fix extension (both extension are broken. IDK src extension and drain extension IDK metal layer) + src_extension = to_decimal(0.6) + drain_extension = src_extension + 3*to_decimal(pdk.get_grule("met4")["min_separation"]) + sd_side = "W" if sd_route_left else "E" + gate_side = "E" if sd_route_left else "W" + if routing and multipliers > 1: + for rownum in range(multipliers-1): + thismult = "multiplier_" + str(rownum) + "_" + nextmult = "multiplier_" + str(rownum+1) + "_" + # route sources left + srcpfx = thismult + "source_" + this_src = multiplier_arr.ports[srcpfx+sd_side] + next_src = multiplier_arr.ports[nextmult + "source_"+sd_side] + src_ref = multiplier_arr << c_route(pdk, this_src, next_src, viaoffset=(True,False), extension=to_float(src_extension)) + multiplier_arr.add_ports(src_ref.get_ports_list(), prefix=srcpfx) + # route drains left + drainpfx = thismult + "drain_" + this_drain = multiplier_arr.ports[drainpfx+sd_side] + next_drain = multiplier_arr.ports[nextmult + "drain_"+sd_side] + drain_ref = multiplier_arr << c_route(pdk, this_drain, next_drain, viaoffset=(True,False), extension=to_float(drain_extension)) + multiplier_arr.add_ports(drain_ref.get_ports_list(), prefix=drainpfx) + # route gates right + gatepfx = thismult + "gate_" + this_gate = multiplier_arr.ports[gatepfx+gate_side] + next_gate = multiplier_arr.ports[nextmult + "gate_"+gate_side] + gate_ref = multiplier_arr << c_route(pdk, this_gate, next_gate, viaoffset=(True,False), extension=to_float(src_extension)) + multiplier_arr.add_ports(gate_ref.get_ports_list(), prefix=gatepfx) + multiplier_arr = component_snap_to_grid(rename_ports_by_orientation(multiplier_arr)) + # recenter + final_arr = Component() + marrref = final_arr << multiplier_arr + correctionxy = prec_center(marrref) + marrref.movex(correctionxy[0]).movey(correctionxy[1]) + final_arr.add_ports(marrref.get_ports_list()) + return component_snap_to_grid(rename_ports_by_orientation(final_arr)) + + +@cell +def nmos( + pdk, + width: float = 3, + fingers: Optional[int] = 1, + multipliers: Optional[int] = 1, + with_tie: bool = True, + with_dummy: Union[bool, tuple[bool, bool]] = True, + with_dnwell: bool = True, + with_substrate_tap: bool = True, + length: Optional[float] = None, + sd_route_topmet: str = "met2", + gate_route_topmet: str = "met2", + sd_route_left: bool = True, + rmult: Optional[int] = None, + sd_rmult: int=1, + gate_rmult: int=1, + interfinger_rmult: int=1 +) -> Component: + """Generic NMOS generator + pdk = mapped pdk to use + width = expands the NMOS in the y direction + fingers = introduces additional fingers (sharing source/drain) of width=width + multipliers = number of multipliers (a multiplier is a row of fingers) + with_tie = true or false, specfies if a bulk tie is required + with_dummy = tuple(bool,bool) or bool specifying both sides dummy or neither side dummy + ****using the tuple option, you can specify a single side dummy such as true,false + with_dnwell = bool use dnwell (multi well) + with_substrate_tap = add substrate tap on the very outside perimeter of nmos + length = if None or below min_length will default to min_length + sd_route_topmet = specify top metal glayer for the source/drain route + gate_route_topmet = specify top metal glayer for the gate route + sd_route_left = specify if the source/drain inter-multiplier routes should be on the left side or right side (if false) + rmult = if not None overrides all other multiplier options to provide a simple routing multiplier (int only) + sd_rmult = mulitplies the thickness of the source drain route (int only) + gate_rmult = add additional via rows to the gate route via array (int only) + interfinger_rmult = multiplies the thickness of the metal routes between the fingers (int only) + """ + # TODO: glayer checks + pdk.activate() + nfet = Component() + if rmult: + if rmult<1: + raise ValueError("rmult must be positive int") + sd_rmult = rmult + gate_rmult = 1 + interfinger_rmult = ((rmult-1) or 1) + # create and add multipliers to nfet + multiplier_arr = __mult_array_macro( + pdk, + "n+s/d", + width, + fingers, + multipliers, + dummy=with_dummy, + length=length, + sd_route_topmet=sd_route_topmet, + gate_route_topmet=gate_route_topmet, + sd_route_left=sd_route_left, + sd_rmult=sd_rmult, + gate_rmult=gate_rmult, + interfinger_rmult=interfinger_rmult + ) + multiplier_arr_ref = multiplier_arr.ref() + nfet.add(multiplier_arr_ref) + nfet.add_ports(multiplier_arr_ref.get_ports_list()) + # add tie if tie + if with_tie: + tap_separation = max( + pdk.get_grule("met2")["min_separation"], + pdk.get_grule("met1")["min_separation"], + pdk.get_grule("active_diff", "active_tap")["min_separation"], + ) + tap_separation += pdk.get_grule("p+s/d", "active_tap")["min_enclosure"] + tap_encloses = ( + 2 * (tap_separation + nfet.xmax), + 2 * (tap_separation + nfet.ymax), + ) + tiering_ref = nfet << tapring( + pdk, + enclosed_rectangle=tap_encloses, + sdlayer="p+s/d", + horizontal_glayer="met2", + vertical_glayer="met1", + ) + nfet.add_ports(tiering_ref.get_ports_list(), prefix="tie_") + # add pwell + nfet.add_padding( + layers=(pdk.get_glayer("pwell"),), + default=pdk.get_grule("pwell", "active_tap")["min_enclosure"], + ) + nfet = add_ports_perimeter(nfet,layer=pdk.get_glayer("pwell"),prefix="well_") + # add dnwell if dnwell + if with_dnwell: + nfet.add_padding( + layers=(pdk.get_glayer("dnwell"),), + default=pdk.get_grule("pwell", "dnwell")["min_enclosure"], + ) + # add substrate tap if with_substrate_tap + if with_substrate_tap: + substrate_tap_separation = pdk.get_grule("dnwell", "active_tap")[ + "min_separation" + ] + substrate_tap_encloses = ( + 2 * (substrate_tap_separation + nfet.xmax), + 2 * (substrate_tap_separation + nfet.ymax), + ) + ringtoadd = tapring( + pdk, + enclosed_rectangle=substrate_tap_encloses, + sdlayer="p+s/d", + horizontal_glayer="met2", + vertical_glayer="met1", + ) + tapring_ref = nfet << ringtoadd + nfet.add_ports(tapring_ref.get_ports_list(),prefix="guardring_") + return rename_ports_by_orientation(nfet).flatten() + + +@cell +def pmos( + pdk, + width: float = 3, + fingers: Optional[int] = 1, + multipliers: Optional[int] = 1, + with_tie: Optional[bool] = True, + dnwell: Optional[bool] = False, + with_dummy: Optional[Union[bool, tuple[bool, bool]]] = True, + with_substrate_tap: Optional[bool] = True, + length: Optional[float] = None, + sd_route_topmet: Optional[str] = "met2", + gate_route_topmet: Optional[str] = "met2", + sd_route_left: Optional[bool] = True, + rmult: Optional[int] = None, + sd_rmult: int=1, + gate_rmult: int=1, + interfinger_rmult: int=1 +) -> Component: + """Generic PMOS generator + pdk = mapped pdk to use + width = expands the PMOS in the y direction + fingers = introduces additional fingers (sharing source/drain) of width=width + multipliers = number of multipliers (a multiplier is a row of fingers) + with_tie = true or false, specfies if a bulk tie is required + dnwell = bool use dnwell if True, or use nwell if False + with_dummy = tuple(bool,bool) or bool specifying both sides dummy or neither side dummy + ****using the tuple option, you can specify a single side dummy such as true,false + with_substrate_tap = add substrate tap on the very outside perimeter of pmos + length = if None or below min_length will default to min_length + sd_route_topmet = specify top metal glayer for the source/drain route + gate_route_topmet = specify top metal glayer for the gate route + sd_route_left = specify if the source/drain inter-multiplier routes should be on the left side or right side (if false) + rmult = if not None overrides all other multiplier options to provide a simple routing multiplier (int only) + sd_rmult = mulitplies the thickness of the source drain route (int only) + gate_rmult = add additional via rows to the gate route via array (int only) + interfinger_rmult = multiplies the thickness of the metal routes between the fingers (int only) + """ + # TODO: glayer checks + pdk.activate() + pfet = Component() + if rmult: + if rmult<1: + raise ValueError("rmult must be positive int") + sd_rmult = rmult + gate_rmult = 1 + interfinger_rmult = ((rmult-1) or 1) + # create and add multipliers to nfet + multiplier_arr = __mult_array_macro( + pdk, + "p+s/d", + width, + fingers, + multipliers, + dummy=with_dummy, + length=length, + sd_route_topmet=sd_route_topmet, + gate_route_topmet=gate_route_topmet, + sd_route_left=sd_route_left, + gate_rmult=gate_rmult, + interfinger_rmult=interfinger_rmult, + sd_rmult=sd_rmult + ) + multiplier_arr_ref = multiplier_arr.ref() + pfet.add(multiplier_arr_ref) + pfet.add_ports(multiplier_arr_ref.get_ports_list()) + # add tie if tie + if with_tie: + tap_separation = max( + pdk.get_grule("met2")["min_separation"], + pdk.get_grule("met1")["min_separation"], + pdk.get_grule("active_diff", "active_tap")["min_separation"], + ) + tap_separation += pdk.get_grule("n+s/d", "active_tap")["min_enclosure"] + tap_encloses = ( + 2 * (tap_separation + pfet.xmax), + 2 * (tap_separation + pfet.ymax), + ) + tapring_ref = pfet << tapring( + pdk, + enclosed_rectangle=tap_encloses, + sdlayer="n+s/d", + horizontal_glayer="met2", + vertical_glayer="met1", + ) + pfet.add_ports(tapring_ref.get_ports_list(),prefix="tie_") + # add nwell + nwell_glayer = "dnwell" if dnwell else "nwell" + pfet.add_padding( + layers=(pdk.get_glayer(nwell_glayer),), + default=pdk.get_grule("active_tap", nwell_glayer)["min_enclosure"], + ) + pfet = add_ports_perimeter(pfet,layer=pdk.get_glayer(nwell_glayer),prefix="well_") + # add substrate tap if with_substrate_tap + if with_substrate_tap: + substrate_tap_separation = pdk.get_grule("dnwell", "active_tap")[ + "min_separation" + ] + substrate_tap_encloses = ( + 2 * (substrate_tap_separation + pfet.xmax), + 2 * (substrate_tap_separation + pfet.ymax), + ) + pfet << tapring( + pdk, + enclosed_rectangle=substrate_tap_encloses, + sdlayer="p+s/d", + horizontal_glayer="met2", + vertical_glayer="met1", + ) + return rename_ports_by_orientation(pfet).flatten() + + +if __name__ == "__main__": + from .pdk.util.standard_main import pdk + + showmult = True + if showmult: + mycomp = multiplier(pdk, "p+s/d", fingers=1, dummy=True, gate_route_topmet="met4",sd_route_topmet="met3", length=1, width=6) + #bcomp = multiplier(pdk, "p+s/d", fingers=8, dummy=True, gate_route_topmet="met4",sd_route_topmet="met3", length=1, rmult=2) + #bcomp.show() + else: + #mycomp = pmos(pdk, fingers=8, length=1, multipliers=3, width=6, with_dummy=True) + mycomp = pmos(pdk, fingers=8, length=0, multipliers=3, width=6, with_dummy=True,rmult=2) + #print(*mycomp.get_polygons(),sep="\n") + #large = pmos(pdk, fingers=20, length=1, multipliers=5, width=6, with_dummy=True) + #large.show() + #mycomp = pmos(pdk, fingers=8, multipliers=2, with_dummy=False, gate_route_topmet="met4",sd_route_topmet="met4") + mycomp.show() + for key in mycomp.ports.keys(): + print(key) diff --git a/openfasoc/generators/gdsfactory-gen/glayout/guardring.py b/openfasoc/generators/gdsfactory-gen/glayout/guardring.py new file mode 100644 index 000000000..4a6c66d35 --- /dev/null +++ b/openfasoc/generators/gdsfactory-gen/glayout/guardring.py @@ -0,0 +1,120 @@ +from glayout.pdk.mappedpdk import MappedPDK +from gdsfactory.cell import cell +from gdsfactory.component import Component +from gdsfactory.components.rectangle import rectangle +from gdsfactory.components.rectangular_ring import rectangular_ring +from glayout.via_gen import via_array, via_stack +from typing import Optional +from glayout.pdk.util.comp_utils import to_decimal, to_float, evaluate_bbox +from glayout.pdk.util.port_utils import print_ports +from glayout.pdk.util.snap_to_grid import component_snap_to_grid +from glayout.routing.L_route import L_route + + +@cell +def tapring( + pdk: MappedPDK, + enclosed_rectangle=(2.0, 4.0), + sdlayer: Optional[str] = "p+s/d", + horizontal_glayer: Optional[str] = "met2", + vertical_glayer: Optional[str] = "met1", +) -> Component: + """ptapring produce a p substrate / pwell tap rectanglular ring + This ring will legally enclose a rectangular shape + args: + pdk: MappedPDK is the pdk to use + enclosed_rectangle: tuple is the (width, hieght) of the area to enclose + ****NOTE: the enclosed_rectangle will be the enclosed dimensions of active_tap + horizontal_glayer: string=met2, layer used over the ring horizontally + vertical_glayer: string=met1, layer used over the ring vertically + ports: + Narr_... all ports in top via array + Sarr_... all ports in bottom via array + Earr_... all ports in right via array + Warr_... all ports in left via array + bl_corner_...all ports in bottom left L route + """ + enclosed_rectangle = pdk.snap_to_2xgrid(enclosed_rectangle,return_type="float") + # check layers, activate pdk, create top cell + pdk.has_required_glayers( + [sdlayer, "active_tap", "mcon", horizontal_glayer, vertical_glayer] + ) + pdk.activate() + ptapring = Component() + if not "met" in horizontal_glayer or not "met" in vertical_glayer: + raise ValueError("both horizontal and vertical glayers should be metals") + # check that ring is not too small + min_gap_tap = pdk.get_grule("active_tap")["min_separation"] + if enclosed_rectangle[0] < min_gap_tap: + raise ValueError("ptapring must be larger than " + str(min_gap_tap)) + # create active tap + tap_width = max( + pdk.get_grule("active_tap")["min_width"], + 2 * pdk.get_grule("active_tap", "mcon")["min_enclosure"] + + pdk.get_grule("mcon")["width"], + ) + ptapring << rectangular_ring( + enclosed_size=enclosed_rectangle, + width=tap_width, + centered=True, + layer=pdk.get_glayer("active_tap"), + ) + # create p plus area + pp_enclosure = pdk.get_grule("active_tap", sdlayer)["min_enclosure"] + pp_width = 2 * pp_enclosure + tap_width + pp_enclosed_rectangle = [dim - 2 * pp_enclosure for dim in enclosed_rectangle] + ptapring << rectangular_ring( + enclosed_size=pp_enclosed_rectangle, + width=pp_width, + centered=True, + layer=pdk.get_glayer(sdlayer), + ) + # create via arrs + via_width_horizontal = evaluate_bbox(via_stack(pdk, "active_tap", horizontal_glayer))[0] + arr_size_horizontal = enclosed_rectangle[0] + horizontal_arr = via_array( + pdk, + "active_tap", + horizontal_glayer, + (arr_size_horizontal, via_width_horizontal), + minus1=True, + ) + via_width_vertical = evaluate_bbox(via_stack(pdk, "active_tap", vertical_glayer))[1] + arr_size_vertical = enclosed_rectangle[1] + vertical_arr = via_array( + pdk, + "active_tap", + vertical_glayer, + (via_width_vertical, arr_size_vertical), + minus1=True, + ) + # add via arrs + refs_prefixes = list() + metal_ref_n = ptapring << horizontal_arr + metal_ref_e = ptapring << vertical_arr + metal_ref_s = ptapring << horizontal_arr + metal_ref_w = ptapring << vertical_arr + metal_ref_n.movey(round(0.5 * (enclosed_rectangle[1] + tap_width),4)) + metal_ref_e.movex(round(0.5 * (enclosed_rectangle[0] + tap_width),4)) + metal_ref_s.movey(round(-0.5 * (enclosed_rectangle[1] + tap_width),4)) + metal_ref_w.movex(round(-0.5 * (enclosed_rectangle[0] + tap_width),4)) + refs_prefixes += [(metal_ref_n,"N_"), (metal_ref_e,"E_"), (metal_ref_s,"S_"), (metal_ref_w,"W_")] + # connect vertices + tlvia = ptapring << L_route(pdk, metal_ref_n.ports["top_met_W"], metal_ref_w.ports["top_met_N"]) + trvia = ptapring << L_route(pdk, metal_ref_n.ports["top_met_E"], metal_ref_e.ports["top_met_N"]) + blvia = ptapring << L_route(pdk, metal_ref_s.ports["top_met_W"], metal_ref_w.ports["top_met_S"]) + brvia = ptapring << L_route(pdk, metal_ref_s.ports["top_met_E"], metal_ref_e.ports["top_met_S"]) + refs_prefixes += [(tlvia,"tl_"),(trvia,"tr_"),(blvia,"bl_"),(brvia,"br_")] + # add ports, flatten and return + for ref_, prefix in refs_prefixes: + ptapring.add_ports(ref_.get_ports_list(),prefix=prefix) + return component_snap_to_grid(ptapring) + + +if __name__ == "__main__": + from .pdk.util.standard_main import pdk + + mycomp = Component("displacment test") + tapref = mycomp << tapring(pdk, sdlayer="p+s/d", enclosed_rectangle=(75.9, 31.0)) + #tapref.movey(100.105) + mycomp.show() diff --git a/openfasoc/generators/gdsfactory-gen/glayout/mimcap.py b/openfasoc/generators/gdsfactory-gen/glayout/mimcap.py new file mode 100644 index 000000000..c47edd22a --- /dev/null +++ b/openfasoc/generators/gdsfactory-gen/glayout/mimcap.py @@ -0,0 +1,105 @@ +from gdsfactory.cell import cell +from gdsfactory.component import Component +from gdsfactory.components.rectangle import rectangle +from glayout.pdk.mappedpdk import MappedPDK +from typing import Optional +from glayout.via_gen import via_array +from glayout.pdk.util.comp_utils import prec_array, to_decimal, to_float +from glayout.pdk.util.port_utils import rename_ports_by_orientation, add_ports_perimeter, print_ports +from pydantic import validate_arguments +from glayout.routing.straight_route import straight_route +from decimal import ROUND_UP, Decimal + + +@validate_arguments +def __get_mimcap_layerconstruction_info(pdk: MappedPDK) -> tuple[str,str]: + """returns the glayer metal below and glayer metal above capmet + args: pdk + """ + capmettop = pdk.layer_to_glayer(pdk.get_grule("capmet")["capmettop"]) + capmetbottom = pdk.layer_to_glayer(pdk.get_grule("capmet")["capmetbottom"]) + pdk.has_required_glayers(["capmet",capmettop,capmetbottom]) + pdk.activate() + return capmettop, capmetbottom + + +@cell +def mimcap( + pdk: MappedPDK, size: tuple[float,float]=(5.0, 5.0) +) -> Component: + """create a mimcap + args: + pdk=pdk to use + size=tuple(float,float) size of cap + ****Note: size is the size of the capmet layer + ports: + top_met_...all edges, this is the metal over the capmet + bottom_met_...all edges, this is the metal below capmet + """ + size = pdk.snap_to_2xgrid(size) + # error checking and + capmettop, capmetbottom = __get_mimcap_layerconstruction_info(pdk) + # create top component + mim_cap = Component() + mim_cap << rectangle(size=size, layer=pdk.get_glayer("capmet"), centered=True) + top_met_ref = mim_cap << via_array( + pdk, capmetbottom, capmettop, size=size, minus1=True, lay_bottom=False + ) + bottom_met_enclosure = pdk.get_grule(capmetbottom,"capmet")["min_enclosure"] + mim_cap.add_padding(layers=(pdk.get_glayer(capmetbottom),),default=bottom_met_enclosure) + # flatten and create ports + mim_cap = add_ports_perimeter(mim_cap, layer=pdk.get_glayer(capmetbottom), prefix="bottom_met_") + mim_cap.add_ports(top_met_ref.get_ports_list()) + return rename_ports_by_orientation(mim_cap).flatten() + + +@cell +def mimcap_array(pdk: MappedPDK, rows: int, columns: int, size: tuple[float,float] = (5.0,5.0), rmult: Optional[int]=1) -> Component: + """create mimcap array + args: + pdk to use + size = tuple(float,float) size of a single cap + ****Note: size is the size of the capmet layer + ports: + cap_x_y_top_met_...all edges, this is the metal over the capmet in row x, col y + cap_x_y_bottom_met_...all edges, this is the metal below capmet in row x, col y + """ + capmettop, capmetbottom = __get_mimcap_layerconstruction_info(pdk) + mimcap_arr = Component() + # create the mimcap array + mimcap_single = mimcap(pdk, size) + mimcap_space = pdk.get_grule("capmet")["min_separation"] #+ evaluate_bbox(mimcap_single)[0] + array_ref = mimcap_arr << prec_array(mimcap_single, rows, columns, spacing=2*[mimcap_space]) + mimcap_arr.add_ports(array_ref.get_ports_list()) + # create a list of ports that should be routed to connect the array + port_pairs = list() + for rownum in range(rows): + for colnum in range(columns): + bl_mimcap = f"row{rownum}_col{colnum}_" + right_mimcap = f"row{rownum}_col{colnum+1}_" + top_mimcap = f"row{rownum+1}_col{colnum}_" + for level,layer in [("bottom_met_",capmetbottom),("top_met_",capmettop)]: + bl_east_port = mimcap_arr.ports.get(bl_mimcap+level+"E") + r_west_port = mimcap_arr.ports.get(right_mimcap+level+"W") + bl_north_port = mimcap_arr.ports.get(bl_mimcap+level+"N") + top_south_port = mimcap_arr.ports.get(top_mimcap+level+"S") + if rownum == rows-1 and colnum == columns-1: + continue + elif rownum == rows-1: + port_pairs.append((bl_east_port,r_west_port,layer)) + elif colnum == columns-1: + port_pairs.append((bl_north_port,top_south_port,layer)) + else: + port_pairs.append((bl_east_port,r_west_port,layer)) + port_pairs.append((bl_north_port,top_south_port,layer)) + for port_pair in port_pairs: + mimcap_arr << straight_route(pdk,port_pair[0],port_pair[1],width=rmult*pdk.get_grule(port_pair[2])["min_width"]) + return mimcap_arr.flatten() + + +if __name__ == "__main__": + from .pdk.util.standard_main import pdk + + mycap = mimcap_array(pdk,1,1) + mycap.show() + print_ports(mycap) diff --git a/openfasoc/generators/gdsfactory-gen/glayout/opamp.py b/openfasoc/generators/gdsfactory-gen/glayout/opamp.py new file mode 100644 index 000000000..4660e09dc --- /dev/null +++ b/openfasoc/generators/gdsfactory-gen/glayout/opamp.py @@ -0,0 +1,610 @@ +from gdsfactory.cell import cell, clear_cache +from gdsfactory.component import Component, copy +from gdsfactory.component_reference import ComponentReference +from gdsfactory.components.rectangle import rectangle +from glayout.pdk.mappedpdk import MappedPDK +from typing import Optional, Union +from glayout.fet import nmos, pmos, multiplier +from glayout.diff_pair import diff_pair +from glayout.guardring import tapring +from glayout.mimcap import mimcap_array, mimcap +from glayout.routing.L_route import L_route +from glayout.routing.c_route import c_route +from glayout.via_gen import via_stack, via_array +from gdsfactory.routing.route_quad import route_quad +from glayout.pdk.util.comp_utils import evaluate_bbox, prec_ref_center, movex, movey, to_decimal, to_float, move, align_comp_to_port, get_padding_points_cc +from glayout.pdk.util.port_utils import rename_ports_by_orientation, rename_ports_by_list, add_ports_perimeter, print_ports, set_port_orientation, rename_component_ports +from glayout.routing.straight_route import straight_route +from glayout.pdk.util.snap_to_grid import component_snap_to_grid +from pydantic import validate_arguments +from glayout.common.two_transistor_interdigitized import two_nfet_interdigitized + + + + +@validate_arguments +def __add_diff_pair_and_bias(pdk: MappedPDK, opamp_top: Component, diffpair_params: tuple[float, float, int], diffpair_bias: tuple[float, float, int], rmult: int) -> Component: + # create and center diffpair + diffpair_i_ = Component("temp diffpair and current source") + center_diffpair_comp = diff_pair( + pdk, + width=diffpair_params[0], + length=diffpair_params[1], + fingers=diffpair_params[2], + rmult=rmult + ) + diffpair_i_.add(prec_ref_center(center_diffpair_comp)) + diffpair_i_.add_ports(center_diffpair_comp.get_ports_list()) + # create and position tail current source + cmirror = two_nfet_interdigitized( + pdk, + width=diffpair_bias[0], + length=diffpair_bias[1], + numcols=diffpair_bias[2], + with_tie=False, + with_substrate_tap=False, + gate_route_topmet="met3", + sd_route_topmet="met3", + rmult=rmult + ) + # cmirror routing + metal_sep = pdk.util_max_metal_seperation() + gate_short = cmirror << c_route(pdk, cmirror.ports["A_gate_E"],cmirror.ports["B_gate_E"],extension=3*metal_sep,viaoffset=None) + cmirror << L_route(pdk, gate_short.ports["con_N"],cmirror.ports["A_drain_E"],viaoffset=False,fullbottom=False) + srcshort = cmirror << c_route(pdk, cmirror.ports["A_source_W"],cmirror.ports["B_source_W"],extension=metal_sep,viaoffset=False) + cmirror.add_ports(srcshort.get_ports_list(),prefix="purposegndports") + # add cmirror + tailcurrent_ref = diffpair_i_ << cmirror + tailcurrent_ref.movey( + -0.5 * (center_diffpair_comp.ymax - center_diffpair_comp.ymin) + - abs(tailcurrent_ref.ymax) - metal_sep + ) + purposegndPort = tailcurrent_ref.ports["purposegndportscon_S"].copy() + purposegndPort.name = "ibias_purposegndport" + diffpair_i_.add_ports([purposegndPort]) + diffpair_i_.add_ports(tailcurrent_ref.get_ports_list(), prefix="ibias_") + diffpair_i_ref = prec_ref_center(diffpair_i_) + opamp_top.add(diffpair_i_ref) + opamp_top.add_ports(diffpair_i_ref.get_ports_list(),prefix="diffpair_") + return opamp_top + +@validate_arguments +def __add_common_source_nbias_transistors(pdk: MappedPDK, opamp_top: Component, half_common_source_nbias: tuple[float, float, int, int], rmult: int) -> Component: + # create each half of the nmos bias transistor for the common source stage and place them + x_dim_center = opamp_top.xmax + for i in range(2): + cmirror_output = nmos( + pdk, + width=half_common_source_nbias[0], + length=half_common_source_nbias[1], + fingers=half_common_source_nbias[2], + multipliers=half_common_source_nbias[3], + with_tie=True, + with_dnwell=False, + with_substrate_tap=False, + with_dummy=True, + sd_route_left = bool(i), + rmult=rmult + ) + cmirrorref = nmos( + pdk, + width=half_common_source_nbias[0], + length=half_common_source_nbias[1], + fingers=half_common_source_nbias[2], + multipliers=1, + with_tie=True, + with_dnwell=False, + with_substrate_tap=False, + with_dummy=True, + sd_route_left = bool(i), + rmult=rmult + ) + cmirrorref_ref = prec_ref_center(cmirrorref) + cmirrorout_ref = prec_ref_center(cmirror_output) + # xtranslation + direction = (-1) ** i + xtranslationO = direction * abs(x_dim_center + cmirrorout_ref.xmax + pdk.util_max_metal_seperation()) + xtranslationR = direction * abs(x_dim_center + cmirrorref_ref.xmax + pdk.util_max_metal_seperation()) + xtranslationO, xtranslationR = pdk.snap_to_2xgrid([xtranslationO, xtranslationR]) + cmirrorout_ref.movex(xtranslationO) + cmirrorref_ref.movex(xtranslationR) + # ytranslation + cmirrorout_ref.movey(opamp_top.ports["diffpair_bl_multiplier_0_gate_S"].center[1]) + cmirrorref_ref.movey(cmirrorout_ref.ymin - evaluate_bbox(cmirrorref_ref)[1]/2 - pdk.util_max_metal_seperation()) + # add ports + opamp_top.add(cmirrorref_ref) + opamp_top.add(cmirrorout_ref) + side = "R" if i==0 else "L" + opamp_top.add_ports(cmirrorout_ref.get_ports_list(), prefix="commonsource_cmirror_output_"+side+"_") + opamp_top.add_ports(cmirrorref_ref.get_ports_list(), prefix="commonsource_cmirror_ref_"+side+"_") + opamp_top << straight_route(pdk, opamp_top.ports["commonsource_cmirror_output_"+side+"_tie_S_top_met_S"], opamp_top.ports["commonsource_cmirror_ref_"+side+"_tie_N_top_met_N"],width=2) + return opamp_top + +@validate_arguments +def __route_bottom_ncomps_except_drain_nbias(pdk: MappedPDK, opamp_top: Component, gndpin: Union[Component,ComponentReference], halfmultn_num_mults: int) -> tuple: + # route diff pair cmirror + opamp_top << L_route(pdk, opamp_top.ports["diffpair_ibias_purposegndport"],gndpin.ports["e1"]) + # common source + # route to gnd the sources of cmirror + _cref = opamp_top << c_route(pdk, opamp_top.ports["commonsource_cmirror_output_R_multiplier_0_source_con_S"], opamp_top.ports["commonsource_cmirror_output_L_multiplier_0_source_con_S"], extension=abs(gndpin.ports["e2"].center[1]-opamp_top.ports["commonsource_cmirror_output_R_multiplier_0_source_con_S"].center[1]),fullbottom=True) + opamp_top << straight_route(pdk, opamp_top.ports["commonsource_cmirror_ref_R_multiplier_0_source_E"],_cref.ports["con_E"],glayer2="met3",via2_alignment=('c','c')) + opamp_top << straight_route(pdk, opamp_top.ports["commonsource_cmirror_ref_L_multiplier_0_source_W"],_cref.ports["con_W"],glayer2="met3",via2_alignment=('c','c')) + # connect cmirror ref drain to cmirror output gate, then short cmirror ref drain and gate + Ldrainport = opamp_top.ports["commonsource_cmirror_ref_L_multiplier_0_drain_W"] + Lgateport = opamp_top.ports["commonsource_cmirror_output_L_multiplier_0_gate_W"] + Rdrainport = opamp_top.ports["commonsource_cmirror_ref_R_multiplier_0_drain_E"] + Rgateport = opamp_top.ports["commonsource_cmirror_output_R_multiplier_0_gate_E"] + extension = max(abs(opamp_top.xmin-Ldrainport.center[0]),abs(opamp_top.xmin-Lgateport.center[0])) + 2*pdk.util_max_metal_seperation() + draintogate_L = opamp_top << c_route(pdk, Ldrainport, Lgateport, extension=extension, width2=Lgateport.width) + draintogate_R = opamp_top << c_route(pdk, Rdrainport, Rgateport, extension=extension, width2=Rgateport.width) + Lcmirrorrefgate = opamp_top.ports["commonsource_cmirror_ref_L_multiplier_0_gate_W"] + Rcmirrorrefgate = opamp_top.ports["commonsource_cmirror_ref_R_multiplier_0_gate_E"] + opamp_top << L_route(pdk, Lcmirrorrefgate, draintogate_L.ports["con_S"]) + opamp_top << L_route(pdk, Rcmirrorrefgate, draintogate_R.ports["con_S"]) + # connect gates and drains of cmirror output + halfMultn_left_gate_port = opamp_top.ports["commonsource_cmirror_output_R_multiplier_"+str(halfmultn_num_mults-2)+"_gate_con_N"] + halfMultn_right_gate_port = opamp_top.ports["commonsource_cmirror_output_L_multiplier_"+str(halfmultn_num_mults-2)+"_gate_con_N"] + halfmultn_gate_routeref = opamp_top << c_route(pdk, halfMultn_left_gate_port, halfMultn_right_gate_port, extension=abs(opamp_top.ymax-halfMultn_left_gate_port.center[1])+1,fullbottom=True, viaoffset=(False,False)) + halfMultn_left_drain_port = opamp_top.ports["commonsource_cmirror_output_R_multiplier_"+str(halfmultn_num_mults-2)+"_drain_con_N"] + halfMultn_right_drain_port = opamp_top.ports["commonsource_cmirror_output_L_multiplier_"+str(halfmultn_num_mults-2)+"_drain_con_N"] + halfmultn_drain_routeref = opamp_top << c_route(pdk, halfMultn_left_drain_port, halfMultn_right_drain_port, extension=abs(opamp_top.ymax-halfMultn_left_drain_port.center[1])+1,fullbottom=True) + # route to gnd the guardring of cmirror output + opamp_top << straight_route(pdk,opamp_top.ports["commonsource_cmirror_ref_R_tie_S_top_met_S"],movey(gndpin.ports["e1"],evaluate_bbox(gndpin)[1]/4),width=2,glayer1="met3",fullbottom=True) + opamp_top << straight_route(pdk,opamp_top.ports["commonsource_cmirror_ref_L_tie_S_top_met_S"],movey(gndpin.ports["e3"],evaluate_bbox(gndpin)[1]/4),width=2,glayer1="met3",fullbottom=True) + # diffpair + # route source of diffpair to drain of diffpair cmirror + opamp_top << L_route(pdk,opamp_top.ports["diffpair_source_routeW_con_N"],opamp_top.ports["diffpair_ibias_B_drain_W"]) + opamp_top << L_route(pdk,opamp_top.ports["diffpair_source_routeE_con_N"],opamp_top.ports["diffpair_ibias_B_drain_E"]) + return opamp_top, halfmultn_drain_routeref, halfmultn_gate_routeref, _cref + + + +@validate_arguments +def __create_sharedgatecomps(pdk: MappedPDK, rmult: int) -> tuple: + # add diffpair current mirror loads (this is a pmos current mirror split into 2 for better matching/compensation) + shared_gate_comps = Component("shared gate components") + # create the 2*2 multiplier transistors (placed twice later) + twomultpcomps = Component("2 multiplier shared gate comps") + pcompR = multiplier(pdk, "p+s/d", width=6, length=1, fingers=6, dummy=(False, True),rmult=rmult) + pcompL = multiplier(pdk, "p+s/d", width=6, length=1, fingers=6, dummy=(True, False),rmult=rmult) + pcomp_AB_spacing = max(2*pdk.util_max_metal_seperation() + 6*pdk.get_grule("met4")["min_width"],pdk.get_grule("p+s/d")["min_separation"]) + _prefL = (twomultpcomps << pcompL).movex(-1 * pcompL.xmax - pcomp_AB_spacing/2) + _prefR = (twomultpcomps << pcompR).movex(-1 * pcompR.xmin + pcomp_AB_spacing/2) + twomultpcomps.add_ports(_prefL.get_ports_list(),prefix="L_") + twomultpcomps.add_ports(_prefR.get_ports_list(),prefix="R_") + twomultpcomps << route_quad(_prefL.ports["gate_W"], _prefR.ports["gate_E"], layer=pdk.get_glayer("met2")) + # center + relative_dim_comp = multiplier( + pdk, "p+s/d", width=6, length=1, fingers=4, dummy=False, rmult=rmult + ) + # TODO: figure out single dim spacing rule then delete both test delete and this + single_dim = to_decimal(relative_dim_comp.xmax) + to_decimal(0.11) + LRplusdopedPorts = list() + LRgatePorts = list() + LRdrainsPorts = list() + LRsourcesPorts = list() + for i in [-2, -1, 1, 2]: + dummy = False + extra_t = 0 + if i == -2: + dummy = [True, False] + pcenterfourunits = multiplier( + pdk, "p+s/d", width=6, length=1, fingers=4, dummy=dummy, rmult=rmult + ) + extra_t = -1 * single_dim + elif i == 2: + dummy = [False, True] + pcenterfourunits = multiplier( + pdk, "p+s/d", width=6, length=1, fingers=4, dummy=dummy, rmult=rmult + ) + extra_t = single_dim + else: + pcenterfourunits = relative_dim_comp + pref_ = (shared_gate_comps << pcenterfourunits).movex(pdk.snap_to_2xgrid(to_float(i * single_dim + extra_t))) + LRplusdopedPorts += [pref_.ports["plusdoped_W"] , pref_.ports["plusdoped_E"]] + LRgatePorts += [pref_.ports["gate_W"],pref_.ports["gate_E"]] + LRdrainsPorts += [pref_.ports["source_W"],pref_.ports["source_E"]] + LRsourcesPorts += [pref_.ports["drain_W"],pref_.ports["drain_E"]] + # combine the two multiplier top and bottom with the 4 multiplier center row + ytranslation_pcenter = 2 * pcenterfourunits.ymax + 5*pdk.util_max_metal_seperation() + ptop_AB = (shared_gate_comps << twomultpcomps).movey(ytranslation_pcenter) + pbottom_AB = (shared_gate_comps << twomultpcomps).movey(-1 * ytranslation_pcenter) + return shared_gate_comps, ptop_AB, pbottom_AB, LRplusdopedPorts, LRgatePorts, LRdrainsPorts, LRsourcesPorts + +def __route_sharedgatecomps(pdk: MappedPDK, shared_gate_comps, via_location, ptop_AB, pbottom_AB, LRplusdopedPorts, LRgatePorts, LRdrainsPorts, LRsourcesPorts) -> Component: + _max_metal_seperation_ps = pdk.util_max_metal_seperation() + # connect p+s/d layer of the transistors + shared_gate_comps << route_quad(LRplusdopedPorts[0],LRplusdopedPorts[-1],layer=pdk.get_glayer("p+s/d")) + # connect drain of the left 2 and right 2, short sources of all 4 + shared_gate_comps << route_quad(LRdrainsPorts[0],LRdrainsPorts[3],layer=LRdrainsPorts[0].layer) + shared_gate_comps << route_quad(LRdrainsPorts[4],LRdrainsPorts[7],layer=LRdrainsPorts[0].layer) + shared_gate_comps << route_quad(LRsourcesPorts[0],LRsourcesPorts[-1],layer=LRsourcesPorts[0].layer) + pcomps_2L_2R_sourcevia = shared_gate_comps << via_stack(pdk,pdk.layer_to_glayer(LRsourcesPorts[0].layer), "met4") + pcomps_2L_2R_sourcevia.movey(evaluate_bbox(pcomps_2L_2R_sourcevia.parent.extract(layers=[LRsourcesPorts[0].layer,]))[1]/2 + LRsourcesPorts[0].center[1]) + shared_gate_comps.add_ports(pcomps_2L_2R_sourcevia.get_ports_list(),prefix="2L2Rsrcvia_") + # short all the gates + shared_gate_comps << route_quad(LRgatePorts[0],LRgatePorts[-1],layer=pdk.get_glayer("met2")) + shared_gate_comps.add_ports(ptop_AB.get_ports_list(),prefix="ptopAB_") + shared_gate_comps.add_ports(pbottom_AB.get_ports_list(),prefix="pbottomAB_") + # short all gates of shared_gate_comps + pcenter_gate_route_extension = pdk.snap_to_2xgrid(shared_gate_comps.xmax - min(ptop_AB.ports["R_gate_E"].center[0], LRgatePorts[-1].center[0]) - pdk.get_grule("active_diff")["min_width"]) + pcenter_l_croute = shared_gate_comps << c_route(pdk, ptop_AB.ports["L_gate_W"], pbottom_AB.ports["L_gate_W"],extension=pcenter_gate_route_extension) + pcenter_r_croute = shared_gate_comps << c_route(pdk, ptop_AB.ports["R_gate_E"], pbottom_AB.ports["R_gate_E"],extension=pcenter_gate_route_extension) + shared_gate_comps << straight_route(pdk, LRgatePorts[0], pcenter_l_croute.ports["con_N"]) + shared_gate_comps << straight_route(pdk, LRgatePorts[-1], pcenter_r_croute.ports["con_N"]) + # connect drain of A to the shorted gates + shared_gate_comps << L_route(pdk,ptop_AB.ports["L_source_W"],pcenter_l_croute.ports["con_N"]) + shared_gate_comps << straight_route(pdk,pbottom_AB.ports["R_source_E"],pcenter_r_croute.ports["con_N"]) + # connect source of A to the drain of 2L + pcomps_route_A_drain_extension = shared_gate_comps.xmax-max(ptop_AB.ports["R_drain_E"].center[0], LRdrainsPorts[-1].center[0])+_max_metal_seperation_ps + pcomps_route_A_drain = shared_gate_comps << c_route(pdk, ptop_AB.ports["L_drain_W"], LRdrainsPorts[0], extension=pcomps_route_A_drain_extension) + row_rectangle_routing = rectangle(layer=ptop_AB.ports["L_drain_W"].layer,size=(pbottom_AB.ports["R_source_N"].width,pbottom_AB.ports["R_source_W"].width)).copy() + Aextra_top_connection = align_comp_to_port(row_rectangle_routing, pbottom_AB.ports["R_source_N"], ('c','t')).movey(row_rectangle_routing.ymax + _max_metal_seperation_ps) + shared_gate_comps.add(Aextra_top_connection) + shared_gate_comps << straight_route(pdk,Aextra_top_connection.ports["e4"],pbottom_AB.ports["R_drain_N"]) + shared_gate_comps << L_route(pdk,pcomps_route_A_drain.ports["con_S"], Aextra_top_connection.ports["e1"],viaoffset=(False,True)) + # connect source of B to drain of 2R + pcomps_route_B_source_extension = shared_gate_comps.xmax-max(LRsourcesPorts[-1].center[0],ptop_AB.ports["R_source_E"].center[0])+_max_metal_seperation_ps + mimcap_connection_ref = shared_gate_comps << c_route(pdk, ptop_AB.ports["R_source_E"], LRdrainsPorts[-1],extension=pcomps_route_B_source_extension,viaoffset=(True,False)) + bottom_pcompB_floating_port = set_port_orientation(movey(movex(pbottom_AB.ports["L_source_E"].copy(),5*_max_metal_seperation_ps), destination=Aextra_top_connection.ports["e1"].center[1]+Aextra_top_connection.ports["e1"].width+_max_metal_seperation_ps),"S") + pmos_bsource_2Rdrain_v = shared_gate_comps << L_route(pdk,pbottom_AB.ports["L_source_E"],bottom_pcompB_floating_port,vglayer="met3") + shared_gate_comps << c_route(pdk, LRdrainsPorts[-1], set_port_orientation(bottom_pcompB_floating_port,"E"),extension=pcomps_route_B_source_extension,viaoffset=(True,False)) + pmos_bsource_2Rdrain_v_center = via_stack(pdk,"met2","met3",fulltop=True) + shared_gate_comps.add(align_comp_to_port(pmos_bsource_2Rdrain_v_center, bottom_pcompB_floating_port,('r','t'))) + # connect drain of B to each other directly over where the diffpair top left drain will be + pmos_bdrain_diffpair_v = shared_gate_comps << via_stack(pdk, "met2","met5",fullbottom=True) + pmos_bdrain_diffpair_v = align_comp_to_port(pmos_bdrain_diffpair_v, movex(pbottom_AB.ports["L_gate_S"].copy(),destination=via_location)) + pmos_bdrain_diffpair_v.movey(0-_max_metal_seperation_ps) + pcomps_route_B_drain_extension = shared_gate_comps.xmax-ptop_AB.ports["R_drain_E"].center[0]+_max_metal_seperation_ps + shared_gate_comps << c_route(pdk, ptop_AB.ports["R_drain_E"], pmos_bdrain_diffpair_v.ports["bottom_met_E"],extension=pcomps_route_B_drain_extension +_max_metal_seperation_ps) + shared_gate_comps << c_route(pdk, pbottom_AB.ports["L_drain_W"], pmos_bdrain_diffpair_v.ports["bottom_met_W"],extension=pcomps_route_B_drain_extension +_max_metal_seperation_ps) + shared_gate_comps.add_ports(pmos_bdrain_diffpair_v.get_ports_list(),prefix="minusvia_") + shared_gate_comps.add_ports(mimcap_connection_ref.get_ports_list(),prefix="mimcap_connection_") + return shared_gate_comps + +def __add_common_source_Pamp_and_finish_pcomps(pdk: MappedPDK, pmos_comps: Component, pamp_hparams, rmult) -> Component: + x_dim_center = max(abs(pmos_comps.xmax),abs(pmos_comps.xmin)) + for direction in [-1, 1]: + halfMultp = pmos( + pdk, + width=pamp_hparams[0], + length=pamp_hparams[1], + fingers=pamp_hparams[2], + multipliers=pamp_hparams[3], + with_tie=True, + dnwell=False, + with_substrate_tap=False, + sd_route_left=bool(direction-1), + rmult=rmult + ) + halfMultp_ref = pmos_comps << halfMultp + halfMultp_ref.movex(direction * abs(x_dim_center + halfMultp_ref.xmax+1)) + label = "L_" if direction==-1 else "R_" + # this special marker is used to rename these ports in the opamp to commonsource_Pamp_ + pmos_comps.add_ports(halfMultp_ref.get_ports_list(),prefix="halfpspecialmarker_"+label) + # add npadding and add ports + nwellbbox = pmos_comps.extract(layers=[pdk.get_glayer("poly"),pdk.get_glayer("active_diff"),pdk.get_glayer("active_tap"), pdk.get_glayer("nwell"),pdk.get_glayer("dnwell")]).bbox + nwellspacing = pdk.get_grule("nwell", "active_tap")["min_enclosure"] + nwell_points = get_padding_points_cc(nwellbbox, default=nwellspacing, pdk_for_snap2xgrid=pdk) + pmos_comps.add_polygon(nwell_points, layer=pdk.get_glayer("nwell")) + tapcenter_rect = [(evaluate_bbox(pmos_comps)[0] + 1), (evaluate_bbox(pmos_comps)[1] + 1)] + topptap = prec_ref_center(tapring(pdk, tapcenter_rect, "p+s/d"),destination=tuple(pmos_comps.center)) + pmos_comps.add(topptap) + pmos_comps.add_ports(topptap.get_ports_list(),prefix="top_ptap_") + return pmos_comps + + + +@validate_arguments +def __create_and_route_pins( + pdk: MappedPDK, + opamp_top: Component, + pmos_comps_ref: ComponentReference, + halfmultn_drain_routeref: ComponentReference, + halfmultn_gate_routeref: ComponentReference +) -> tuple: + _max_metal_seperation_ps = pdk.util_max_metal_seperation() + # route halfmultp source, drain, and gate together, place vdd pin in the middle + halfmultp_Lsrcport = opamp_top.ports["commonsource_Pamp_L_multiplier_0_source_con_N"] + halfmultp_Rsrcport = opamp_top.ports["commonsource_Pamp_R_multiplier_0_source_con_N"] + opamp_top << c_route(pdk, halfmultp_Lsrcport, halfmultp_Rsrcport, extension=opamp_top.ymax-halfmultp_Lsrcport.center[1], fullbottom=True,viaoffset=(False,False)) + # place vdd pin + vddpin = opamp_top << rectangle(size=(5,3),layer=pdk.get_glayer("met4"),centered=True) + vddpin.movey(opamp_top.ymax) + # route vdd to source of 2L/2R + opamp_top << straight_route(pdk, opamp_top.ports["pcomps_2L2Rsrcvia_top_met_N"], vddpin.ports["e4"]) + # drain route above vdd pin + halfmultp_Ldrainport = opamp_top.ports["commonsource_Pamp_L_multiplier_0_drain_con_N"] + halfmultp_Rdrainport = opamp_top.ports["commonsource_Pamp_R_multiplier_0_drain_con_N"] + halfmultp_drain_routeref = opamp_top << c_route(pdk, halfmultp_Ldrainport, halfmultp_Rdrainport, extension=opamp_top.ymax-halfmultp_Ldrainport.center[1]+pdk.get_grule("met5")["min_separation"], fullbottom=True) + halfmultp_Lgateport = opamp_top.ports["commonsource_Pamp_L_multiplier_0_gate_con_S"] + halfmultp_Rgateport = opamp_top.ports["commonsource_Pamp_R_multiplier_0_gate_con_S"] + ptop_halfmultp_gate_route = opamp_top << c_route(pdk, halfmultp_Lgateport, halfmultp_Rgateport, extension=abs(pmos_comps_ref.ymin-halfmultp_Lgateport.center[1])+pdk.get_grule("met5")["min_separation"],fullbottom=True,viaoffset=(False,False)) + # halfmultn to halfmultp drain to drain route + extensionL = min(halfmultn_drain_routeref.ports["con_W"].center[0],halfmultp_drain_routeref.ports["con_W"].center[0]) + extensionR = max(halfmultn_drain_routeref.ports["con_E"].center[0],halfmultp_drain_routeref.ports["con_E"].center[0]) + opamp_top << c_route(pdk, halfmultn_drain_routeref.ports["con_W"], halfmultp_drain_routeref.ports["con_W"],extension=abs(opamp_top.xmin-extensionL)+2,cwidth=2) + n_to_p_output_route = opamp_top << c_route(pdk, halfmultn_drain_routeref.ports["con_E"], halfmultp_drain_routeref.ports["con_E"],extension=abs(opamp_top.xmax-extensionR)+2,cwidth=2) + # top nwell taps to vdd, top p substrate taps to gnd + opamp_top << L_route(pdk, opamp_top.ports["pcomps_top_ptap_bl_top_met_S"], opamp_top.ports["commonsource_cmirror_output_L_tie_N_top_met_W"],hwidth=2) + opamp_top << L_route(pdk, opamp_top.ports["pcomps_top_ptap_br_top_met_S"], opamp_top.ports["commonsource_cmirror_output_R_tie_N_top_met_E"],hwidth=2) + L_toptapn_route = opamp_top.ports["commonsource_Pamp_L_tie_N_top_met_N"] + R_toptapn_route = opamp_top.ports["commonsource_Pamp_R_tie_N_top_met_N"] + opamp_top << straight_route(pdk, movex(vddpin.ports["e4"],destination=L_toptapn_route.center[0]), L_toptapn_route, glayer1="met3") + opamp_top << straight_route(pdk, movex(vddpin.ports["e4"],destination=R_toptapn_route.center[0]), R_toptapn_route, glayer1="met3") + # bias pins for first two stages + vbias1 = opamp_top << rectangle(size=(5,3),layer=pdk.get_glayer("met3"),centered=True) + vbias1.movey(opamp_top.ymin - _max_metal_seperation_ps - vbias1.ymax) + opamp_top << straight_route(pdk, vbias1.ports["e2"], opamp_top.ports["diffpair_ibias_B_gate_S"],width=1,fullbottom=False) + vbias2 = opamp_top << rectangle(size=(5,3),layer=pdk.get_glayer("met5"),centered=True) + vbias2.movex(1+opamp_top.xmax+evaluate_bbox(vbias2)[0]+pdk.util_max_metal_seperation()).movey(opamp_top.ymin+vbias2.ymax) + opamp_top << L_route(pdk, halfmultn_gate_routeref.ports["con_E"], vbias2.ports["e2"],hwidth=2) + # out pin + #output = opamp_top << rectangle(size=(5,3),layer=pdk.get_glayer("met5"),centered=True) + #output.movex(opamp_top.xmax).movey(opamp_top.ymin+output.ymax) + #opamp_top << L_route(pdk, output.ports["e2"], set_port_orientation(n_to_p_output_route.ports["con_S"],"E")) + # route + and - pins + plus_pin = opamp_top << rectangle(size=(5,2),layer=pdk.get_glayer("met4"),centered=True) + plus_pin.movex(opamp_top.xmin).movey(_max_metal_seperation_ps + plus_pin.ymax + halfmultn_drain_routeref.ports["con_W"].center[1] + halfmultn_drain_routeref.ports["con_W"].width/2) + route_to_pluspin = opamp_top << L_route(pdk, opamp_top.ports["diffpair_MINUSgateroute_W_con_N"], plus_pin.ports["e3"]) + minus_pin = opamp_top << rectangle(size=(5,2),layer=pdk.get_glayer("met4"),centered=True) + minus_pin.movex(opamp_top.xmin + minus_pin.xmax).movey(_max_metal_seperation_ps + plus_pin.ymax + minus_pin.ymax) + opamp_top << L_route(pdk, opamp_top.ports["diffpair_PLUSgateroute_E_con_N"], minus_pin.ports["e3"]) + # route top center components to diffpair + opamp_top << straight_route(pdk,opamp_top.ports["diffpair_tr_multiplier_0_drain_N"], opamp_top.ports["pcomps_pbottomAB_R_gate_S"], glayer1="met5",width=3*pdk.get_grule("met5")["min_width"],via1_alignment_layer="met2",via1_alignment=('c','c')) + opamp_top << straight_route(pdk,opamp_top.ports["diffpair_tl_multiplier_0_drain_N"], opamp_top.ports["pcomps_minusvia_top_met_S"], glayer1="met5",width=3*pdk.get_grule("met5")["min_width"],via1_alignment_layer="met2",via1_alignment=('c','c')) + # route minus transistor drain to output + outputvia_diff_pcomps = opamp_top << via_stack(pdk,"met5","met4") + outputvia_diff_pcomps.movex(opamp_top.ports["diffpair_tl_multiplier_0_drain_N"].center[0]).movey(ptop_halfmultp_gate_route.ports["con_E"].center[1]) + # add pin ports + opamp_top.add_ports(vddpin.get_ports_list(), prefix="pin_vdd_") + opamp_top.add_ports(vbias1.get_ports_list(), prefix="pin_diffpairibias_") + opamp_top.add_ports(vbias2.get_ports_list(), prefix="pin_commonsourceibias_") + opamp_top.add_ports(plus_pin.get_ports_list(), prefix="pin_plus_") + opamp_top.add_ports(minus_pin.get_ports_list(), prefix="pin_minus_") + #opamp_top.add_ports(output.get_ports_list(), prefix="pin_output_") + return opamp_top, n_to_p_output_route + + + +@validate_arguments +def __add_mimcap_arr(pdk: MappedPDK, opamp_top: Component, mim_cap_size, mim_cap_rows, ymin: float, n_to_p_output_route) -> Component: + mim_cap_size = pdk.snap_to_2xgrid(mim_cap_size, return_type="float") + max_metalsep = pdk.util_max_metal_seperation() + mimcaps_ref = opamp_top << mimcap_array(pdk,mim_cap_rows,2,size=mim_cap_size,rmult=6) + displace_fact = max(max_metalsep,pdk.get_grule("capmet")["min_separation"]) + mimcaps_ref.movex(pdk.snap_to_2xgrid(opamp_top.xmax + displace_fact + mim_cap_size[0]/2)) + mimcaps_ref.movey(pdk.snap_to_2xgrid(ymin + mim_cap_size[1]/2)) + # connect mimcap to gnd + port1 = opamp_top.ports["pcomps_mimcap_connection_con_N"] + port2 = mimcaps_ref.ports["row"+str(int(mim_cap_rows)-1)+"_col0_bottom_met_N"] + cref2_extension = max_metalsep + opamp_top.ymax - max(port1.center[1], port2.center[1]) + opamp_top << c_route(pdk,port1,port2, extension=cref2_extension, fullbottom=True) + intermediate_output = set_port_orientation(n_to_p_output_route.ports["con_S"],"E") + opamp_top << L_route(pdk, mimcaps_ref.ports["row0_col0_top_met_S"], intermediate_output, hwidth=3) + opamp_top.add_ports(mimcaps_ref.get_ports_list(),prefix="mimcap_") + # add the cs output as a port + opamp_top.add_port(name="commonsource_output_E", port=intermediate_output) + return opamp_top + + + + +@validate_arguments +def __add_output_stage( + pdk: MappedPDK, + opamp_top: Component, + amplifierParams: tuple[float, float, int], + biasParams: list, + rmult: int, + n_to_p_output_route: Union[Component, ComponentReference] +) -> Component: + '''add output stage to opamp_top, args: + pdk = pdk to use + opamp_top = component to add output stage to + amplifierParams = [width,length,fingers,mults] for amplifying FET + biasParams = [width,length,fingers,mults] for bias FET + ''' + # Instantiate output amplifier + amp_fet_ref = opamp_top << nmos( + pdk, + width=amplifierParams[0], + length=amplifierParams[1], + fingers=amplifierParams[2], + multipliers=1, + sd_route_topmet="met3", + gate_route_topmet="met3", + rmult=rmult, + with_dnwell=False + ) + # Instantiate bias FET + cmirror_ibias = opamp_top << two_nfet_interdigitized( + pdk, + numcols=biasParams[2], + width=biasParams[0], + length=biasParams[1], + fingers=1, + gate_route_topmet="met3", + sd_route_topmet="met3", + rmult=rmult + ) + metal_sep = pdk.util_max_metal_seperation() + # Locate output stage relative position + # x-coordinate: Center of SW capacitor in array + # y-coordinate: Top of NMOS blocks + xref_port = opamp_top.ports["mimcap_row0_col0_bottom_met_S"] + x_cord = xref_port.center[0] - xref_port.width/2 + y_cord = opamp_top.ports["commonsource_cmirror_output_R_tie_tr_top_met_N"].center[1] + dims = evaluate_bbox(amp_fet_ref) + center = [x_cord + dims[0]/2, y_cord - dims[1]/2] + amp_fet_ref.move(center) + amp_fet_ref.movey(pdk.get_grule("active_tap", "p+s/d")["min_enclosure"]) + dims = evaluate_bbox(cmirror_ibias) + cmirror_ibias.movex(amp_fet_ref.xmin + dims[0]/2) + cmirror_ibias.movey(amp_fet_ref.ymin - dims[1]/2 - metal_sep) + # route input of output_stage to output of previous stage + opamp_top << L_route(pdk, n_to_p_output_route.ports["con_S"], amp_fet_ref.ports["multiplier_0_gate_W"]) + # route drain of amplifier to vdd + opamp_top << L_route(pdk, opamp_top.ports["commonsource_Pamp_R_tie_bl_top_met_W"], amp_fet_ref.ports["multiplier_0_drain_N"],hwidth=2) + # route drain of cmirror to source of amplifier + opamp_top << c_route(pdk, cmirror_ibias.ports["B_drain_E"],amp_fet_ref.ports["multiplier_0_source_E"],extension=metal_sep) + # route cmirror: A gate, B gate and A drain together. Then A source and B source to ground + gate_short = opamp_top << c_route(pdk, cmirror_ibias.ports["A_gate_E"],cmirror_ibias.ports["B_gate_E"],extension=3*metal_sep,viaoffset=None) + opamp_top << L_route(pdk, gate_short.ports["con_N"],cmirror_ibias.ports["A_drain_E"],viaoffset=False,fullbottom=False) + srcshort = opamp_top << c_route(pdk, cmirror_ibias.ports["A_source_W"],cmirror_ibias.ports["B_source_W"],extension=metal_sep) + opamp_top << straight_route(pdk, srcshort.ports["con_N"], cmirror_ibias.ports["welltie_N_top_met_S"],via2_alignment_layer="met2") + # Route all tap rings together and ground them + opamp_top << straight_route(pdk, amp_fet_ref.ports["tie_N_top_met_N"],amp_fet_ref.ports["guardring_N_top_met_S"],width=2) + opamp_top << straight_route(pdk, cmirror_ibias.ports["welltie_N_top_met_N"],cmirror_ibias.ports["substratetap_N_top_met_S"],width=2) + opamp_top << straight_route(pdk, amp_fet_ref.ports["guardring_bl_top_met_S"],cmirror_ibias.ports["substratetap_tr_top_met_N"]) + opamp_top << straight_route(pdk, amp_fet_ref.ports["guardring_tl_top_met_W"], opamp_top.ports["commonsource_cmirror_output_R_tie_tr_top_met_E"]) + # add ports, add bias/output pin, and return + psuedo_out_port = movex(amp_fet_ref.ports["multiplier_0_source_E"].copy(),6*metal_sep) + output_pin = opamp_top << straight_route(pdk, amp_fet_ref.ports["multiplier_0_source_E"], psuedo_out_port) + opamp_top.add_ports(amp_fet_ref.get_ports_list(),prefix="outputstage_amp_") + opamp_top.add_ports(cmirror_ibias.get_ports_list(),prefix="outputstage_bias_") + opamp_top.add_ports(output_pin.get_ports_list(),prefix="pin_output_") + bias_pin = opamp_top << rectangle(size=(5,3),layer=pdk.get_glayer("met3"),centered=True) + bias_pin.movex(cmirror_ibias.center[0]).movey(cmirror_ibias.ports["B_gate_S"].center[1]-bias_pin.ymax-5*metal_sep) + opamp_top << straight_route(pdk, bias_pin.ports["e2"], cmirror_ibias.ports["B_gate_S"],width=1) + opamp_top.add_ports(bias_pin.get_ports_list(),prefix="pin_outputibias_") + return opamp_top + + +@cell +def opamp( + pdk: MappedPDK, + diffpair_params: tuple[float, float, int] = (6, 1, 4), + diffpair_bias: tuple[float, float, int] = (6, 2, 4), + half_common_source_params: tuple[float, float, int, int] = (7, 1, 10, 3), + half_common_source_bias: tuple[float, float, int, int] = (6, 2, 8, 2), + output_stage_params: tuple[float, float, int] = (5, 1, 16), + output_stage_bias: tuple[float, float, int] = (6, 2, 4), + mim_cap_size=(12, 12), + mim_cap_rows=3, + rmult: int = 2 +) -> Component: + """create an opamp, args: + pdk=pdk to use + diffpair_params = diffpair (width,length,fingers) + diffpair_bias = bias transistor for diffpair nmos (width,length,fingers). The ref and output of the cmirror are identical + half_common_source_params = pmos top component amp (width,length,fingers,mults) + half_common_source_bias = bottom L/R large nmos current mirror (width,length,fingers,mults). The ref of the cmirror always has 1 multplier. multiplier must be >=2 + ****NOTE: change the multiplier option to change the relative sizing of the current mirror ref/output + output_stage_amp_params = output amplifier transistor params (width, length, fingers) + output_stage_bias = output amplifier current mirror params (width, length, fingers). The ref and output of the cmirror are identical + mim_cap_size = width,length of individual mim_cap + mim_cap_rows = number of rows in the mimcap array (always 2 cols) + rmult = routing multiplier (larger = wider routes) + """ + # error checks + if half_common_source_bias[3] < 2: + raise ValueError("half_common_source_bias num multiplier must be >= 2") + # create opamp top component + _max_metal_seperation_ps = pdk.util_max_metal_seperation() + opamp_top = Component() + # place nmos components + clear_cache() + diffpair_and_bias = __add_diff_pair_and_bias(pdk, opamp_top, diffpair_params, diffpair_bias, rmult) + # create and position each half of the nmos bias transistor for the common source stage symetrically + clear_cache() + opamp_top = __add_common_source_nbias_transistors(pdk, opamp_top, half_common_source_bias, rmult) + opamp_top.add_padding(layers=(pdk.get_glayer("pwell"),),default=0) + # add ground pin + gndpin = opamp_top << rectangle(size=(5,3),layer=pdk.get_glayer("met4"),centered=True) + gndpin.movey(pdk.snap_to_2xgrid(opamp_top.ymin-pdk.util_max_metal_seperation()-gndpin.ymax)) + # route bottom ncomps except drain of nbias (still need to place common source pmos amp) + clear_cache() + opamp_top, halfmultn_drain_routeref, halfmultn_gate_routeref, _cref = __route_bottom_ncomps_except_drain_nbias(pdk, opamp_top, gndpin, half_common_source_bias[3]) + opamp_top.add_ports(gndpin.get_ports_list(), prefix="pin_gnd_") + # place pmos components + #TODO: report as bug + clear_cache() + pmos_comps, ptop_AB, pbottom_AB, LRplusdopedPorts, LRgatePorts, LRdrainsPorts, LRsourcesPorts = __create_sharedgatecomps(pdk, rmult) + clear_cache() + pmos_comps = __route_sharedgatecomps(pdk, pmos_comps, opamp_top.ports["diffpair_tl_multiplier_0_drain_N"].center[0], ptop_AB, pbottom_AB, LRplusdopedPorts, LRgatePorts, LRdrainsPorts, LRsourcesPorts) + clear_cache() + pmos_comps = __add_common_source_Pamp_and_finish_pcomps(pdk, pmos_comps, half_common_source_params, rmult) + ydim_ncomps = opamp_top.ymax + pmos_comps_ref = opamp_top << pmos_comps + pmos_comps_ref.movey(round(ydim_ncomps + pmos_comps_ref.ymax+10)) + opamp_top.add_ports(pmos_comps_ref.get_ports_list(),prefix="pcomps_") + rename_func = lambda name_, port_ : name_.replace("pcomps_halfpspecialmarker","commonsource_Pamp") if name_.startswith("pcomps_halfpspecialmarker") else name_ + opamp_top = rename_component_ports(opamp_top, rename_function=rename_func) + # create pins and route + clear_cache() + opamp_top, n_to_p_output_route = __create_and_route_pins(pdk, opamp_top, pmos_comps_ref, halfmultn_drain_routeref, halfmultn_gate_routeref) + # place mimcaps and route + clear_cache() + opamp_top = __add_mimcap_arr(pdk, opamp_top, mim_cap_size, mim_cap_rows, pmos_comps_ref.ymin, n_to_p_output_route) + # add output amplfier stage + opamp_top = __add_output_stage(pdk, opamp_top, output_stage_params, output_stage_bias, rmult, n_to_p_output_route) + # return + opamp_top.add_ports(_cref.get_ports_list(), prefix="gnd_route_") + return rename_ports_by_orientation(component_snap_to_grid(opamp_top)) + + +if __name__ == "__main__": + from . pdk.util.standard_main import pdk + + iterate=False + # TO TRY: + #pdk = pdk to use + #diffpair_params = diffpair (width,length,fingers) + #diffpair_bias = bias transistor for diffpair nmos (width,length,fingers) + #half_common_source_nbias = west current mirror (width,length,fingers,mults), two halves + #pamp_hparams = pmos top component amp (width,length,fingers,mults) + #mim_cap_size = width,length of individual mim_cap + if iterate: # 486 versions + # construct all diffpairs to try + diffpairs = list() + for width in [4,6,8]: + for length in [0,1]: + for fingers in [3,4,5]: + diffpairs.append((width,length,fingers)) + # construct all bias1 (diffpair bias) transistors to try + bias1s = list() + for width in [4,6,8]: + for length in [1,2,4]: + for fingers in [3,4,5]: + bias1s.append((width,length,fingers)) + cap_arrays = [1,3] + opamps = list() + for diffpair_v in diffpairs: + for bias1_v in bias1s: + for cap_array_v in cap_arrays: + comp = opamp(pdk,diffpair_params=diffpair_v,diffpair_bias=bias1_v,mim_cap_rows=cap_array_v) + opamps.append(comp) + for i,comp in enumerate(opamps): + comp.write_gds(str(i)+".gds") + else: + opamp( + pdk, + diffpair_params = (6, 1, 4), + diffpair_bias = (6, 2, 4), + half_common_source_nbias = (6, 2, 8, 3), + pamp_hparams = (7, 1, 10, 3), + mim_cap_size = (12, 12), + mim_cap_rows = 3, + rmult = 2 + ).show() + + + +def benchmark(pdk: MappedPDK, save_file: Optional[str]="./oPamp_Runtime_second.txt") -> float: + """get runtime of opamp in seconds (note running this with sky130 results in longer runtime due to addition of NPC)""" + import time + start = time.time() + opamp(pdk) + end = time.time() + elapsed_time = end - start + print(elapsed_time) + if save_file: + from pathlib import Path + save_file = Path(save_file).resolve() + try: + with open(save_file,"w") as resultfile: + resultfile.write(str(elapsed_time)) + except Exception: + print("benchmark was not able to write to savefile") + return elapsed_time diff --git a/openfasoc/generators/gdsfactory-gen/PDK/__init__.py b/openfasoc/generators/gdsfactory-gen/glayout/pdk/__init__.py similarity index 100% rename from openfasoc/generators/gdsfactory-gen/PDK/__init__.py rename to openfasoc/generators/gdsfactory-gen/glayout/pdk/__init__.py diff --git a/openfasoc/generators/gdsfactory-gen/glayout/pdk/gf180_mapped/__init__.py b/openfasoc/generators/gdsfactory-gen/glayout/pdk/gf180_mapped/__init__.py new file mode 100644 index 000000000..20dc68d90 --- /dev/null +++ b/openfasoc/generators/gdsfactory-gen/glayout/pdk/gf180_mapped/__init__.py @@ -0,0 +1,5 @@ +""" +Usage at the package level: from pdk.gf180_mapped import gf180_mapped_pdk +""" + +from glayout.pdk.gf180_mapped.gf180_mapped import gf180_mapped_pdk diff --git a/openfasoc/generators/gdsfactory-gen/glayout/pdk/gf180_mapped/gf180_mapped.py b/openfasoc/generators/gdsfactory-gen/glayout/pdk/gf180_mapped/gf180_mapped.py new file mode 100644 index 000000000..e4cab6e41 --- /dev/null +++ b/openfasoc/generators/gdsfactory-gen/glayout/pdk/gf180_mapped/gf180_mapped.py @@ -0,0 +1,51 @@ +""" +usage: from gf180_mapped import gf180_mapped_pdk +""" + +from gf180.layers import LAYER # , LAYER_VIEWS +from ..gf180_mapped.grules import grulesobj +from ..mappedpdk import MappedPDK +from pathlib import Path + +LAYER = LAYER.dict() +#LAYER["fusetop"]=(75, 0) +LAYER["CAP_MK"] = (117,5) + +gf180_glayer_mapping = { + "met5": "metal5", + "via4": "via4", + "met4": "metal4", + "via3": "via3", + "met3": "metal3", + "via2": "via2", + "met2": "metal2", + "via1": "via1", + "met1": "metal1", + "mcon": "contact", + "poly": "poly2", + "active_diff": "comp", + "active_tap": "comp", + "n+s/d": "nplus", + "p+s/d": "pplus", + "nwell": "nwell", + "pwell": "lvpwell", + "dnwell": "dnwell", + "capmet": "CAP_MK" +} + +# note for DRC, there is mim_option 'A'. This is the one configured for use + +gf180_lydrc_file_path = Path(__file__).resolve().parent / "gf180mcu_drc.lydrc" + + +gf180_mapped_pdk = MappedPDK( + name="gf180", + glayers=gf180_glayer_mapping, + layers=LAYER, + klayout_lydrc_file=gf180_lydrc_file_path, + grules=grulesobj, +) + +# configure the grid size and other settings +gf180_mapped_pdk.gds_write_settings.precision = 5*10**-9 +gf180_mapped_pdk.cell_decorator_settings.cache=False diff --git a/openfasoc/generators/gdsfactory-gen/PDK/gf180_mapped/gf180mcu.drc b/openfasoc/generators/gdsfactory-gen/glayout/pdk/gf180_mapped/gf180mcu.drc similarity index 99% rename from openfasoc/generators/gdsfactory-gen/PDK/gf180_mapped/gf180mcu.drc rename to openfasoc/generators/gdsfactory-gen/glayout/pdk/gf180_mapped/gf180mcu.drc index 7fe40b1ac..38542b8ed 100644 --- a/openfasoc/generators/gdsfactory-gen/PDK/gf180_mapped/gf180mcu.drc +++ b/openfasoc/generators/gdsfactory-gen/glayout/pdk/gf180_mapped/gf180mcu.drc @@ -1,4 +1,4 @@ -# Copyright 2022 GlobalFoundries PDK Authors +# Copyright 2022 GlobalFoundries pdk Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -453,13 +453,9 @@ else end # BEOL # connectivity rules -if $conn_drc == "true" - CONNECTIVITY_RULES = $conn_drc - logger.info("connectivity rules are enabled.") -else - CONNECTIVITY_RULES = false - logger.info("connectivity rules are disabled.") -end # connectivity rules +CONNECTIVITY_RULES = true +logger.info("connectivity rules are enabled.") + # METAL_TOP if $metal_top diff --git a/openfasoc/generators/gdsfactory-gen/PDK/gf180_mapped/gf180mcu_drc.lydrc b/openfasoc/generators/gdsfactory-gen/glayout/pdk/gf180_mapped/gf180mcu_drc.lydrc similarity index 90% rename from openfasoc/generators/gdsfactory-gen/PDK/gf180_mapped/gf180mcu_drc.lydrc rename to openfasoc/generators/gdsfactory-gen/glayout/pdk/gf180_mapped/gf180mcu_drc.lydrc index 066552ec3..f530192ec 100644 --- a/openfasoc/generators/gdsfactory-gen/PDK/gf180_mapped/gf180mcu_drc.lydrc +++ b/openfasoc/generators/gdsfactory-gen/glayout/pdk/gf180_mapped/gf180mcu_drc.lydrc @@ -11,11 +11,11 @@ true - submenu>end("gf180mcu PDK").end + submenu>end("gf180mcu pdk").end dsl drc-dsl-xml -# Copyright 2022 GlobalFoundries PDK Authors +# Copyright 2022 GlobalFoundries pdk Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/openfasoc/generators/gdsfactory-gen/glayout/pdk/gf180_mapped/grules.py b/openfasoc/generators/gdsfactory-gen/glayout/pdk/gf180_mapped/grules.py new file mode 100644 index 000000000..bf211155d --- /dev/null +++ b/openfasoc/generators/gdsfactory-gen/glayout/pdk/gf180_mapped/grules.py @@ -0,0 +1,368 @@ +from ..mappedpdk import MappedPDK + +grulesobj = dict() +for glayer in MappedPDK.valid_glayers: + grulesobj[glayer] = dict((x, None) for x in MappedPDK.valid_glayers) + +grulesobj["dnwell"]["dnwell"] = {'min_width': 1.7, 'min_separation': 5.42} +grulesobj["dnwell"]["pwell"] = {'min_enclosure': 2.5} +grulesobj["dnwell"]["nwell"] = {'min_separation': 3.1, 'min_enclosure': 0.5} +grulesobj["dnwell"]["p+s/d"] = {} +grulesobj["dnwell"]["n+s/d"] = {} +grulesobj["dnwell"]["active_diff"] = {'min_enclosure': 0.93} +grulesobj["dnwell"]["active_tap"] = {'min_enclosure': 0.62, 'min_separation': 2.5} +grulesobj["dnwell"]["poly"] = {} +grulesobj["dnwell"]["mcon"] = {} +grulesobj["dnwell"]["met1"] = {} +grulesobj["dnwell"]["via1"] = {} +grulesobj["dnwell"]["met2"] = {} +grulesobj["dnwell"]["via2"] = {} +grulesobj["dnwell"]["met3"] = {} +grulesobj["dnwell"]["via3"] = {} +grulesobj["dnwell"]["met4"] = {} +grulesobj["dnwell"]["via4"] = {} +grulesobj["dnwell"]["met5"] = {} +grulesobj["dnwell"]["capmet"] = {} +grulesobj["pwell"]["dnwell"] = {} +grulesobj["pwell"]["pwell"] = {'min_width': 0.6, 'min_separation': 1.4} +grulesobj["pwell"]["nwell"] = {'min_separation': 0.0} +grulesobj["pwell"]["p+s/d"] = {} +grulesobj["pwell"]["n+s/d"] = {} +grulesobj["pwell"]["active_diff"] = {'min_enclosure': 0.43} +grulesobj["pwell"]["active_tap"] = {'min_enclosure': 0.12} +grulesobj["pwell"]["poly"] = {} +grulesobj["pwell"]["mcon"] = {} +grulesobj["pwell"]["met1"] = {} +grulesobj["pwell"]["via1"] = {} +grulesobj["pwell"]["met2"] = {} +grulesobj["pwell"]["via2"] = {} +grulesobj["pwell"]["met3"] = {} +grulesobj["pwell"]["via3"] = {} +grulesobj["pwell"]["met4"] = {} +grulesobj["pwell"]["via4"] = {} +grulesobj["pwell"]["met5"] = {} +grulesobj["pwell"]["capmet"] = {} +grulesobj["nwell"]["dnwell"] = {} +grulesobj["nwell"]["pwell"] = {} +grulesobj["nwell"]["nwell"] = {'min_width': 0.86, 'min_separation': 1.4} +grulesobj["nwell"]["p+s/d"] = {} +grulesobj["nwell"]["n+s/d"] = {} +grulesobj["nwell"]["active_diff"] = {'min_enclosure': 0.43} +grulesobj["nwell"]["active_tap"] = {'min_enclosure': 0.12} +grulesobj["nwell"]["poly"] = {} +grulesobj["nwell"]["mcon"] = {} +grulesobj["nwell"]["met1"] = {} +grulesobj["nwell"]["via1"] = {} +grulesobj["nwell"]["met2"] = {} +grulesobj["nwell"]["via2"] = {} +grulesobj["nwell"]["met3"] = {} +grulesobj["nwell"]["via3"] = {} +grulesobj["nwell"]["met4"] = {} +grulesobj["nwell"]["via4"] = {} +grulesobj["nwell"]["met5"] = {} +grulesobj["nwell"]["capmet"] = {} +grulesobj["p+s/d"]["dnwell"] = {} +grulesobj["p+s/d"]["pwell"] = {} +grulesobj["p+s/d"]["nwell"] = {} +grulesobj["p+s/d"]["p+s/d"] = {'min_width': 0.4, 'min_separation': 0.4} +grulesobj["p+s/d"]["n+s/d"] = {} +grulesobj["p+s/d"]["active_diff"] = {'min_enclosure': 0.23} +grulesobj["p+s/d"]["active_tap"] = {'min_enclosure': 0.16} +grulesobj["p+s/d"]["poly"] = {} +grulesobj["p+s/d"]["mcon"] = {} +grulesobj["p+s/d"]["met1"] = {} +grulesobj["p+s/d"]["via1"] = {} +grulesobj["p+s/d"]["met2"] = {} +grulesobj["p+s/d"]["via2"] = {} +grulesobj["p+s/d"]["met3"] = {} +grulesobj["p+s/d"]["via3"] = {} +grulesobj["p+s/d"]["met4"] = {} +grulesobj["p+s/d"]["via4"] = {} +grulesobj["p+s/d"]["met5"] = {} +grulesobj["p+s/d"]["capmet"] = {} +grulesobj["n+s/d"]["dnwell"] = {} +grulesobj["n+s/d"]["pwell"] = {} +grulesobj["n+s/d"]["nwell"] = {} +grulesobj["n+s/d"]["p+s/d"] = {} +grulesobj["n+s/d"]["n+s/d"] = {'min_width': 0.4, 'min_separation': 0.4} +grulesobj["n+s/d"]["active_diff"] = {'min_enclosure': 0.23} +grulesobj["n+s/d"]["active_tap"] = {'min_enclosure': 0.16} +grulesobj["n+s/d"]["poly"] = {} +grulesobj["n+s/d"]["mcon"] = {} +grulesobj["n+s/d"]["met1"] = {} +grulesobj["n+s/d"]["via1"] = {} +grulesobj["n+s/d"]["met2"] = {} +grulesobj["n+s/d"]["via2"] = {} +grulesobj["n+s/d"]["met3"] = {} +grulesobj["n+s/d"]["via3"] = {} +grulesobj["n+s/d"]["met4"] = {} +grulesobj["n+s/d"]["via4"] = {} +grulesobj["n+s/d"]["met5"] = {} +grulesobj["n+s/d"]["capmet"] = {} +grulesobj["active_diff"]["dnwell"] = {} +grulesobj["active_diff"]["pwell"] = {} +grulesobj["active_diff"]["nwell"] = {} +grulesobj["active_diff"]["p+s/d"] = {} +grulesobj["active_diff"]["n+s/d"] = {} +grulesobj["active_diff"]["active_diff"] = {'min_width': 0.22, 'min_separation': 0.28} +grulesobj["active_diff"]["active_tap"] = {'min_separation': 0.28, 'max_separation': 20.0} +grulesobj["active_diff"]["poly"] = {'overhang': 0.24, 'min_separation': 0.1} +grulesobj["active_diff"]["mcon"] = {'min_enclosure': 0.07} +grulesobj["active_diff"]["met1"] = {} +grulesobj["active_diff"]["via1"] = {} +grulesobj["active_diff"]["met2"] = {} +grulesobj["active_diff"]["via2"] = {} +grulesobj["active_diff"]["met3"] = {} +grulesobj["active_diff"]["via3"] = {} +grulesobj["active_diff"]["met4"] = {} +grulesobj["active_diff"]["via4"] = {} +grulesobj["active_diff"]["met5"] = {} +grulesobj["active_diff"]["capmet"] = {} +grulesobj["active_tap"]["dnwell"] = {} +grulesobj["active_tap"]["pwell"] = {} +grulesobj["active_tap"]["nwell"] = {} +grulesobj["active_tap"]["p+s/d"] = {} +grulesobj["active_tap"]["n+s/d"] = {} +grulesobj["active_tap"]["active_diff"] = {} +grulesobj["active_tap"]["active_tap"] = {'min_width': 0.22, 'min_separation': 0.28} +grulesobj["active_tap"]["poly"] = {'min_separation': 0.1} +grulesobj["active_tap"]["mcon"] = {'min_enclosure': 0.07} +grulesobj["active_tap"]["met1"] = {} +grulesobj["active_tap"]["via1"] = {} +grulesobj["active_tap"]["met2"] = {} +grulesobj["active_tap"]["via2"] = {} +grulesobj["active_tap"]["met3"] = {} +grulesobj["active_tap"]["via3"] = {} +grulesobj["active_tap"]["met4"] = {} +grulesobj["active_tap"]["via4"] = {} +grulesobj["active_tap"]["met5"] = {} +grulesobj["active_tap"]["capmet"] = {} +grulesobj["poly"]["dnwell"] = {} +grulesobj["poly"]["pwell"] = {} +grulesobj["poly"]["nwell"] = {} +grulesobj["poly"]["p+s/d"] = {} +grulesobj["poly"]["n+s/d"] = {} +grulesobj["poly"]["active_diff"] = {} +grulesobj["poly"]["active_tap"] = {} +grulesobj["poly"]["poly"] = {'min_width': 0.28} +grulesobj["poly"]["mcon"] = {'min_enclosure': 0.07, 'min_separation': 0.17} +grulesobj["poly"]["met1"] = {} +grulesobj["poly"]["via1"] = {} +grulesobj["poly"]["met2"] = {} +grulesobj["poly"]["via2"] = {} +grulesobj["poly"]["met3"] = {} +grulesobj["poly"]["via3"] = {} +grulesobj["poly"]["met4"] = {} +grulesobj["poly"]["via4"] = {} +grulesobj["poly"]["met5"] = {} +grulesobj["poly"]["capmet"] = {} +grulesobj["mcon"]["dnwell"] = {} +grulesobj["mcon"]["pwell"] = {} +grulesobj["mcon"]["nwell"] = {} +grulesobj["mcon"]["p+s/d"] = {} +grulesobj["mcon"]["n+s/d"] = {} +grulesobj["mcon"]["active_diff"] = {} +grulesobj["mcon"]["active_tap"] = {} +grulesobj["mcon"]["poly"] = {} +grulesobj["mcon"]["mcon"] = {'min_separation': 0.28, 'width': 0.22} +grulesobj["mcon"]["met1"] = {'min_enclosure': 0.12} +grulesobj["mcon"]["via1"] = {} +grulesobj["mcon"]["met2"] = {} +grulesobj["mcon"]["via2"] = {} +grulesobj["mcon"]["met3"] = {} +grulesobj["mcon"]["via3"] = {} +grulesobj["mcon"]["met4"] = {} +grulesobj["mcon"]["via4"] = {} +grulesobj["mcon"]["met5"] = {} +grulesobj["mcon"]["capmet"] = {} +grulesobj["met1"]["dnwell"] = {} +grulesobj["met1"]["pwell"] = {} +grulesobj["met1"]["nwell"] = {} +grulesobj["met1"]["p+s/d"] = {} +grulesobj["met1"]["n+s/d"] = {} +grulesobj["met1"]["active_diff"] = {} +grulesobj["met1"]["active_tap"] = {} +grulesobj["met1"]["poly"] = {} +grulesobj["met1"]["mcon"] = {} +grulesobj["met1"]["met1"] = {'min_width': 0.23, 'min_separation': 0.3} +grulesobj["met1"]["via1"] = {'min_enclosure': 0.12} +grulesobj["met1"]["met2"] = {} +grulesobj["met1"]["via2"] = {} +grulesobj["met1"]["met3"] = {} +grulesobj["met1"]["via3"] = {} +grulesobj["met1"]["met4"] = {} +grulesobj["met1"]["via4"] = {} +grulesobj["met1"]["met5"] = {} +grulesobj["met1"]["capmet"] = {} +grulesobj["via1"]["dnwell"] = {} +grulesobj["via1"]["pwell"] = {} +grulesobj["via1"]["nwell"] = {} +grulesobj["via1"]["p+s/d"] = {} +grulesobj["via1"]["n+s/d"] = {} +grulesobj["via1"]["active_diff"] = {} +grulesobj["via1"]["active_tap"] = {} +grulesobj["via1"]["poly"] = {} +grulesobj["via1"]["mcon"] = {} +grulesobj["via1"]["met1"] = {} +grulesobj["via1"]["via1"] = {'width': 0.26, 'min_separation': 0.36} +grulesobj["via1"]["met2"] = {'min_enclosure': 0.12} +grulesobj["via1"]["via2"] = {} +grulesobj["via1"]["met3"] = {} +grulesobj["via1"]["via3"] = {} +grulesobj["via1"]["met4"] = {} +grulesobj["via1"]["via4"] = {} +grulesobj["via1"]["met5"] = {} +grulesobj["via1"]["capmet"] = {} +grulesobj["met2"]["dnwell"] = {} +grulesobj["met2"]["pwell"] = {} +grulesobj["met2"]["nwell"] = {} +grulesobj["met2"]["p+s/d"] = {} +grulesobj["met2"]["n+s/d"] = {} +grulesobj["met2"]["active_diff"] = {} +grulesobj["met2"]["active_tap"] = {} +grulesobj["met2"]["poly"] = {} +grulesobj["met2"]["mcon"] = {} +grulesobj["met2"]["met1"] = {} +grulesobj["met2"]["via1"] = {} +grulesobj["met2"]["met2"] = {'min_width': 0.28, 'min_separation': 0.3} +grulesobj["met2"]["via2"] = {'min_enclosure': 0.12} +grulesobj["met2"]["met3"] = {} +grulesobj["met2"]["via3"] = {} +grulesobj["met2"]["met4"] = {} +grulesobj["met2"]["via4"] = {} +grulesobj["met2"]["met5"] = {} +grulesobj["met2"]["capmet"] = {'min_enclosure': 0.6} +grulesobj["via2"]["dnwell"] = {} +grulesobj["via2"]["pwell"] = {} +grulesobj["via2"]["nwell"] = {} +grulesobj["via2"]["p+s/d"] = {} +grulesobj["via2"]["n+s/d"] = {} +grulesobj["via2"]["active_diff"] = {} +grulesobj["via2"]["active_tap"] = {} +grulesobj["via2"]["poly"] = {} +grulesobj["via2"]["mcon"] = {} +grulesobj["via2"]["met1"] = {} +grulesobj["via2"]["via1"] = {} +grulesobj["via2"]["met2"] = {} +grulesobj["via2"]["via2"] = {'width': 0.26, 'min_separation': 0.36} +grulesobj["via2"]["met3"] = {'min_enclosure': 0.12} +grulesobj["via2"]["via3"] = {} +grulesobj["via2"]["met4"] = {} +grulesobj["via2"]["via4"] = {} +grulesobj["via2"]["met5"] = {} +grulesobj["via2"]["capmet"] = {} +grulesobj["met3"]["dnwell"] = {} +grulesobj["met3"]["pwell"] = {} +grulesobj["met3"]["nwell"] = {} +grulesobj["met3"]["p+s/d"] = {} +grulesobj["met3"]["n+s/d"] = {} +grulesobj["met3"]["active_diff"] = {} +grulesobj["met3"]["active_tap"] = {} +grulesobj["met3"]["poly"] = {} +grulesobj["met3"]["mcon"] = {} +grulesobj["met3"]["met1"] = {} +grulesobj["met3"]["via1"] = {} +grulesobj["met3"]["met2"] = {} +grulesobj["met3"]["via2"] = {} +grulesobj["met3"]["met3"] = {'min_width': 0.28, 'min_separation': 0.3} +grulesobj["met3"]["via3"] = {'min_enclosure': 0.12} +grulesobj["met3"]["met4"] = {} +grulesobj["met3"]["via4"] = {} +grulesobj["met3"]["met5"] = {} +grulesobj["met3"]["capmet"] = {} +grulesobj["via3"]["dnwell"] = {} +grulesobj["via3"]["pwell"] = {} +grulesobj["via3"]["nwell"] = {} +grulesobj["via3"]["p+s/d"] = {} +grulesobj["via3"]["n+s/d"] = {} +grulesobj["via3"]["active_diff"] = {} +grulesobj["via3"]["active_tap"] = {} +grulesobj["via3"]["poly"] = {} +grulesobj["via3"]["mcon"] = {} +grulesobj["via3"]["met1"] = {} +grulesobj["via3"]["via1"] = {} +grulesobj["via3"]["met2"] = {} +grulesobj["via3"]["via2"] = {} +grulesobj["via3"]["met3"] = {} +grulesobj["via3"]["via3"] = {'width': 0.26, 'min_separation': 0.36} +grulesobj["via3"]["met4"] = {'min_enclosure': 0.12} +grulesobj["via3"]["via4"] = {} +grulesobj["via3"]["met5"] = {} +grulesobj["via3"]["capmet"] = {} +grulesobj["met4"]["dnwell"] = {} +grulesobj["met4"]["pwell"] = {} +grulesobj["met4"]["nwell"] = {} +grulesobj["met4"]["p+s/d"] = {} +grulesobj["met4"]["n+s/d"] = {} +grulesobj["met4"]["active_diff"] = {} +grulesobj["met4"]["active_tap"] = {} +grulesobj["met4"]["poly"] = {} +grulesobj["met4"]["mcon"] = {} +grulesobj["met4"]["met1"] = {} +grulesobj["met4"]["via1"] = {} +grulesobj["met4"]["met2"] = {} +grulesobj["met4"]["via2"] = {} +grulesobj["met4"]["met3"] = {} +grulesobj["met4"]["via3"] = {} +grulesobj["met4"]["met4"] = {'min_width': 0.28, 'min_separation': 0.3} +grulesobj["met4"]["via4"] = {'min_enclosure': 0.12} +grulesobj["met4"]["met5"] = {} +grulesobj["met4"]["capmet"] = {} +grulesobj["via4"]["dnwell"] = {} +grulesobj["via4"]["pwell"] = {} +grulesobj["via4"]["nwell"] = {} +grulesobj["via4"]["p+s/d"] = {} +grulesobj["via4"]["n+s/d"] = {} +grulesobj["via4"]["active_diff"] = {} +grulesobj["via4"]["active_tap"] = {} +grulesobj["via4"]["poly"] = {} +grulesobj["via4"]["mcon"] = {} +grulesobj["via4"]["met1"] = {} +grulesobj["via4"]["via1"] = {} +grulesobj["via4"]["met2"] = {} +grulesobj["via4"]["via2"] = {} +grulesobj["via4"]["met3"] = {} +grulesobj["via4"]["via3"] = {} +grulesobj["via4"]["met4"] = {} +grulesobj["via4"]["via4"] = {'width': 0.26, 'min_separation': 0.36} +grulesobj["via4"]["met5"] = {'min_enclosure': 0.12} +grulesobj["via4"]["capmet"] = {} +grulesobj["met5"]["dnwell"] = {} +grulesobj["met5"]["pwell"] = {} +grulesobj["met5"]["nwell"] = {} +grulesobj["met5"]["p+s/d"] = {} +grulesobj["met5"]["n+s/d"] = {} +grulesobj["met5"]["active_diff"] = {} +grulesobj["met5"]["active_tap"] = {} +grulesobj["met5"]["poly"] = {} +grulesobj["met5"]["mcon"] = {} +grulesobj["met5"]["met1"] = {} +grulesobj["met5"]["via1"] = {} +grulesobj["met5"]["met2"] = {} +grulesobj["met5"]["via2"] = {} +grulesobj["met5"]["met3"] = {} +grulesobj["met5"]["via3"] = {} +grulesobj["met5"]["met4"] = {} +grulesobj["met5"]["via4"] = {} +grulesobj["met5"]["met5"] = {'min_width': 0.28, 'min_separation': 0.3} +grulesobj["met5"]["capmet"] = {} +grulesobj["capmet"]["dnwell"] = {} +grulesobj["capmet"]["pwell"] = {} +grulesobj["capmet"]["nwell"] = {} +grulesobj["capmet"]["p+s/d"] = {} +grulesobj["capmet"]["n+s/d"] = {} +grulesobj["capmet"]["active_diff"] = {} +grulesobj["capmet"]["active_tap"] = {} +grulesobj["capmet"]["poly"] = {} +grulesobj["capmet"]["mcon"] = {} +grulesobj["capmet"]["met1"] = {} +grulesobj["capmet"]["via1"] = {} +grulesobj["capmet"]["met2"] = {} +grulesobj["capmet"]["via2"] = {} +grulesobj["capmet"]["met3"] = {} +grulesobj["capmet"]["via3"] = {} +grulesobj["capmet"]["met4"] = {} +grulesobj["capmet"]["via4"] = {} +grulesobj["capmet"]["met5"] = {} +grulesobj["capmet"]["capmet"] = {'capmettop': (42, 0), 'capmetbottom': (36, 0), 'min_separation': 1.2} + diff --git a/openfasoc/generators/gdsfactory-gen/glayout/pdk/mappedpdk.py b/openfasoc/generators/gdsfactory-gen/glayout/pdk/mappedpdk.py new file mode 100644 index 000000000..04aaba5fe --- /dev/null +++ b/openfasoc/generators/gdsfactory-gen/glayout/pdk/mappedpdk.py @@ -0,0 +1,316 @@ +""" +usage: from mappedpdk import MappedPDK +""" + +from gdsfactory.pdk import Pdk +from gdsfactory.typings import Component, PathType, Layer +from pydantic import validator, StrictStr, ValidationError +from typing import ClassVar, Optional, Any, Union, Literal, Iterable +from pathlib import Path +from decimal import Decimal, ROUND_UP +import tempfile +import subprocess +from decimal import Decimal +from pydantic import validate_arguments +import xml.etree.ElementTree as ET + +class MappedPDK(Pdk): + """Inherits everything from the pdk class but also requires mapping to glayers + glayers are generic layers which can be returned with get_glayer(name: str) + has_required_glayers(list[str]) is used to verify all required generic layers are + present""" + + valid_glayers: ClassVar[tuple[str]] = ( + "dnwell", + "pwell", + "nwell", + "p+s/d", + "n+s/d", + "active_diff", + "active_tap", + "poly", + "mcon", + "met1", + "via1", + "met2", + "via2", + "met3", + "via3", + "met4", + "via4", + "met5", + "capmet", + ) + + glayers: dict[StrictStr, Union[StrictStr, tuple[int,int]]] + # friendly way to implement a graph + grules: dict[StrictStr, dict[StrictStr, Optional[dict[StrictStr, Any]]]] + klayout_lydrc_file: Optional[Path] = None + + @validator("glayers") + def glayers_check_keys(cls, glayers_obj: dict[StrictStr, Union[StrictStr, tuple[int,int]]]): + """force people to pick glayers from a finite set of string layers that you define + checks glayers to ensure valid keys,type. Glayers must be passed as dict[str,str] + if someone tries to pass a glayers dict that has a bad key, throw an error""" + for glayer, mapped_layer in glayers_obj.items(): + if (not isinstance(glayer, str)) or (not isinstance(mapped_layer, Union[str, tuple])): + raise TypeError("glayers should be passed as dict[str, Union[StrictStr, tuple[int,int]]]") + if glayer not in cls.valid_glayers: + raise ValueError( + "glayers keys must be one of generic layers listed in class variable valid_glayers" + ) + return glayers_obj + + @validator("klayout_lydrc_file") + def lydrc_file_exists(cls, lydrc_file_path): + """Check that lydrc_file_path exists if not none""" + if lydrc_file_path != None and not lydrc_file_path.is_file(): + raise ValueError(".lydrc script: the path given is not a file") + return lydrc_file_path + + @validate_arguments + def drc( + self, + layout: Component | PathType, + output_dir_or_file: Optional[PathType] = None, + ): + """Returns true if the layout is DRC clean and false if not + Also saves detailed results to output_dir_or_file location as lyrdb + layout can be passed as a file path or gdsfactory component""" + if not self.klayout_lydrc_file: + raise NotImplementedError("no drc script for this pdk") + # find layout gds file path + tempdir = None + if isinstance(layout, Component): + tempdir = tempfile.TemporaryDirectory() + layout_path = Path(layout.write_gds(gdsdir=tempdir.name)).resolve() + elif isinstance(layout, PathType): + layout_path = Path(layout).resolve() + else: + raise TypeError("layout should be a Component, Path, or string") + if not layout_path.is_file(): + raise ValueError("layout must exist, the path given is not a file") + # find report file path, if None then use current directory + report_path = ( + Path(output_dir_or_file).resolve() + if output_dir_or_file + else Path.cwd().resolve() + ) + if report_path.is_dir(): + report_path = Path( + report_path + / str( + self.name + + layout_path.name.replace(layout_path.suffix, "") + + "_drcreport.lyrdb" + ) + ) + elif not report_path.is_file(): + raise ValueError("report_path must be file or dir") + # run klayout drc + drc_args = [ + "klayout", + "-b", + "-r", + str(self.klayout_lydrc_file), + "-rd", + "input=" + str(layout_path), + "-rd", + "report=" + str(report_path), + ] + rtr_code = subprocess.Popen(drc_args).wait() + if rtr_code: + raise RuntimeError("error running klayout DRC") + # clean up and return + if tempdir: + tempdir.cleanup() + # there is a drc parsing open-source at: + # https://github.com/google/globalfoundries-pdk-libs-gf180mcu_fd_pr/blob/main/rules/klayout/drc + # eventually I can return more info on the drc run, but for now just void and view the lyrdb in klayout + + # Open DRC output XML file + drc_tree = ET.parse(report_path.resolve()) + drc_root = drc_tree.getroot() + if drc_root.tag != "report-database": + raise TypeError("DRC report file is not a valid report-database") + # Check if DRC passed + drc_error_count = len(drc_root[7]) + return (drc_error_count == 0) + + @validate_arguments + def has_required_glayers(self, layers_required: list[str]): + """Raises ValueError if any of the generic layers in layers_required: list[str] + are not mapped to anything in the pdk.glayers dictionary + also checks that the values in the glayers dictionary map to real Pdk layers""" + for layer in layers_required: + if layer not in self.glayers: + raise ValueError( + f"{layer!r} not in self.glayers {list(self.glayers.keys())}" + ) + if isinstance(self.glayers[layer], str): + self.validate_layers([self.glayers[layer]]) + elif not isinstance(self.glayers[layer], tuple): + raise TypeError("glayer mapped value should be str or tuple[int,int]") + + + @validate_arguments + def layer_to_glayer(self, layer: tuple[int, int]) -> str: + """if layer provided corresponds to a glayer, will return a glayer + else will raise an exception + takes layer as a tuple(int,int)""" + # lambda for finding last matching key in dict from val + find_last = lambda val, d: [x for x, y in d.items() if y == val].pop() + if layer in self.glayers.values(): + return find_last(layer) + elif self.layers is not None: + # find glayer verfying presence along the way + pdk_real_layers = self.layers.values() + if layer in pdk_real_layers: + layer_name = find_last(layer, self.layers) + if layer_name in self.glayers.values(): + glayer_name = find_last(layer_name, self.glayers) + else: + raise ValueError("layer does not correspond to a glayer") + else: + raise ValueError("layer is not a layer present in the pdk") + return glayer_name + else: + raise ValueError("layer might not be a layer present in the pdk") + + # TODO: implement LayerSpec type + @validate_arguments + def get_glayer(self, layer: str) -> Layer: + """Returns the pdk layer from the generic layer name""" + direct_mapping = self.glayers[layer] + if isinstance(direct_mapping, tuple): + return direct_mapping + else: + return self.get_layer(direct_mapping) + + @validate_arguments + def get_grule( + self, glayer1: str, glayer2: Optional[str] = None, return_decimal = False + ) -> dict[StrictStr, Union[float,Decimal]]: + """Returns a dictionary describing the relationship between two layers + If one layer is specified, returns a dictionary with all intra layer rules""" + if glayer1 not in MappedPDK.valid_glayers: + raise ValueError("get_grule, " + str(glayer1) + " not valid glayer") + # decide if two or one inputs and set rules_dict accordingly + rules_dict = None + if glayer2 is not None: + if glayer2 not in MappedPDK.valid_glayers: + raise ValueError("get_grule, " + str(glayer2) + " not valid glayer") + rules_dict = self.grules.get(glayer1, dict()).get(glayer2) + if rules_dict is None or rules_dict == {}: + rules_dict = self.grules.get(glayer2, dict()).get(glayer1) + else: + glayer2 = glayer1 + rules_dict = self.grules.get(glayer1, dict()).get(glayer1) + # error check, convert type, and return + if rules_dict is None or rules_dict == {}: + raise NotImplementedError( + "no rules found between " + str(glayer1) + " and " + str(glayer2) + ) + for rule in rules_dict: + if type(rule) == float and return_decimal: + rules_dict[rule] = Decimal(str(rule)) + return rules_dict + + @classmethod + def is_routable_glayer(cls, glayer: StrictStr): + return any(hint in glayer for hint in ["met", "active", "poly"]) + + # TODO: implement + @classmethod + def from_gf_pdk( + cls, + gfpdk: Pdk, + **kwargs + ): + """Construct a mapped pdk from an existing pdk and the extra parts of MappedPDK + grid is the grid size in nm""" + # input type and value validation + if not isinstance(gfpdk, Pdk): + raise TypeError("from_gf_pdk: gfpdk arg only accepts GDSFactory pdk type") + # create argument dictionary + passargs = dict() + # pdk args + passargs["name"]=gfpdk.name + #passargs["cross_sections"]=gfpdk.cross_sections + #passargs["cells"]=gfpdk.cells + #passargs["symbols"]=gfpdk.symbols + #passargs["default_symbol_factory"]=gfpdk.default_symbol_factory + #passargs["containers"]=gfpdk.containers + #passargs["base_pdk"]=gfpdk.base_pdk + #passargs["default_decorator"]=gfpdk.default_decorator + passargs["layers"]=gfpdk.layers + #passargs["layer_stack"]=gfpdk.layer_stack + #passargs["layer_views"]=gfpdk.layer_views#??? layer view broken??? +# passargs["layer_transitions"]=gfpdk.layer_transitions +# passargs["sparameters_path"]=gfpdk.sparameters_path +# passargs["modes_path"]=gfpdk.modes_path +# passargs["interconnect_cml_path"]=gfpdk.interconnect_cml_path +# passargs["warn_off_grid_ports"]=gfpdk.warn_off_grid_ports +# passargs["constants"]=gfpdk.constants +# passargs["materials_index"]=gfpdk.materials_index +# passargs["routing_strategies"]=gfpdk.routing_strategies +# passargs["circuit_yaml_parser"]=gfpdk.circuit_yaml_parser +# passargs["gds_write_settings"]=gfpdk.gds_write_settings +# passargs["oasis_settings"]=gfpdk.oasis_settings +# passargs["cell_decorator_settings"]=gfpdk.cell_decorator_settings +# passargs["bend_points_distance"]=gfpdk.bend_points_distance + # MappedPDK args override existing args + passargs.update(kwargs) + # create and return MappedPDK + mappedpdk = MappedPDK(**passargs) + return mappedpdk + + # util methods + @validate_arguments + def util_max_metal_seperation(self, metal_levels: Union[list[int],list[str], str, int] = range(1,6)) -> float: + """returns the maximum of the min_seperation rule for all layers specfied + although the name of this function is util_max_metal_seperation, layers do not have to be metals + you can specify non metals by using metal_levels=list of glayers + if metal_levels is list of int, integers are converted to metal levels + if a single int is provided, all metals below and including that int level are considerd + by default this function returns the maximum metal seperation of metals1-5 + """ + if type(metal_levels)==int: + metal_levels = range(1,metal_levels+1) + metal_levels = metal_levels if isinstance(metal_levels,Iterable) else [metal_levels] + if len(metal_levels)<1: + raise ValueError("metal levels cannot be empty list") + if type(metal_levels[0])==int: + metal_levels = [f"met{i}" for i in metal_levels] + sep_rules = list() + for met in metal_levels: + sep_rules.append(self.get_grule(met)["min_separation"]) + return self.snap_to_2xgrid(max(sep_rules)) + + @validate_arguments + def snap_to_2xgrid(self, dims: Union[list[Union[float,Decimal]], Union[float,Decimal]], return_type: Literal["decimal","float","same"]="float", snap4: bool=False) -> Union[list[Union[float,Decimal]], Union[float,Decimal]]: + """snap all numbers in dims to double the grid size. + This is useful when a generator accepts a size or dimension argument + because there is a chance the cell may be centered (resulting in off grid components) + args: + dims = a list OR single number specifying the dimensions to snap to grid + return_type = return a decimal, float, or the same type that was passed to the function + snap4: snap to 4xgrid (Defualt false) + """ + dims = dims if isinstance(dims, Iterable) else [dims] + dimtype_in = type(dims[0]) + dims = [Decimal(str(dim)) for dim in dims] # process in decimals + grid = 2 * Decimal(str(self.grid_size)) + grid = grid if grid else Decimal('0.001') + grid = 2*grid if snap4 else grid + # snap dims to grid + snapped_dims = list() + for dim in dims: + snapped_dim = grid * (dim / grid).quantize(1, rounding=ROUND_UP) + snapped_dims.append(snapped_dim) + # convert to correct type + if return_type=="float" or (return_type=="same" and dimtype_in==float): + snapped_dims = [float(snapped_dim) for snapped_dim in snapped_dims] + # correctly return list or single element + return snapped_dims[0] if len(snapped_dims)==1 else snapped_dims + diff --git a/openfasoc/generators/gdsfactory-gen/glayout/pdk/sky130_mapped/__init__.py b/openfasoc/generators/gdsfactory-gen/glayout/pdk/sky130_mapped/__init__.py new file mode 100644 index 000000000..ac744c839 --- /dev/null +++ b/openfasoc/generators/gdsfactory-gen/glayout/pdk/sky130_mapped/__init__.py @@ -0,0 +1,5 @@ +""" +Usage at the package level: from pdk.sky130_mapped import sky130_mapped_pdk +""" + +from glayout.pdk.sky130_mapped.sky130_mapped import sky130_mapped_pdk diff --git a/openfasoc/generators/gdsfactory-gen/glayout/pdk/sky130_mapped/grules.py b/openfasoc/generators/gdsfactory-gen/glayout/pdk/sky130_mapped/grules.py new file mode 100644 index 000000000..474801604 --- /dev/null +++ b/openfasoc/generators/gdsfactory-gen/glayout/pdk/sky130_mapped/grules.py @@ -0,0 +1,371 @@ +from ..mappedpdk import MappedPDK + +grulesobj = dict() +for glayer in MappedPDK.valid_glayers: + grulesobj[glayer] = dict((x, None) for x in MappedPDK.valid_glayers) + +grulesobj["dnwell"]["dnwell"] = {"min_width": 3.0, "min_separation": 6.3} +grulesobj["dnwell"]["pwell"] = {"min_enclosure": 0.0} +grulesobj["dnwell"]["nwell"] = {"min_separation": 4.5} +grulesobj["dnwell"]["p+s/d"] = {} +grulesobj["dnwell"]["n+s/d"] = {} +grulesobj["dnwell"]["active_diff"] = {} +grulesobj["dnwell"]["active_tap"] = {"min_separation": 0.34} +grulesobj["dnwell"]["poly"] = {} +grulesobj["dnwell"]["mcon"] = {} +grulesobj["dnwell"]["met1"] = {} +grulesobj["dnwell"]["via1"] = {} +grulesobj["dnwell"]["met2"] = {} +grulesobj["dnwell"]["via2"] = {} +grulesobj["dnwell"]["met3"] = {} +grulesobj["dnwell"]["via3"] = {} +grulesobj["dnwell"]["met4"] = {} +grulesobj["dnwell"]["via4"] = {} +grulesobj["dnwell"]["met5"] = {} +grulesobj["dnwell"]["capmet"] = {} +grulesobj["pwell"]["dnwell"] = {} +grulesobj["pwell"]["pwell"] = {} +grulesobj["pwell"]["nwell"] = {} +grulesobj["pwell"]["p+s/d"] = {} +grulesobj["pwell"]["n+s/d"] = {} +grulesobj["pwell"]["active_diff"] = {} +grulesobj["pwell"]["active_tap"] = {"min_enclosure": 0.18} +grulesobj["pwell"]["poly"] = {} +grulesobj["pwell"]["mcon"] = {} +grulesobj["pwell"]["met1"] = {} +grulesobj["pwell"]["via1"] = {} +grulesobj["pwell"]["met2"] = {} +grulesobj["pwell"]["via2"] = {} +grulesobj["pwell"]["met3"] = {} +grulesobj["pwell"]["via3"] = {} +grulesobj["pwell"]["met4"] = {} +grulesobj["pwell"]["via4"] = {} +grulesobj["pwell"]["met5"] = {} +grulesobj["pwell"]["capmet"] = {} +grulesobj["nwell"]["dnwell"] = {} +grulesobj["nwell"]["pwell"] = {} +grulesobj["nwell"]["nwell"] = {"min_width": 0.84, "min_separation": 1.27} +grulesobj["nwell"]["p+s/d"] = {} +grulesobj["nwell"]["n+s/d"] = {} +grulesobj["nwell"]["active_diff"] = {} +grulesobj["nwell"]["active_tap"] = {"min_enclosure": 0.18} +grulesobj["nwell"]["poly"] = {} +grulesobj["nwell"]["mcon"] = {} +grulesobj["nwell"]["met1"] = {} +grulesobj["nwell"]["via1"] = {} +grulesobj["nwell"]["met2"] = {} +grulesobj["nwell"]["via2"] = {} +grulesobj["nwell"]["met3"] = {} +grulesobj["nwell"]["via3"] = {} +grulesobj["nwell"]["met4"] = {} +grulesobj["nwell"]["via4"] = {} +grulesobj["nwell"]["met5"] = {} +grulesobj["nwell"]["capmet"] = {} +grulesobj["p+s/d"]["dnwell"] = {} +grulesobj["p+s/d"]["pwell"] = {} +grulesobj["p+s/d"]["nwell"] = {} +grulesobj["p+s/d"]["p+s/d"] = {"min_width": 0.38, "min_separation": 0.38} +grulesobj["p+s/d"]["n+s/d"] = {} +grulesobj["p+s/d"]["active_diff"] = {"min_enclosure": 0.13, "min_separation": 0.13} +grulesobj["p+s/d"]["active_tap"] = {"min_enclosure": 0.13, "min_separation": 0.13} +grulesobj["p+s/d"]["poly"] = {} +grulesobj["p+s/d"]["mcon"] = {} +grulesobj["p+s/d"]["met1"] = {} +grulesobj["p+s/d"]["via1"] = {} +grulesobj["p+s/d"]["met2"] = {} +grulesobj["p+s/d"]["via2"] = {} +grulesobj["p+s/d"]["met3"] = {} +grulesobj["p+s/d"]["via3"] = {} +grulesobj["p+s/d"]["met4"] = {} +grulesobj["p+s/d"]["via4"] = {} +grulesobj["p+s/d"]["met5"] = {} +grulesobj["p+s/d"]["capmet"] = {} +grulesobj["n+s/d"]["dnwell"] = {} +grulesobj["n+s/d"]["pwell"] = {} +grulesobj["n+s/d"]["nwell"] = {} +grulesobj["n+s/d"]["p+s/d"] = {} +grulesobj["n+s/d"]["n+s/d"] = {"min_width": 0.38, "min_separation": 0.38} +grulesobj["n+s/d"]["active_diff"] = {"min_enclosure": 0.13, "min_separation": 0.13} +grulesobj["n+s/d"]["active_tap"] = {"min_enclosure": 0.13, "min_separation": 0.13} +grulesobj["n+s/d"]["poly"] = {} +grulesobj["n+s/d"]["mcon"] = {} +grulesobj["n+s/d"]["met1"] = {} +grulesobj["n+s/d"]["via1"] = {} +grulesobj["n+s/d"]["met2"] = {} +grulesobj["n+s/d"]["via2"] = {} +grulesobj["n+s/d"]["met3"] = {} +grulesobj["n+s/d"]["via3"] = {} +grulesobj["n+s/d"]["met4"] = {} +grulesobj["n+s/d"]["via4"] = {} +grulesobj["n+s/d"]["met5"] = {} +grulesobj["n+s/d"]["capmet"] = {} +grulesobj["active_diff"]["dnwell"] = {} +grulesobj["active_diff"]["pwell"] = {} +grulesobj["active_diff"]["nwell"] = {} +grulesobj["active_diff"]["p+s/d"] = {} +grulesobj["active_diff"]["n+s/d"] = {} +grulesobj["active_diff"]["active_diff"] = {"min_width": 0.15, "min_separation": 0.27} +grulesobj["active_diff"]["active_tap"] = {"min_separation": 0.27} +grulesobj["active_diff"]["poly"] = {"overhang": 0.25} +grulesobj["active_diff"]["mcon"] = {"min_enclosure": 0.06} +grulesobj["active_diff"]["met1"] = {} +grulesobj["active_diff"]["via1"] = {} +grulesobj["active_diff"]["met2"] = {} +grulesobj["active_diff"]["via2"] = {} +grulesobj["active_diff"]["met3"] = {} +grulesobj["active_diff"]["via3"] = {} +grulesobj["active_diff"]["met4"] = {} +grulesobj["active_diff"]["via4"] = {} +grulesobj["active_diff"]["met5"] = {} +grulesobj["active_diff"]["capmet"] = {} +grulesobj["active_tap"]["dnwell"] = {} +grulesobj["active_tap"]["pwell"] = {} +grulesobj["active_tap"]["nwell"] = {} +grulesobj["active_tap"]["p+s/d"] = {} +grulesobj["active_tap"]["n+s/d"] = {} +grulesobj["active_tap"]["active_diff"] = {} +grulesobj["active_tap"]["active_tap"] = {"min_width": 0.15, "min_separation": 0.27} +grulesobj["active_tap"]["poly"] = {} +grulesobj["active_tap"]["mcon"] = {"min_enclosure": 0.12} +grulesobj["active_tap"]["met1"] = {} +grulesobj["active_tap"]["via1"] = {} +grulesobj["active_tap"]["met2"] = {} +grulesobj["active_tap"]["via2"] = {} +grulesobj["active_tap"]["met3"] = {} +grulesobj["active_tap"]["via3"] = {} +grulesobj["active_tap"]["met4"] = {} +grulesobj["active_tap"]["via4"] = {} +grulesobj["active_tap"]["met5"] = {} +grulesobj["active_tap"]["capmet"] = {} +grulesobj["poly"]["dnwell"] = {} +grulesobj["poly"]["pwell"] = {} +grulesobj["poly"]["nwell"] = {} +grulesobj["poly"]["p+s/d"] = {} +grulesobj["poly"]["n+s/d"] = {} +grulesobj["poly"]["active_diff"] = {} +grulesobj["poly"]["active_tap"] = {} +grulesobj["poly"]["poly"] = { + "min_width": 0.15, + "min_separation": 0.21, + "extension": 0.13, +} +grulesobj["poly"]["mcon"] = {"min_enclosure": 0.05, "min_separation": 0.06} +grulesobj["poly"]["met1"] = {} +grulesobj["poly"]["via1"] = {} +grulesobj["poly"]["met2"] = {} +grulesobj["poly"]["via2"] = {} +grulesobj["poly"]["met3"] = {} +grulesobj["poly"]["via3"] = {} +grulesobj["poly"]["met4"] = {} +grulesobj["poly"]["via4"] = {} +grulesobj["poly"]["met5"] = {} +grulesobj["poly"]["capmet"] = {} +grulesobj["mcon"]["dnwell"] = {} +grulesobj["mcon"]["pwell"] = {} +grulesobj["mcon"]["nwell"] = {} +grulesobj["mcon"]["p+s/d"] = {} +grulesobj["mcon"]["n+s/d"] = {} +grulesobj["mcon"]["active_diff"] = {} +grulesobj["mcon"]["active_tap"] = {} +grulesobj["mcon"]["poly"] = {} +grulesobj["mcon"]["mcon"] = {"min_width": 0.17, "min_separation": 0.17, "width": 0.17} +grulesobj["mcon"]["met1"] = {"min_enclosure": 0.08} +grulesobj["mcon"]["via1"] = {} +grulesobj["mcon"]["met2"] = {} +grulesobj["mcon"]["via2"] = {} +grulesobj["mcon"]["met3"] = {} +grulesobj["mcon"]["via3"] = {} +grulesobj["mcon"]["met4"] = {} +grulesobj["mcon"]["via4"] = {} +grulesobj["mcon"]["met5"] = {} +grulesobj["mcon"]["capmet"] = {} +grulesobj["met1"]["dnwell"] = {} +grulesobj["met1"]["pwell"] = {} +grulesobj["met1"]["nwell"] = {} +grulesobj["met1"]["p+s/d"] = {} +grulesobj["met1"]["n+s/d"] = {} +grulesobj["met1"]["active_diff"] = {} +grulesobj["met1"]["active_tap"] = {} +grulesobj["met1"]["poly"] = {} +grulesobj["met1"]["mcon"] = {} +grulesobj["met1"]["met1"] = {"min_width": 0.17, "min_separation": 0.17} +grulesobj["met1"]["via1"] = {"min_enclosure": 0.0} +grulesobj["met1"]["met2"] = {} +grulesobj["met1"]["via2"] = {} +grulesobj["met1"]["met3"] = {} +grulesobj["met1"]["via3"] = {} +grulesobj["met1"]["met4"] = {} +grulesobj["met1"]["via4"] = {} +grulesobj["met1"]["met5"] = {} +grulesobj["met1"]["capmet"] = {} +grulesobj["via1"]["dnwell"] = {} +grulesobj["via1"]["pwell"] = {} +grulesobj["via1"]["nwell"] = {} +grulesobj["via1"]["p+s/d"] = {} +grulesobj["via1"]["n+s/d"] = {} +grulesobj["via1"]["active_diff"] = {} +grulesobj["via1"]["active_tap"] = {} +grulesobj["via1"]["poly"] = {} +grulesobj["via1"]["mcon"] = {} +grulesobj["via1"]["met1"] = {} +grulesobj["via1"]["via1"] = {"min_width": 0.17, "min_separation": 0.19, "width": 0.17} +grulesobj["via1"]["met2"] = {"min_enclosure": 0.06} +grulesobj["via1"]["via2"] = {} +grulesobj["via1"]["met3"] = {} +grulesobj["via1"]["via3"] = {} +grulesobj["via1"]["met4"] = {} +grulesobj["via1"]["via4"] = {} +grulesobj["via1"]["met5"] = {} +grulesobj["via1"]["capmet"] = {} +grulesobj["met2"]["dnwell"] = {} +grulesobj["met2"]["pwell"] = {} +grulesobj["met2"]["nwell"] = {} +grulesobj["met2"]["p+s/d"] = {} +grulesobj["met2"]["n+s/d"] = {} +grulesobj["met2"]["active_diff"] = {} +grulesobj["met2"]["active_tap"] = {} +grulesobj["met2"]["poly"] = {} +grulesobj["met2"]["mcon"] = {} +grulesobj["met2"]["met1"] = {} +grulesobj["met2"]["via1"] = {} +grulesobj["met2"]["met2"] = {"min_width": 0.14, "min_separation": 0.14} +grulesobj["met2"]["via2"] = {"min_enclosure": 0.14} +grulesobj["met2"]["met3"] = {} +grulesobj["met2"]["via3"] = {} +grulesobj["met2"]["met4"] = {} +grulesobj["met2"]["via4"] = {} +grulesobj["met2"]["met5"] = {} +grulesobj["met2"]["capmet"] = {} +grulesobj["via2"]["dnwell"] = {} +grulesobj["via2"]["pwell"] = {} +grulesobj["via2"]["nwell"] = {} +grulesobj["via2"]["p+s/d"] = {} +grulesobj["via2"]["n+s/d"] = {} +grulesobj["via2"]["active_diff"] = {} +grulesobj["via2"]["active_tap"] = {} +grulesobj["via2"]["poly"] = {} +grulesobj["via2"]["mcon"] = {} +grulesobj["via2"]["met1"] = {} +grulesobj["via2"]["via1"] = {} +grulesobj["via2"]["met2"] = {} +grulesobj["via2"]["via2"] = {"min_width": 0.21, "min_separation": 0.17, "width": 0.15} +grulesobj["via2"]["met3"] = {"min_enclosure": 0.09} +grulesobj["via2"]["via3"] = {} +grulesobj["via2"]["met4"] = {} +grulesobj["via2"]["via4"] = {} +grulesobj["via2"]["met5"] = {} +grulesobj["via2"]["capmet"] = {} +grulesobj["met3"]["dnwell"] = {} +grulesobj["met3"]["pwell"] = {} +grulesobj["met3"]["nwell"] = {} +grulesobj["met3"]["p+s/d"] = {} +grulesobj["met3"]["n+s/d"] = {} +grulesobj["met3"]["active_diff"] = {} +grulesobj["met3"]["active_tap"] = {} +grulesobj["met3"]["poly"] = {} +grulesobj["met3"]["mcon"] = {} +grulesobj["met3"]["met1"] = {} +grulesobj["met3"]["via1"] = {} +grulesobj["met3"]["met2"] = {} +grulesobj["met3"]["via2"] = {} +grulesobj["met3"]["met3"] = {"min_width": 0.14, "min_separation": 0.14} +grulesobj["met3"]["via3"] = {"min_enclosure":0.19} +grulesobj["met3"]["met4"] = {} +grulesobj["met3"]["via4"] = {} +grulesobj["met3"]["met5"] = {} +grulesobj["met3"]["capmet"] = {} +grulesobj["via3"]["dnwell"] = {} +grulesobj["via3"]["pwell"] = {} +grulesobj["via3"]["nwell"] = {} +grulesobj["via3"]["p+s/d"] = {} +grulesobj["via3"]["n+s/d"] = {} +grulesobj["via3"]["active_diff"] = {} +grulesobj["via3"]["active_tap"] = {} +grulesobj["via3"]["poly"] = {} +grulesobj["via3"]["mcon"] = {} +grulesobj["via3"]["met1"] = {} +grulesobj["via3"]["via1"] = {} +grulesobj["via3"]["met2"] = {} +grulesobj["via3"]["via2"] = {} +grulesobj["via3"]["met3"] = {} +grulesobj["via3"]["via3"] = {"min_width": 0.2, "min_separation": 0.2, "width": 0.2} +grulesobj["via3"]["met4"] = {"min_enclosure": 0.65} +grulesobj["via3"]["via4"] = {} +grulesobj["via3"]["met5"] = {} +grulesobj["via3"]["capmet"] = {} +grulesobj["met4"]["dnwell"] = {} +grulesobj["met4"]["pwell"] = {} +grulesobj["met4"]["nwell"] = {} +grulesobj["met4"]["p+s/d"] = {} +grulesobj["met4"]["n+s/d"] = {} +grulesobj["met4"]["active_diff"] = {} +grulesobj["met4"]["active_tap"] = {} +grulesobj["met4"]["poly"] = {} +grulesobj["met4"]["mcon"] = {} +grulesobj["met4"]["met1"] = {} +grulesobj["met4"]["via1"] = {} +grulesobj["met4"]["met2"] = {} +grulesobj["met4"]["via2"] = {} +grulesobj["met4"]["met3"] = {} +grulesobj["met4"]["via3"] = {} +grulesobj["met4"]["met4"] = {"min_width": 0.3, "min_separation": 0.4} +grulesobj["met4"]["via4"] = {"min_enclosure": 0.09} +grulesobj["met4"]["met5"] = {} +grulesobj["met4"]["capmet"] = {"min_enclosure": 0.14} +grulesobj["via4"]["dnwell"] = {} +grulesobj["via4"]["pwell"] = {} +grulesobj["via4"]["nwell"] = {} +grulesobj["via4"]["p+s/d"] = {} +grulesobj["via4"]["n+s/d"] = {} +grulesobj["via4"]["active_diff"] = {} +grulesobj["via4"]["active_tap"] = {} +grulesobj["via4"]["poly"] = {} +grulesobj["via4"]["mcon"] = {} +grulesobj["via4"]["met1"] = {} +grulesobj["via4"]["via1"] = {} +grulesobj["via4"]["met2"] = {} +grulesobj["via4"]["via2"] = {} +grulesobj["via4"]["met3"] = {} +grulesobj["via4"]["via3"] = {} +grulesobj["via4"]["met4"] = {} +grulesobj["via4"]["via4"] = {"width": 0.2, "min_separation": 0.35} +grulesobj["via4"]["met5"] = {"min_enclosure": 0.07} +grulesobj["via4"]["capmet"] = {} +grulesobj["met5"]["dnwell"] = {} +grulesobj["met5"]["pwell"] = {} +grulesobj["met5"]["nwell"] = {} +grulesobj["met5"]["p+s/d"] = {} +grulesobj["met5"]["n+s/d"] = {} +grulesobj["met5"]["active_diff"] = {} +grulesobj["met5"]["active_tap"] = {} +grulesobj["met5"]["poly"] = {} +grulesobj["met5"]["mcon"] = {} +grulesobj["met5"]["met1"] = {} +grulesobj["met5"]["via1"] = {} +grulesobj["met5"]["met2"] = {} +grulesobj["met5"]["via2"] = {} +grulesobj["met5"]["met3"] = {} +grulesobj["met5"]["via3"] = {} +grulesobj["met5"]["met4"] = {} +grulesobj["met5"]["via4"] = {} +grulesobj["met5"]["met5"] = {"min_width": 0.3, "min_separation": 0.4} +grulesobj["met5"]["capmet"] = {} +grulesobj["capmet"]["dnwell"] = {} +grulesobj["capmet"]["pwell"] = {} +grulesobj["capmet"]["nwell"] = {} +grulesobj["capmet"]["p+s/d"] = {} +grulesobj["capmet"]["n+s/d"] = {} +grulesobj["capmet"]["active_diff"] = {} +grulesobj["capmet"]["active_tap"] = {} +grulesobj["capmet"]["poly"] = {} +grulesobj["capmet"]["mcon"] = {} +grulesobj["capmet"]["met1"] = {} +grulesobj["capmet"]["via1"] = {} +grulesobj["capmet"]["met2"] = {} +grulesobj["capmet"]["via2"] = {} +grulesobj["capmet"]["met3"] = {} +grulesobj["capmet"]["via3"] = {} +grulesobj["capmet"]["met4"] = {} +grulesobj["capmet"]["via4"] = {} +grulesobj["capmet"]["met5"] = {} +grulesobj["capmet"]["capmet"] = {"capmettop": (71, 20), "capmetbottom": (70, 20), "min_separation": 1.2} diff --git a/openfasoc/generators/gdsfactory-gen/PDK/sky130_mapped/sky130.lydrc b/openfasoc/generators/gdsfactory-gen/glayout/pdk/sky130_mapped/sky130.lydrc similarity index 100% rename from openfasoc/generators/gdsfactory-gen/PDK/sky130_mapped/sky130.lydrc rename to openfasoc/generators/gdsfactory-gen/glayout/pdk/sky130_mapped/sky130.lydrc diff --git a/openfasoc/generators/gdsfactory-gen/glayout/pdk/sky130_mapped/sky130_add_npc.py b/openfasoc/generators/gdsfactory-gen/glayout/pdk/sky130_mapped/sky130_add_npc.py new file mode 100644 index 000000000..04d411181 --- /dev/null +++ b/openfasoc/generators/gdsfactory-gen/glayout/pdk/sky130_mapped/sky130_add_npc.py @@ -0,0 +1,64 @@ +from gdsfactory.component import Component +from gdsfactory.polygon import Polygon +from gdsfactory.geometry.boolean import boolean + + +def sky130_add_npc(comp: Component) -> Component: + """To keep with the generic generator structure, + we do NOT add nitride poly cut layer in the generic generators (npc is specfic to sky130). + Because it is easy to add idenpedently, + we implement this as a function wrapper to correctly lay npc + returns the modified component""" + # extract licon polygons which are over poly (using booleans) + licon_comp = comp.extract(layers=[(66,44)]) + poly_comp = comp.extract(layers=[(66,20)]) + existing_npc = comp.extract(layers=[(95,20)]) + # TODO: see about an implemtation using gdsfactory component metadata + if len(licon_comp.get_polygons()) < 2 and len(poly_comp.get_polygons()) < 2: + return comp + liconANDpoly = boolean(licon_comp, poly_comp, layer=(1,2), operation="and") + if len(existing_npc.get_polygons()) > 1: + liconANDpoly = boolean(liconANDpoly, existing_npc, layer=(1,2), operation="A-B") + licon_polygons = liconANDpoly.get_polygons(as_array=False) + # iterate through all licon and create npc (ignore merges for now) + npc_polygons = list() + for licon_polygon in licon_polygons: + bbox = licon_polygon.bounding_box() + licon_polygonxmin = bbox[0][0] + licon_polygonymin = bbox[0][1] + licon_polygonxmax = bbox[1][0] + licon_polygonymax = bbox[1][1] + padding_points = [ + [licon_polygonxmin - 0.1, licon_polygonymin - 0.1], + [licon_polygonxmax + 0.1, licon_polygonymin - 0.1], + [licon_polygonxmax + 0.1, licon_polygonymax + 0.1], + [licon_polygonxmin - 0.1, licon_polygonymax + 0.1], + ] + npc_polygons.append(Polygon(padding_points, layer=(95,20))) + # determine which npc polygons should be merged + # also merge them by adding a polygon over them + # naive approach, n^2 complexity + npc_merged_polygons = list() + for i, npc_polygon in enumerate(npc_polygons): + for j, other_polygon in enumerate(npc_polygons): + # use the fact that all npc polys have the same width (at this point) + yviolation = abs(npc_polygon.center[1] - other_polygon.center[1]) < 0.64#0.27+0.37 + xviolation = abs(npc_polygon.center[0] - other_polygon.center[0]) < 0.64 + if i==j:#skip same polygon + continue + elif (xviolation and yviolation): + nxmax = max(npc_polygon.xmax, other_polygon.xmax) + nxmin = min(npc_polygon.xmin, other_polygon.xmin) + nymax = max(npc_polygon.ymax, other_polygon.ymax) + nymin = min(npc_polygon.ymin, other_polygon.ymin) + points = [ + [nxmin,nymin], + [nxmax,nymin], + [nxmax,nymax], + [nxmin,nymax], + ] + npc_merged_polygons.append(Polygon(points=points,layer=(95,20))) + # add npc and return + npc_polygons_to_add = npc_polygons + npc_merged_polygons + comp.add(npc_polygons_to_add) + return comp diff --git a/openfasoc/generators/gdsfactory-gen/glayout/pdk/sky130_mapped/sky130_mapped.py b/openfasoc/generators/gdsfactory-gen/glayout/pdk/sky130_mapped/sky130_mapped.py new file mode 100644 index 000000000..b708cc88b --- /dev/null +++ b/openfasoc/generators/gdsfactory-gen/glayout/pdk/sky130_mapped/sky130_mapped.py @@ -0,0 +1,51 @@ +""" +usage: from sky130_mapped import sky130_mapped_pdk +""" +import sky130 + +from ..mappedpdk import MappedPDK +from ..sky130_mapped.grules import grulesobj +from pathlib import Path +from ..sky130_mapped.sky130_add_npc import sky130_add_npc + +sky130.PDK.layers["capm3"] = (89, 44) + +# use mimcap over metal 3 +sky130_glayer_mapping = { + "capmet": "capm3", + "met5": "met4drawing", + "via4": "via3drawing", + "met4": "met3drawing", + "via3": "via2drawing", + "met3": "met2drawing", + "via2": "viadrawing", + "met2": "met1drawing", + "via1": "mcondrawing", + "met1": "li1drawing", + "mcon": "licon1drawing", + "poly": "polydrawing", + "active_diff": "diffdrawing", + "active_tap": "diffdrawing", + #"active_tap": "tapdrawing", + "n+s/d": "nsdmdrawing", + "p+s/d": "psdmdrawing", + "nwell": "nwelldrawing", + "pwell": "pwelldrawing", + "dnwell": "dnwelldrawing", +} + + +sky130_lydrc_file_path = Path(__file__).resolve().parent / "sky130.lydrc" + + +sky130_mapped_pdk = MappedPDK.from_gf_pdk( + sky130.PDK, + glayers=sky130_glayer_mapping, + grules=grulesobj, + klayout_lydrc_file=sky130_lydrc_file_path, + default_decorator=sky130_add_npc +) +# set the grid size +sky130_mapped_pdk.gds_write_settings.precision = 5*10**-9 +sky130_mapped_pdk.cell_decorator_settings.cache=False +sky130_mapped_pdk.gds_write_settings.flatten_invalid_refs=False diff --git a/openfasoc/generators/gdsfactory-gen/glayout/pdk/util/__init__.py b/openfasoc/generators/gdsfactory-gen/glayout/pdk/util/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/openfasoc/generators/gdsfactory-gen/glayout/pdk/util/comp_utils.py b/openfasoc/generators/gdsfactory-gen/glayout/pdk/util/comp_utils.py new file mode 100644 index 000000000..6905fec6d --- /dev/null +++ b/openfasoc/generators/gdsfactory-gen/glayout/pdk/util/comp_utils.py @@ -0,0 +1,308 @@ +from pydantic import validate_arguments +from gdsfactory.snap import snap_to_grid +from gdsfactory.typings import Component, ComponentReference +from gdsfactory.components.rectangle import rectangle +from gdsfactory.port import Port +from typing import Callable, Union, Optional, Iterable +from decimal import Decimal +from gdsfactory.functions import transformed +from gdsfactory.functions import move as __gf_move +from glayout.pdk.mappedpdk import MappedPDK +from gdstk import rectangle as primitive_rectangle +from .port_utils import add_ports_perimeter, rename_ports_by_list + + +@validate_arguments +def evaluate_bbox(custom_comp: Union[Component, ComponentReference], return_decimal: Optional[bool]=False) -> tuple[Union[float,Decimal],Union[float,Decimal]]: + """returns the length and height of a component like object""" + compbbox = custom_comp.bbox + width = abs(Decimal(str(compbbox[1][0])) - Decimal(str(compbbox[0][0]))) + height = abs(Decimal(str(compbbox[1][1])) - Decimal(str(compbbox[0][1]))) + if return_decimal: + return (width,height) + return (float(width),float(height)) + + +@validate_arguments +def move(custom_comp: Union[Port, ComponentReference, Component], offsetxy: tuple[float,float] = (0,0), destination: Optional[tuple[Optional[float],Optional[float]]]=None, layer: Optional[tuple[int,int]]=None) -> Union[Port, ComponentReference, Component]: + """moves custom_comp + moves by offset[0]=x offset, offset[1]=y offset + destination (x,y) if not none overrides offset option + layer if specfied will move based on a layer (only relevant for destination option) + returns the modified custom_comp + """ + if layer and isinstance(custom_comp, Component): + custom_comp_ext = custom_comp.extract(layers=[layer,]) + elif layer and isinstance(custom_comp, ComponentReference): + raise NotImplementedError("layer not implemented for comp ref") + elif layer and isinstance(custom_comp,Port): + raise TypeError("move:layer option for Port does not exist") + else: + custom_comp_ext = custom_comp + if destination is not None: + xoffset = destination[0] - custom_comp_ext.center[0] if destination[0] is not None else 0 + yoffset = destination[1] - custom_comp_ext.center[1] if destination[1] is not None else 0 + if isinstance(custom_comp, Port): + if destination is None: + custom_comp = custom_comp.move_copy(offsetxy) + else: + custom_comp = custom_comp.move_copy((xoffset,yoffset)) + elif isinstance(custom_comp, ComponentReference): + if destination is None: + custom_comp.movex(offsetxy[0]).movey(offsetxy[1]) + else: + custom_comp.movex(xoffset).movey(yoffset) + elif isinstance(custom_comp, Component): + ref = custom_comp.copy().ref() + if destination is None: + ref.movex(offsetxy[0]).movey(offsetxy[1]) + else: + ref.movex(xoffset).movey(yoffset) + custom_comp = transformed(ref).copy() + return custom_comp + + +@validate_arguments +def movex(custom_comp: Union[Port, ComponentReference, Component], offsetx: Optional[float] = 0, destination: Optional[float]=None, layer: Optional[tuple[int,int]]=None) -> Union[Port, ComponentReference, Component]: + """moves custom_comp by offsetx in the x direction + returns the modified custom_comp + """ + if destination is not None: + destination = (destination, None) + return move(custom_comp, (offsetx,0),destination,layer) + + +@validate_arguments +def movey(custom_comp: Union[Port, ComponentReference, Component], offsety: Optional[float] = 0, destination: Optional[float]=None, layer: Optional[tuple[int,int]]=None) -> Union[Port, ComponentReference, Component]: + """moves custom_comp by offsety in the y direction + returns the modified custom_comp + """ + if destination is not None: + destination = (None, destination) + return move(custom_comp, (0,offsety),destination,layer) + + +@validate_arguments +def align_comp_to_port( + custom_comp: Union[Component,ComponentReference], + align_to: Port, + alignment: Optional[tuple[Optional[str],Optional[str]]] = None, + layer: Optional[tuple[int,int]] = None, + rtr_comp_ref = True +) -> Union[Component,ComponentReference]: + """Returns component/componentReference of component/componentReference aligned to port as specifed + by default returns a componentReference + for componentReference, the componentReference is modified (mutable), but for component, a copy of component is returned + args: + custom_comp = component to align properly + align_to = Port to align to + alignment = tuple(str,str) = (xalign,yalign). You can individually specify x/y algin=None and that means do nothing for that dim + ***NOTE, if left None, function will align component to outside and center of port (based on port orientation), specify (None,None) for real no align (do not move at all) + ****xalign = either l/left or r/right or c/center or None. component will be flush to right or left side of port or centered + ****yalgin = either t/top or b/bottom or c/center or None. top or bottom edge or center of component will align with port top/bottom/center + layer = extract this layer from the component and aligns to this layer. + rtr_comp_ref = will return a component reference if set true, else return component + """ + # find center and bbox + if isinstance(custom_comp, ComponentReference): + comp_type = transformed(custom_comp) + else: + comp_type = custom_comp + if layer: + comp_type = comp_type.extract([layer]) + cbbox = comp_type.bbox + ccenter = comp_type.center + # setup + xdim = abs(cbbox[1][0] - cbbox[0][0]) + ydim = abs(cbbox[1][1] - cbbox[0][1]) + width = align_to.width + is_EW = bool(round(align_to.orientation + 90) % 180) # not EW == NS + # error checks and decide orientation if None + if alignment is None: + if round(align_to.orientation) == 0:# facing east + xalign = "r" + yalign = "c" + elif round(align_to.orientation) == 180:# facing west + xalign = "l" + yalign = "c" + elif round(align_to.orientation) == 270:# facing south + xalign = "c" + yalign = "b" + elif round(align_to.orientation) == 90:#facing north + xalign = "c" + yalign = "t" + else: + raise ValueError("port must be vertical or horizontal") + else: + xalign = (alignment[0] or "none").lower() + yalign = (alignment[1] or "none").lower() + # compute translation x amount for x alignment + x_movcenter = align_to.center[0] - ccenter[0] + x_mov_lr = abs(xdim/2 if is_EW else (width-xdim)/2) + if "none" in xalign: + xmov = 0 + elif "l" in xalign: + xmov = x_movcenter - x_mov_lr + elif "r" in xalign: + xmov = x_movcenter + x_mov_lr + elif "c" in xalign: + xmov = x_movcenter + else: + raise ValueError("please specify valid x alignment of l/r/c/None") + # compute translation y amount for y alignment + y_movcenter = align_to.center[1] - ccenter[1] + y_move_updown = abs((width-ydim)/2 if is_EW else ydim/2) + if "none" in yalign: + ymov = 0 + elif "t" in yalign: + ymov = y_movcenter + y_move_updown + elif "b" in yalign: + ymov = y_movcenter - y_move_updown + elif "c" in yalign: + ymov = y_movcenter + else: + raise ValueError("please specify valid y alignment of t/b/c/None") + # make reference type, execute move + if isinstance(custom_comp, Component): + comp_ref = custom_comp.ref() + else: + comp_ref = custom_comp + comp_ref.movex(xmov).movey(ymov) + # make correct type and return + if rtr_comp_ref: + return comp_ref + else: + return transformed(comp_ref) + + +@validate_arguments +def to_decimal(elements: Union[tuple,list,float,int,str]): + """converts all elements of list like object into decimals + or converts single num into decimal""" + if not isinstance(elements,Iterable): + return Decimal(str(elements)) + else: + elements = list(elements) + for i, element in enumerate(elements): + if isinstance(element,Union[int,float]): + elements[i] = Decimal(str(element)) + return elements + +@validate_arguments +def to_float(elements: Union[tuple,list,Decimal,float]): + """converts all elements of list like object into floats and snaps to grid + or converts single decimal into floats""" + if not isinstance(elements,Iterable): + return snap_to_grid(float(elements)) + else: + elements = list(elements) + for i, element in enumerate(elements): + if isinstance(element, Union[float,Decimal]): + elements[i] = snap_to_grid(float(element)) + return elements + +@validate_arguments +def prec_array(custom_comp: Component, rows: int, columns: int, spacing: tuple[Union[float,Decimal],Union[float,Decimal]], absolute_spacing: Optional[bool]=False) -> Component: + """instead of using the component.add_array function, if you are having grid snapping issues try using this function + works the same way as add_array but uses decimals and snaps to grid to mitigate grid snapping issues + args + custom_comp: Component type to make an array from + columns: num cols in the array + rows: num rows in the array + absolute_spacing: the spacing mode of spacing variable + spacing: IF absolute_spacing spacing BETWEEN elements in the array ELSE spacing BETWEEN ORIGINS of elements in the array + ****NOTE do not use negative spacing, instead specify absolute_spacing=True + """ + # make sure to work with decimals + precspacing = list(spacing) + for i in range(2): + if isinstance(spacing[i],Union[int,float]): + precspacing[i] = Decimal(str(spacing[i])) + if not absolute_spacing: + precspacing = [precspacing[i] + evaluate_bbox(custom_comp,True)[i] for i in range(2)] + # create array + precarray = Component() + for colnum in range(columns): + coldisp = colnum * precspacing[0] + for rownum in range(rows): + rowdisp = rownum * precspacing[1] + cref = precarray << custom_comp + cref.movex(to_float(coldisp)).movey(to_float(rowdisp)) + precarray.add_ports(cref.get_ports_list(),prefix=f"row{rownum}_col{colnum}_") + return precarray.flatten() + + +@validate_arguments +def prec_center(custom_comp: Union[Component,ComponentReference], return_decimal: bool=False) -> tuple[Union[float,Decimal],Union[float,Decimal]]: + """instead of using component.ref_center() to get the center of a component, + use this function which will return the correct offset to center a component + returns (x,y) corrections + if return_decimal=True, return in Decimal, otherwise return float""" + correctmax = [dim/2 for dim in evaluate_bbox(custom_comp, True)] + currentmax = to_decimal((custom_comp.xmax,custom_comp.ymax)) + correctionxy = [correctmax[i] - currentmax[i] for i in range(2)] + if return_decimal: + return correctionxy + return to_float(correctionxy) + +@validate_arguments +def prec_ref_center(custom_comp: Union[Component,ComponentReference], destination: Optional[tuple[float,float]]=None) -> ComponentReference: + """instead of using component.ref_center() to get a ref to center at origin, + use this function which will return a centered ref + you can then run component.add(prec_ref_center(custom_comp)) to add the reference to your component + you can optionally specify a destination = tuple(x,y), by default, the destination is 0,0 + returns component reference + """ + compref = custom_comp if isinstance(custom_comp, ComponentReference) else custom_comp.ref() + xcor, ycor = prec_center(compref, False) + if destination is not None: + xcor += destination[0] + ycor += destination[1] + return compref.movex(xcor).movey(ycor) + + + +def get_padding_points_cc( + custom_comp: Union[ComponentReference, Component, list], + default: float = 50.0, + top: Optional[float]=None, + bottom: Optional[float]=None, + right: Optional[float]=None, + left: Optional[float]=None, + pdk_for_snap2xgrid: Optional[MappedPDK]=None +) -> list: + """works like gdsfactory.add_padding.get_padding_points, but also accepts componentReference or bbox + additionally, if you optionally pass a pdk it will snap to 2x grid (else just operates like get_padding_points)""" + if isinstance(custom_comp, ComponentReference) or isinstance(custom_comp, Component): + bbox = custom_comp.bbox + else: + bbox = custom_comp + top = top if top is not None else default + bottom = bottom if bottom is not None else default + right = right if right is not None else default + left = left if left is not None else default + ppoints = [ + [bbox[0][0] - left, bbox[0][1] - bottom], + [bbox[1][0] + right, bbox[0][1] - bottom], + [bbox[1][0] + right, bbox[1][1] + top], + [bbox[0][0] - left, bbox[1][1] + top], + ] + if pdk_for_snap2xgrid is not None: + for i, ppoint in enumerate(ppoints): + ppoints[i] = pdk_for_snap2xgrid.snap_to_2xgrid(ppoint) + return ppoints + + + +def get_primitive_rectangle(size: tuple[float,float]=(5,3), layer: tuple[int,int]=(0,0)): + """creates a rectangle component which snaps point to grid (does not snap to 2x grid) + has same behavoir as gdsfactory.components.rectangle but doesnt allow centering (would snap to grid) + """ + temprect = Component() + temprect.add_polygon(primitive_rectangle((0,0),size,*layer)) + temprect = rename_ports_by_list(add_ports_perimeter(temprect,layer=layer,prefix="route_"),[("W","e1"),("N","e2"),("E","e3"),("S","e4")]) + #rect = Component() + #clogic_ref = prec_ref_center(temprect) if centered else temprect.ref() + #rect.add(clogic_ref) + #rect.add_ports(clogic_ref.ports) + return temprect.flatten() diff --git a/openfasoc/generators/gdsfactory-gen/glayout/pdk/util/opamp_array_create.py b/openfasoc/generators/gdsfactory-gen/glayout/pdk/util/opamp_array_create.py new file mode 100644 index 000000000..b8be9dd50 --- /dev/null +++ b/openfasoc/generators/gdsfactory-gen/glayout/pdk/util/opamp_array_create.py @@ -0,0 +1,67 @@ +from gdsfactory.read.import_gds import import_gds +from gdsfactory.component import Component +from pathlib import Path +import os +import math +from gdsfactory.pdk import Pdk +from pathlib import Path +from typing import Union, Optional +from pydantic import validate_arguments + +def get_files_with_extension(directory, extension): + file_list = [] + for filename in os.listdir(directory): + if filename.endswith(extension): + file_list.append(filename) + return file_list + + +@validate_arguments +def write_opamp_matrix(opamps_dir: Union[str,Path,list]="./", xspace: float=400,yspace: float=300, rtr_comp: bool=False, write_name: str="big_gds_here.gds"): + """Use the write_opamp_matrix function to create a matrix of many different opamps + reads the different opamps from all gds files in opamps_dir + args: + opamps_dir = a file directory where all gds files are treated as opamps (i.e. to add to the matrix) + ****Note: you can specify this as a list Components, in which case, the list is used to make the matrix + xspace = xspacing to use (center to center x distance between adajacent elements in the matrix) + yspace = yspacing to use (center to center y distance between adajacent elements in the matrix) + rtr_comp = if true will not write the component to gds (default = false) + write_name = name/path of gds write file + """ + pdk_nochache = Pdk(name="nocache") + pdk_nochache.cell_decorator_settings.cache=False + pdk_nochache.activate() + + if isinstance(opamps_dir, list): + opamp_comp_list = opamps_dir + else: + search_dir = Path(opamps_dir).resolve() + opamp_files_list = get_files_with_extension(str(search_dir),".gds") + opamp_comp_list = list() + for i,filev in enumerate(opamp_files_list): + if "big_gds_here" in str(filev) or write_name in str(filev): + continue + tempcomp = import_gds(filev) + tempcomp.name = "opamp"+str(i) + opamp_comp_list.append(tempcomp) + + col_len = round(math.sqrt(len(opamp_comp_list))) + col_index = 0 + row_index = 0 + big_comp = Component("big comp") + for opamp_v in opamp_comp_list: + if opamp_v is None: + continue + opref = big_comp << opamp_v + opref.movex(col_index * xspace).movey(row_index*yspace) + col_index += 1 + if not col_index % col_len: + col_index=0 + row_index += 1 + if rtr_comp: + return big_comp + else: + big_comp.write_gds(write_name) + +if __name__=="__main__": + write_opamp_matrix() diff --git a/openfasoc/generators/gdsfactory-gen/glayout/pdk/util/port_utils.py b/openfasoc/generators/gdsfactory-gen/glayout/pdk/util/port_utils.py new file mode 100644 index 000000000..0ac9956cd --- /dev/null +++ b/openfasoc/generators/gdsfactory-gen/glayout/pdk/util/port_utils.py @@ -0,0 +1,367 @@ +from pydantic import validate_arguments +from gdsfactory.typings import Component, ComponentReference +from gdsfactory.components.rectangle import rectangle +from gdsfactory.port import Port +from typing import Callable, Union, Optional +from decimal import Decimal +from pathlib import Path +import pickle +from PrettyPrint import PrettyPrintTree + + +@validate_arguments +def rename_component_ports(custom_comp: Component, rename_function: Callable[[str, Port], str]) -> Component: + """uses rename_function(str, Port) -> str to decide which ports to rename. + rename_function accepts the current port name (string) and current port (Port) then returns the new port name + rename_function can return new name = current port name, in which case the name will not change + rename_function should raise error if custom requirments for rename are not met + if you want to pass additional args to rename_function, implement a functor + custom_comp is the components to modify. the modified component is returned + """ + names_to_modify = list() + # find ports and get new names + for pname, pobj in custom_comp.ports.items(): + # error checking + if not pname == pobj.name: + raise ValueError("component may have an invalid ports dict") + + new_name = rename_function(pname, pobj) + + names_to_modify.append((pname,new_name)) + # modify names + for namepair in names_to_modify: + if namepair[0] in custom_comp.ports.keys(): + portobj = custom_comp.ports.pop(namepair[0]) + portobj.name = namepair[1] + custom_comp.ports[namepair[1]] = portobj + else: + raise KeyError("name "+str(namepair[0])+" not in component ports") + # returns modified component/component ref + return custom_comp + + +@validate_arguments +def rename_ports_by_orientation__call(old_name: str, pobj: Port) -> str: + """internal implementation of port orientation rename""" + if not "_" in old_name: + raise ValueError("portname must contain underscore \"_\" " + old_name) + # get new suffix (port orientation) + new_suffix = None + angle = pobj.orientation % 360 if pobj.orientation is not None else 0 + angle = round(angle) + if angle <= 45 or angle >= 315: + new_suffix = "E" + elif angle <= 135 and angle >= 45: + new_suffix = "N" + elif angle <= 225 and angle >= 135: + new_suffix = "W" + else: + new_suffix = "S" + # construct new name + old_str_split = old_name.rsplit("_", 1) + old_str_split[1] = new_suffix + new_name = "_".join(old_str_split) + return new_name + +@validate_arguments +def rename_ports_by_orientation(custom_comp: Component) -> Component: + """replaces the last part of the port name + (after the last underscore) with a direction + direction is one of N,E,S,W + returns the modified component + """ + return rename_component_ports(custom_comp, rename_ports_by_orientation__call) + + +class rename_ports_by_list__call: + def __init__(self, replace_list: list[tuple[str,str]] = []): + self.replace_list = dict(replace_list) + self.replace_history = dict.fromkeys(self.replace_list.keys()) + for keyword in self.replace_history: + self.replace_history[keyword] = 0 + @validate_arguments + def __call__(self, old_name: str, pobj: Port) -> str: + for keyword, newname in self.replace_list.items(): + if keyword in old_name: + inst_id = self.replace_history[keyword] + replace_name = newname + str(inst_id if inst_id else "") + self.replace_history[keyword] += 1 + return replace_name + return old_name + +@validate_arguments +def rename_ports_by_list(custom_comp: Component, replace_list: list[tuple[str,str]]) -> Component: + """replace_list is a list of tuple(string, string) + if a port name contains tuple[0], the port will be renamed to tuple[1] + if tuple[1] is None or empty string raise error + when anaylzing a single port, if multiple keywords from the replace_list are found, first match is returned + since we cannot have duplicate port names, different ports that end up with the same name get numbered""" + rename_func = rename_ports_by_list__call(replace_list) + return rename_component_ports(custom_comp, rename_func) + + +@validate_arguments +def add_ports_perimeter(custom_comp: Component, layer: tuple[int, int], prefix: Optional[str] = "_") -> Component: + """adds ports to the outside perimeter of a cell + custom_comp = component to add ports to (returns the modified component) + layer = will extract this layer and take it as the bbox, ports will also be on this layer + prefix = prefix to add to the port names. Adds an underscore by default + """ + if "_" not in prefix: + raise ValueError("you need underscore char in prefix") + compbbox = custom_comp.extract(layers=(layer,)).bbox + width = compbbox[1][0] - compbbox[0][0] + height = compbbox[1][1] - compbbox[0][1] + size = (width, height) + temp = Component() + swref = temp << rectangle(layer=layer,size=size) + swref.move(destination=(custom_comp.bbox[0])) + temp.add_ports(swref.get_ports_list(),prefix=prefix) + temp = rename_ports_by_orientation(temp) + custom_comp.add_ports(temp.get_ports_list()) + return custom_comp + + +@validate_arguments +def get_orientation(orientation: Union[int,float,str], int_only: bool=False) -> Union[float,int,str]: + """returns the angle corresponding to port orientation + orientation must contain N/n,E/e,S/s,W/w + e.g. all the follwing are valid: + N/n or N/north,E/e or E/east,S/s or S/south, W/w or W/west + if int_only, will return int regardless of input type, + else will return the opposite type of that given + (i.e. will return str if given int/float and int if given str) + """ + if isinstance(orientation,str): + orientation = orientation.lower() + if "n" in orientation: + return 90 + elif "e" in orientation: + return 0 + elif "w" in orientation: + return 180 + elif "s" in orientation: + return 270 + else: + raise ValueError("orientation must contain N/n,E/e,S/s,W/w") + else:# must be a float/int + orientation = int(orientation) + if int_only: + return orientation + orientation_index = int((orientation % 360) / 90) + orientations = ["E","N","W","S"] + try: + orientation = orientations[orientation_index] + except IndexError as e: + raise ValueError("orientation must be 0,90,180,270 to use this function") + return orientation + + +@validate_arguments +def assert_port_manhattan(edges: Union[list[Port],Port]) -> bool: + """raises assertionerror if port is not vertical or horizontal""" + if isinstance(edges, Port): + edges = [edges] + for edge in edges: + if round(edge.orientation) % 90 != 0: + raise AssertionError("edge is not vertical or horizontal") + return True + + +@validate_arguments +def assert_ports_perpindicular(edge1: Port, edge2: Port) -> bool: + """raises assertionerror if edges are not perindicular""" + or1 = round(edge1.orientation) + or2 = round(edge2.orientation) + angle_difference = abs(round(or1-or2)) + if angle_difference != 90 and angle_difference != 270: + raise AssertionError("edges are not perpindicular") + return True + + +@validate_arguments +def set_port_orientation(custom_comp: Port, orientation: Union[float, int, str], flip180: Optional[bool]=False) -> Port: + """creates a new port with the desired orientation and returns the new port""" + if isinstance(orientation,str): + orientation = get_orientation(orientation, int_only=True) + if flip180: + orientation = (orientation + 180) % 360 + newport = Port( + name = custom_comp.name, + center = custom_comp.center, + orientation = orientation, + parent = custom_comp.parent, + port_type = custom_comp.port_type, + cross_section = custom_comp.cross_section, + shear_angle = custom_comp.shear_angle, + layer = custom_comp.layer, + width = custom_comp.width, + ) + return newport + + +@validate_arguments +def set_port_width(custom_comp: Port, width: float) -> Port: + """creates a new port with the desired width and returns the new port""" + newport = Port( + name = custom_comp.name, + center = custom_comp.center, + orientation = custom_comp.orientation, + parent = custom_comp.parent, + port_type = custom_comp.port_type, + cross_section = custom_comp.cross_section, + shear_angle = custom_comp.shear_angle, + layer = custom_comp.layer, + width = width, + ) + return newport + + +@validate_arguments +def print_ports(custom_comp: Union[Component, ComponentReference], names_only: Optional[bool] = True) -> None: + """prints ports in comp in a nice way + custom_comp = component to use + names_only = only print names if True else print name and port + """ + for key,val in custom_comp.ports.items(): + print(key) + if not names_only: + print(val) + print() + + +class PortTree: + """PortTree helps a glayout programmer visualize the ports in a component + \"_\" should represent a level of hiearchy (much like a directory). think of this like psuedo directories + Initialize a PortTree from a Component or ComponentReference + then use self.ls to list all ports/subdirectories in a directory + you can use self.print to prettyprint a port tree (uses pypi prettyprinttree package) + + You should not need to access the internal dictionary for the tree, but if you do: + PortTree internally uses tuple[str, dict] = name:children as the node type + since the PortTree is not a node type (PortTree is not a real tree class), the root node is: (self.name, self.tree) + """ + + @validate_arguments + def __init__(self, custom_comp: Union[Component, ComponentReference], name: Optional[str]=None): + """creates the tree structure from the ports where _ represent subdirectories + credit -> chatGPT + """ + file_list = custom_comp.ports.keys() + directory_tree = {} + for file_path in file_list: + path_components = file_path.split('_') + current_dir = directory_tree + for path_component in path_components: + if path_component not in current_dir: + current_dir[path_component] = {} + current_dir = current_dir[path_component] + self.tree = directory_tree + self.name = name if name else custom_comp.name + + @validate_arguments + def ls(self, file_path: Optional[str] = None) -> list[str]: + """tries to traverse the tree along the given path and prints all subdirectories in a psuedo directory + if the path given is not found in the tree, raises KeyError + path should not end with \"_\" char + """ + if file_path is None or len(file_path)==0: + return list(self.tree.keys()) + path_components = file_path.split('_') + current_dir = self.tree + for path_component in path_components: + if path_component not in current_dir: + raise KeyError("Port path was not found") + current_dir = current_dir[path_component] + return list(current_dir.keys()) + + @validate_arguments + def save_to_disk(self, savedir: Union[Path, str]="./"): + savedir = Path(savedir).resolve() + savedir.mkdir(exist_ok=True,parents=True) + if not savedir.is_dir(): + raise ValueError("no dir named" + str(savedir)) + with open(savedir / "porttree.pkl", 'wb') as outfile: + pickle.dump(self, outfile) + + @classmethod + def read_from_disk(cls, pklfile: Union[Path, str]): + pklfile = Path(pklfile).resolve() + if not pklfile.is_file(): + raise ValueError("no file named" + str(pklfile)) + with open(str(pklfile), 'rb') as infile: + return pickle.load(infile) + + def get_children(self, node: tuple[str, dict]) -> list[tuple[str, dict]]: + """access children of internal tree node (node might be a PortTree)""" + node_dict = node[1] if isinstance(node, tuple) else self.tree + return node_dict.items() + + + def get_val(self, node: tuple[str, dict]) -> str: + """returns value of a node, (node might be a PortTree)""" + return node[0] if isinstance(node, tuple) else self.name + + def print(self, savetofile: bool=True, default_opts: bool=True, depth: Optional[int]=None, outfile_name: Optional[str]=None, **kwargs): + """prints output to terminal directly using prettyprinttree pypi package + args: + depth = max depth to print. this is a kwarg but since it so common, it should be specfied from depth arg + savetofile = saves print output to a txt file rather than printing to terminal (easier to view, but without nice formatting) + default_opts = bool=True results in using glayout recommended default print arguments + kwargs -> kwargs are prettyprint options passed directly to prettyprint. + ****NOTE: kwargs override all other options + """ + depth = int(depth) if (depth is not None and depth>0) else -1 + extra_kwargs = {} + if default_opts: + extra_kwargs.update({"default_orientation": True}) + if savetofile: + extra_kwargs.update({"return_instead_of_print":savetofile, "color":None, "border":True, "default_orientation": True}) + extra_kwargs.update(kwargs) + pt = PrettyPrintTree(self.get_children, self.get_val, max_depth=depth, **extra_kwargs) + rtrstr = pt(self) + if rtrstr: + outfile_name = "outputtree.txt" if outfile_name is None else outfile_name + with open(outfile_name,"w") as outputfile: + outputfile.write(rtrstr) + + +def print_port_tree_all_cells() -> list: + """print the PortTree for most of the glayout cells and save as a text file. + returns a list of components + """ + from glayout.via_gen import via_stack, via_array + from glayout.opamp import opamp + from glayout.mimcap import mimcap + from glayout.mimcap import mimcap_array + from glayout.guardring import tapring + from glayout.fet import multiplier, nmos, pmos + from glayout.diff_pair import diff_pair + from glayout.routing.straight_route import straight_route + from glayout.routing.c_route import c_route + from glayout.routing.L_route import L_route + from glayout.pdk.sky130_mapped import sky130_mapped_pdk as pdk + from gdsfactory.port import Port + print("saving via_stack, via_array, opamp, mimcap, mimcap_array, tapring, multiplier, nmos, pmos, diff_pair, straight_route, c_route, L_route Ports to txt files") + celllist = list() + celllist.append(["via_stack",via_stack(pdk, "active_diff","met5")]) + celllist.append(["viaarray", via_array(pdk, "active_diff","met5", num_vias=(2,3))]) + celllist.append(["mimcap",mimcap(pdk)]) + celllist.append(["mimcap_array",mimcap_array(pdk, 2, 3)]) + celllist.append(["tapring",tapring(pdk)]) + celllist.append(["multiplier",multiplier(pdk,"n+s/d")]) + celllist.append(["nmos", nmos(pdk,fingers=2,multipliers=2)]) + celllist.append(["pmos", pmos(pdk,fingers=2,multipliers=2)]) + celllist.append(["diff_pair",diff_pair(pdk)]) + psuedo_porta = Port("bottom",90,(0,0),2,layer=pdk.get_glayer("met2")) + psuedo_portb = Port("top",0,(5,10),2.5,layer=pdk.get_glayer("met5")) + psuedo_porta = Port("right",90,(10,10),2,layer=pdk.get_glayer("met2")) + celllist.append(["straight_route",straight_route(pdk,psuedo_porta,psuedo_portb)]) + celllist.append(["L_route",L_route(pdk, psuedo_porta, psuedo_portb)]) + celllist.append(["c_route",c_route(pdk, psuedo_porta, psuedo_porta,extension=2)]) + celllist.append(["opamp",opamp(pdk)]) + for name, py_cell in celllist: + from glayout import __version__ as glayoutvinfo + glayoutv = str(glayoutvinfo) + PortTree(py_cell,name=name).print(depth=5,outfile_name=name+"_v"+glayoutv+"_tree.txt",default_orientation=True) + return celllist diff --git a/openfasoc/generators/gdsfactory-gen/glayout/pdk/util/print_rules.py b/openfasoc/generators/gdsfactory-gen/glayout/pdk/util/print_rules.py new file mode 100644 index 000000000..e63812200 --- /dev/null +++ b/openfasoc/generators/gdsfactory-gen/glayout/pdk/util/print_rules.py @@ -0,0 +1,136 @@ +"""read rule deck we have saved in google sheets into a better format for writing to python: +this is the google sheets format +https://docs.google.com/spreadsheets/d/172P-CW_dGQU5icyUQAasLVq7Jlz51R5lB1rJ4JyvfIc/edit?usp=sharing + +directions +1) go to the google sheets and download as .csv +2) run this program with the csv input +""" + +import csv +from pathlib import Path +from pydantic import validate_arguments + + +def split_rule(rule: str) -> tuple: + """Accepts a rule in the expected format and splits into rule name and float value""" + if (rule != "") and (not "," in rule): + raise ValueError("rule may be formatted wrong " + rule) + rule = rule.replace(" ", "").split(",", maxsplit=1)[-1] + rtr = rule.split("=") + if len(rtr) != 2: + rtr.append("*****FIXTHIS!!!MANUALLY!*****") + elif "," in rtr[1]: + strlist = rtr[1].replace("(", "").replace(")", "").split(",") + rtr[1] = tuple([int(layint) for layint in strlist]) + else: + rtr[1] = float(rtr[1]) + return tuple(rtr) + + +def __str_rules(groupdata: tuple, group: list, glayers: list) -> str: + """appends the rules in groupdata to output""" + group_rules = str() + for edgenum, edge in enumerate(groupdata): + group_rules += 'grulesobj["' + group[1] + '"]' + group_rules += '["' + glayers[edgenum] + '"] = ' + str(edge) + group_rules += "\n" + return group_rules + + +def create_ruledeck_python_dictionary_definition(csvtoread: Path): + if not csvtoread.is_file(): + raise RuntimeError("csv to read must be a file") + output = str() + glayers = list() + groupdata = [] # list of dictionary + group = [0, "none"] + # int,string -> current group + with open(csvtoread, newline="") as csvfile: + myreader = csv.reader(csvfile, delimiter=",") + for rownum, row in enumerate(myreader): + # deal with header and ignore label rows + if rownum == 0: + glayers = row + glayers.pop(0) + elif rownum < 3: + continue + # processing logic + # the google sheets csv format is in rows of 3 + # we use group and groupdata to track all rules in a row then update output + else: + if group[0] == 0: # first in group + groupdata.clear() + group[1] = row[0] + for colnum, col in enumerate(row): + if colnum == 0: + continue + groupdata.append(dict()) + for colnum, col in enumerate(row): + if colnum == 0: + continue + if col == "": + continue + key_val_pair = split_rule(col) + groupdata[colnum - 1][key_val_pair[0]] = key_val_pair[1] + # finished with the group + if group[0] == 2: # last in group + output += __str_rules(groupdata, group, glayers) + # update group index + group[0] = (group[0] + 1) % 3 + # incase missed last group print one more time + last_grp_rules = __str_rules(groupdata, group, glayers) + output += "\n" if last_grp_rules in output else last_grp_rules + return output + + +@validate_arguments +def visualize_ruleset(ruleset: dict): + """use networkx to print a visual of the ruleset graph + nodes are glayers (strings) + edges are rules (tuple[name:value]) + """ + import pygraphviz as pgv + ruleGraph = pgv.AGraph(strict=False, directed=False, multigraph=True) + ruleGraph.add_nodes_from(ruleset.keys()) + for glayer1 in ruleset.keys(): + for glayer2 in ruleset[glayer1].keys(): + for rule, value in ruleset[glayer1][glayer2].items(): + color="black" + if "min_separ" in rule: + color="red" + elif "min_enclos" in rule: + color = "blue" + elif "width" in rule: + color = "orange" + #ruleGraph.add_edge(glayer1,glayer2,label=value,color=color,key=rule) + ruleGraph.add_edge(glayer1,glayer2,color=color,key=rule) + ruleGraph.draw("test.png",format="png",prog="dot") + + +if __name__ == "__main__": + from argparse import ArgumentParser + + parser = ArgumentParser( + prog="print rules", description="read rule deck we have saved in google sheets" + ) + parser.add_argument("-f", "--file", help="path of csv file to read") + parser.add_argument( + "-c", + "--code", + action="store_true", + help="true/false write python file to current dir", + ) + args = parser.parse_args() + csvtoread = Path(args.file).resolve() + output = create_ruledeck_python_dictionary_definition(csvtoread) + print(output) + if args.code: + append_front = """from pdk.mappedpdk import MappedPDK\n +grulesobj = dict() +for glayer in MappedPDK.valid_glayers: + grulesobj[glayer] = dict((x, None) for x in MappedPDK.valid_glayers)\n +""" + output = append_front + output + with open("grules.py", "w") as outputpy: + outputpy.write(output) diff --git a/openfasoc/generators/gdsfactory-gen/glayout/pdk/util/snap_to_grid.py b/openfasoc/generators/gdsfactory-gen/glayout/pdk/util/snap_to_grid.py new file mode 100644 index 000000000..a1464edc8 --- /dev/null +++ b/openfasoc/generators/gdsfactory-gen/glayout/pdk/util/snap_to_grid.py @@ -0,0 +1,18 @@ +from gdsfactory.typings import Component +from pydantic import validate_arguments + + +@validate_arguments +def component_snap_to_grid(comp: Component) -> Component: + """snaps all polygons and ports in component to grid + comp = the component to snap to grid + NOTE this function will flatten the component + """ + #return comp.flatten() + # flatten the component then copy (snaps polygons and ports to grid) + name = comp.name + comp = comp.flatten().copy() + comp.name = name + return comp + + diff --git a/openfasoc/generators/gdsfactory-gen/glayout/pdk/util/standard_main.py b/openfasoc/generators/gdsfactory-gen/glayout/pdk/util/standard_main.py new file mode 100644 index 000000000..ad50dfd6d --- /dev/null +++ b/openfasoc/generators/gdsfactory-gen/glayout/pdk/util/standard_main.py @@ -0,0 +1,33 @@ +"""Provides a reusable module for creating generator main functionality +imports all pdks +provides a command line arg for choosing pdk: -p or --pdk + current options include: + gf180 + sky130 + +generator main function can import this module as follows: +from pdk.util.standard_main import pdk + +the pdk is the pdk object which defaults to sky130 if none selected +""" + +from argparse import ArgumentParser + +parser = ArgumentParser(prog="pdk agnostic generator") +parser.add_argument("--pdk", "-p", choices=["sky130", "gf180"]) +args = parser.parse_known_args() + +pdk = None + +# WARNING: DO NOT CHANGE standard behavoir (importing sky130 by default) + +if args[0].pdk == "gf180": + from glayout.pdk.gf180_mapped import gf180_mapped_pdk + pdk = gf180_mapped_pdk +else: #default to sky130 + from glayout.pdk.sky130_mapped import sky130_mapped_pdk + pdk = sky130_mapped_pdk + +pdk.activate() + + diff --git a/openfasoc/generators/gdsfactory-gen/glayout/routing/L_route.py b/openfasoc/generators/gdsfactory-gen/glayout/routing/L_route.py new file mode 100644 index 000000000..200c0e7c2 --- /dev/null +++ b/openfasoc/generators/gdsfactory-gen/glayout/routing/L_route.py @@ -0,0 +1,120 @@ +from gdsfactory.cell import cell +from gdsfactory.component import Component +from gdsfactory.port import Port +from glayout.pdk.mappedpdk import MappedPDK +from typing import Optional, Union +from glayout.via_gen import via_stack, via_array +from glayout.pdk.util.comp_utils import evaluate_bbox, align_comp_to_port, to_decimal, to_float, prec_ref_center, get_primitive_rectangle +from glayout.pdk.util.port_utils import rename_ports_by_orientation, rename_ports_by_list, print_ports, assert_port_manhattan, assert_ports_perpindicular +from decimal import Decimal + + +@cell +def L_route( + pdk: MappedPDK, + edge1: Port, + edge2: Port, + vwidth: Optional[float] = None, + hwidth: Optional[float] = None, + hglayer: Optional[str] = None, + vglayer: Optional[str] = None, + viaoffset: Optional[Union[tuple[bool,bool],bool]]=True, + fullbottom: bool = True +) -> Component: + """creates a L shaped route between two Ports. + + edge1 + | + ------|edge2 + + REQUIRES: + - ports (a.k.a. edges) be vertical or horizontal + - edges be perpindicular to each other + + DOES NOT REQUIRE: + - correct 180 degree orientation of the port (e.g. a south facing port may result in north facing route) + + ****NOTE: does no drc error checking (creates a dumb route) + args: + pdk = pdk to use + edge1 = first port + edge2 = second port + vwidth = optional will default to vertical edge width if None + hwidth = optional will default to horizontal edge width if None + hglayer = glayer for vertical route. Defaults to the layer of the edge oriented N/S + vglayer = glayer for horizontal route. Defaults to the layer of the edge oriented E/W + viaoffset = push the via away from both edges so that inside corner aligns with via corner + ****via offset can also be specfied as a tuple(bool,bool): movex? if viaoffset[0] and movey? if viaoffset[1] + fullbottom = fullbottom option for via + """ + # error checking, TODO: validate layers + assert_port_manhattan([edge1,edge2]) + assert_ports_perpindicular(edge1,edge2) + pdk.activate() + Lroute = Component() + # figure out which port is vertical + vport = None + hport = None + edge1_is_EW = bool(round(edge1.orientation + 90) % 180) + if edge1_is_EW: + vport, hport = edge1, edge2 + else: + hport, vport = edge1, edge2 + # arg setup + vwidth = to_decimal(vwidth if vwidth else vport.width) + hwidth = to_decimal(hwidth if hwidth else hport.width) + hglayer = hglayer if hglayer else pdk.layer_to_glayer(vport.layer) + vglayer = vglayer if vglayer else pdk.layer_to_glayer(hport.layer) + if isinstance(viaoffset,bool): + viaoffset = (True,True) if viaoffset else (False,False) + # compute required dimensions + hdim_center = to_decimal(vport.center[0]) - to_decimal(hport.center[0]) + vdim_center = to_decimal(hport.center[1]) - to_decimal(vport.center[1]) + hdim = abs(hdim_center) + hwidth/2 + vdim = abs(vdim_center) + vwidth/2 + # create and place vertical and horizontal connections + hconnect = get_primitive_rectangle(size=to_float((hdim,vwidth)),layer=pdk.get_glayer(hglayer)) + vconnect = get_primitive_rectangle(size=to_float((hwidth,vdim)),layer=pdk.get_glayer(vglayer)) + #xalign + valign = ("l","c") if hdim_center > 0 else ("r","c") + halign = ("c","b") if vdim_center > 0 else ("c","t") + #yalign + hconnect_ref = align_comp_to_port(hconnect, vport, valign) + Lroute.add(hconnect_ref) + vconnect_ref = align_comp_to_port(vconnect, hport, halign) + Lroute.add(vconnect_ref) + # create and place via (decide between via stack and via array) + hv_via = via_stack(pdk, hglayer, vglayer,fullbottom=fullbottom,fulltop=True) + hv_via_dims = evaluate_bbox(hv_via,True) + use_stack = hv_via_dims[0] > hwidth or hv_via_dims[1] > vwidth + if not use_stack: + hv_via = via_array(pdk, hglayer, vglayer, size=to_float((hwidth,vwidth)), lay_bottom=True) + h_to_v_via_ref = prec_ref_center(hv_via) + Lroute.add(h_to_v_via_ref) + h_to_v_via_ref.move(destination=(hport.center[0], vport.center[1])) + if viaoffset[0] or viaoffset[1]: + viadim_osx = evaluate_bbox(h_to_v_via_ref,True)[0]/2 + viaxofs = abs(hwidth/2-viadim_osx) + viaxofs = to_float(viaxofs if hdim_center > 0 else -1*viaxofs) + viaxofs = viaxofs if viaoffset[0] else 0 + viadim_osy = evaluate_bbox(h_to_v_via_ref,True)[1]/2 + viayofs = abs(vwidth/2-viadim_osy) + viayofs = to_float(viayofs if vdim_center > 0 else -1*viayofs) + viayofs = viayofs if viaoffset[1] else 0 + h_to_v_via_ref.movex(viaxofs).movey(viayofs) + # add ports and return + Lroute.add_ports(h_to_v_via_ref.get_ports_list()) + return rename_ports_by_orientation(Lroute.flatten()) + + +if __name__ == "__main__": + from glayout.pdk.util.standard_main import pdk + + routebetweentop = get_primitive_rectangle(layer=pdk.get_glayer("met1"),size=(1,1)).ref() + routebetweentop.movey(-4).movex(7) + routebetweenbottom = get_primitive_rectangle(layer=pdk.get_glayer("met1"), size=(1, 0.5)) + mycomp = L_route(pdk,routebetweentop.ports["e4"],routebetweenbottom.ports["e1"]) + mycomp.unlock() + mycomp.add(routebetweentop) + mycomp << routebetweenbottom + mycomp.flatten().show() diff --git a/openfasoc/generators/gdsfactory-gen/glayout/routing/c_route.py b/openfasoc/generators/gdsfactory-gen/glayout/routing/c_route.py new file mode 100644 index 000000000..084125715 --- /dev/null +++ b/openfasoc/generators/gdsfactory-gen/glayout/routing/c_route.py @@ -0,0 +1,214 @@ +from gdsfactory.cell import cell +from gdsfactory.component import Component +from gdsfactory.port import Port +from glayout.pdk.mappedpdk import MappedPDK +from typing import Optional, Union +from math import isclose +from glayout.via_gen import via_stack +from gdsfactory.routing.route_quad import route_quad +from gdsfactory.components.rectangle import rectangle +from glayout.pdk.util.comp_utils import evaluate_bbox, get_primitive_rectangle +from glayout.pdk.util.port_utils import add_ports_perimeter, rename_ports_by_orientation, rename_ports_by_list, print_ports, set_port_width, set_port_orientation, get_orientation +from pydantic import validate_arguments +from gdsfactory.snap import snap_to_grid + + +@validate_arguments +def __fill_empty_viastack__macro(pdk: MappedPDK, glayer: str, size: tuple[float,float]) -> Component: + """returns a rectangle with ports that pretend to be viastack ports""" + comp = rectangle(size=size,layer=pdk.get_glayer(glayer),centered=True) + return rename_ports_by_orientation(rename_ports_by_list(comp,replace_list=[("e","top_met_")])).flatten() + +@cell +def c_route( + pdk: MappedPDK, + edge1: Port, + edge2: Port, + extension: Optional[float]=0.5, + width1: Optional[float] = None, + width2: Optional[float] = None, + cwidth: Optional[float] = None, + e1glayer: Optional[str] = None, + e2glayer: Optional[str] = None, + cglayer: Optional[str] = None, + viaoffset: Optional[Union[bool,tuple[Optional[bool],Optional[bool]]]]=(True,True), + fullbottom: Optional[bool] = False +) -> Component: + """creates a C shaped route between two Ports. + + edge1--| + | + edge2--| + + REQUIRES: ports be parralel vertical or horizontal edges + ****NOTE: does no drc error checking (creates a dumb route) + args: + pdk = pdk to use + edge1 = first port + edge2 = second port + width1 = optional will default to edge1 width if None + width2 = optional will default to edge2 width if None + e1glayer = glayer for the parts connecting to the edge1. Default to layer of edge1 + e2glayer = glayer for the parts connecting to the edge2. Default to layer of edge2 + cglayer = glayer for the connection part (part that goes through a via) defaults to e1glayer met+1 + viaoffset = offsets the via so that it is flush with the cglayer (may be needed for drc) i.e. -| vs _| + - True offsets via towards the other via + - False offsets via away from the other via + - None means center (no offset) + ****NOTE: viaoffset pushes both vias towards each other slightly + """ + extension = snap_to_grid(extension) + # error checking and figure out args + if round(edge1.orientation) % 90 or round(edge2.orientation) % 90: + raise ValueError("Ports must be vertical or horizontal") + if not isclose(edge1.orientation,edge2.orientation): + raise ValueError("Ports must be parralel and have same orientation") + width1 = width1 if width1 else edge1.width + width2 = width2 if width2 else edge1.width + e1glayer = e1glayer if e1glayer else pdk.layer_to_glayer(edge1.layer) + e2glayer = e2glayer if e2glayer else pdk.layer_to_glayer(edge2.layer) + eglayer_plusone = "met" + str(int(e1glayer[-1])+1) + cglayer = cglayer if cglayer else eglayer_plusone + if not "met" in e1glayer or not "met" in e2glayer or not "met" in cglayer: + raise ValueError("given layers must be metals") + viaoffset = (None, None) if viaoffset is None else viaoffset + if isinstance(viaoffset,bool): + viaoffset = (True,True) if viaoffset else (False,False) + pdk.has_required_glayers([e1glayer,e2glayer,cglayer]) + pdk.activate() + # create route + croute = Component() + viastack1 = via_stack(pdk,e1glayer,cglayer,fullbottom=fullbottom,assume_bottom_via=True) + viastack2 = via_stack(pdk,e2glayer,cglayer,fullbottom=fullbottom,assume_bottom_via=True) + if e1glayer == e2glayer: + pass + elif e1glayer == cglayer: + viastack1 = __fill_empty_viastack__macro(pdk,e1glayer,size=evaluate_bbox(viastack2)) + elif e2glayer == cglayer: + viastack2 = __fill_empty_viastack__macro(pdk,e2glayer,size=evaluate_bbox(viastack1)) + # find extension + e1_length = snap_to_grid(extension + evaluate_bbox(viastack1)[0]) + e2_length = snap_to_grid(extension + evaluate_bbox(viastack2)[0]) + xdiff = snap_to_grid(abs(edge1.center[0] - edge2.center[0])) + ydiff = snap_to_grid(abs(edge1.center[1] - edge2.center[1])) + if not isclose(edge1.center[0],edge2.center[0]): + if round(edge1.orientation) == 0:# facing east + if edge1.center[0] > edge2.center[0]: + e2_length += xdiff + else: + e1_length += xdiff + elif round(edge1.orientation) == 180:# facing west + if edge1.center[0] < edge2.center[0]: + e2_length += xdiff + else: + e1_length += xdiff + if not isclose(edge1.center[1],edge2.center[1]): + if round(edge1.orientation) == 270:# facing south + if edge1.center[1] < edge2.center[1]: + e2_length += ydiff + else: + e1_length += ydiff + elif round(edge1.orientation) == 90:#facing north + if edge1.center[1] > edge2.center[1]: + e2_length += ydiff + else: + e1_length += ydiff + # move into position + e1_extension_comp = Component("edge1 extension") + e2_extension_comp = Component("edge2 extension") + box_dims = [(e1_length, width1),(e2_length, width2)] + if round(edge1.orientation) == 90 or round(edge1.orientation) == 270: + box_dims = [(width1, e1_length),(width2, e2_length)] + rect_c1 = get_primitive_rectangle(size=box_dims[0], layer=pdk.get_glayer(e1glayer)) + rect_c2 = get_primitive_rectangle(size=box_dims[1], layer=pdk.get_glayer(e2glayer)) + rect_c1 = rename_ports_by_orientation(rename_ports_by_list(rect_c1,[("e","e_")])) + rect_c2 = rename_ports_by_orientation(rename_ports_by_list(rect_c2,[("e","e_")])) + e1_extension = e1_extension_comp << rect_c1 + e2_extension = e2_extension_comp << rect_c2 + e1_extension.move(destination=edge1.center) + e2_extension.move(destination=edge2.center) + if round(edge1.orientation) == 0:# facing east + e1_extension.movey(0-evaluate_bbox(e1_extension)[1]/2) + e2_extension.movey(0-evaluate_bbox(e2_extension)[1]/2) + elif round(edge1.orientation) == 180:# facing west + e1_extension.movex(0-evaluate_bbox(e1_extension)[0]) + e2_extension.movex(0-evaluate_bbox(e2_extension)[0]) + e1_extension.movey(0-evaluate_bbox(e1_extension)[1]/2) + e2_extension.movey(0-evaluate_bbox(e2_extension)[1]/2) + elif round(edge1.orientation) == 270:# facing south + e1_extension.movex(0-evaluate_bbox(e1_extension)[0]/2) + e2_extension.movex(0-evaluate_bbox(e2_extension)[0]/2) + e1_extension.movey(0-evaluate_bbox(e1_extension)[1]) + e2_extension.movey(0-evaluate_bbox(e2_extension)[1]) + else:#facing north + e1_extension.movex(0-evaluate_bbox(e1_extension)[0]/2) + e2_extension.movex(0-evaluate_bbox(e2_extension)[0]/2) + # place viastacks + e1_extension_comp.add_ports(e1_extension.get_ports_list()) + e2_extension_comp.add_ports(e2_extension.get_ports_list()) + me1 = e1_extension_comp << viastack1 + me2 = e2_extension_comp << viastack2 + route_ports = [None,None] + via_flush = snap_to_grid(abs((width1 - evaluate_bbox(viastack1)[0])/2) if viaoffset else 0) + via_flush1 = via_flush if viaoffset[0] else 0-via_flush + via_flush1 = 0 if viaoffset[0] is None else via_flush1 + via_flush2 = via_flush if viaoffset[1] else 0-via_flush + via_flush2 = 0 if viaoffset[1] is None else via_flush2 + if round(edge1.orientation) == 0:# facing east + me1.move(destination=e1_extension.ports["e_E"].center) + me2.move(destination=e2_extension.ports["e_E"].center) + via_flush *= 1 if me1.ymax > me2.ymax else -1 + me1.movex(0-viastack1.xmax).movey(0-via_flush1) + me2.movex(0-viastack2.xmax).movey(via_flush2) + me1, me2 = (me1, me2) if (me1.origin[1] > me2.origin[1]) else (me2, me1) + route_ports = [me1.ports["top_met_N"],me2.ports["top_met_S"]] + elif round(edge1.orientation) == 180:# facing west + me1.move(destination=e1_extension.ports["e_W"].center) + me2.move(destination=e2_extension.ports["e_W"].center) + via_flush *= 1 if me1.ymax > me2.ymax else -1 + me1.movex(viastack1.xmax).movey(0-via_flush1) + me2.movex(viastack2.xmax).movey(via_flush2) + me1, me2 = (me1, me2) if (me1.origin[1] > me2.origin[1]) else (me2, me1) + route_ports = [me1.ports["top_met_N"],me2.ports["top_met_S"]] + elif round(edge1.orientation) == 270:# facing south + me1.move(destination=e1_extension.ports["e_S"].center) + me2.move(destination=e2_extension.ports["e_S"].center) + via_flush *= 1 if me1.xmax > me2.xmax else -1 + me1.movey(viastack1.xmax).movex(0-via_flush1) + me2.movey(viastack2.xmax).movex(via_flush2) + me1, me2 = (me1, me2) if (me1.origin[0] > me2.origin[0]) else (me2, me1) + route_ports = [me1.ports["top_met_E"],me2.ports["top_met_W"]] + else:#facing north + me1.move(destination=e1_extension.ports["e_N"].center) + me2.move(destination=e2_extension.ports["e_N"].center) + via_flush *= 1 if me1.xmax > me2.xmax else -1 + me1.movey(0-viastack1.xmax).movex(0-via_flush1) + me2.movey(0-viastack2.xmax).movex(via_flush2) + me1, me2 = (me1, me2) if (me1.origin[0] > me2.origin[0]) else (me2, me1) + route_ports = [me1.ports["top_met_E"],me2.ports["top_met_W"]] + # connect extensions, add ports, return + croute << e1_extension_comp + croute << e2_extension_comp + if cwidth: + route_ports = [set_port_width(port_,cwidth) for port_ in route_ports] + route_ports[0].width = route_ports[1].width = max(route_ports[0].width, route_ports[1].width) + cconnection = croute << route_quad(route_ports[0],route_ports[1],layer=pdk.get_glayer(cglayer)) + for i,port_to_add in enumerate(route_ports): + orta = get_orientation(port_to_add.orientation) + #orta = "S" if orta=="N" else ("N" if orta=="S" else orta) + #orta = "E" if orta=="W" else ("W" if orta=="E" else orta) + route_ports[i] = set_port_orientation(port_to_add, orta) + croute.add_ports(route_ports,prefix="con_") + return rename_ports_by_orientation(rename_ports_by_list(croute.flatten(), [("con_","con_")])) + +if __name__ == "__main__": + from glayout.pdk.util.standard_main import pdk + + routebetweentop = copy(get_primitive_rectangle(layer=pdk.get_glayer("met1"))).ref() + routebetweentop.movey(10) + routebetweenbottom = get_primitive_rectangle(layer=pdk.get_glayer("met1")) + mycomp = c_route(pdk,routebetweentop.ports["e3"],routebetweenbottom.ports["e3"]) + mycomp.unlock() + mycomp.add(routebetweentop) + mycomp << routebetweenbottom + mycomp.flatten().show() diff --git a/openfasoc/generators/gdsfactory-gen/glayout/routing/straight_route.py b/openfasoc/generators/gdsfactory-gen/glayout/routing/straight_route.py new file mode 100644 index 000000000..fb861a27a --- /dev/null +++ b/openfasoc/generators/gdsfactory-gen/glayout/routing/straight_route.py @@ -0,0 +1,128 @@ +if __name__ == "__main__": + import sys + sys.path.append("../../") +from gdsfactory.cell import cell +from gdsfactory.component import Component +from gdsfactory.port import Port +from glayout.pdk.mappedpdk import MappedPDK +from typing import Optional +from glayout.via_gen import via_stack, via_array +from gdsfactory.components.rectangle import rectangle +from glayout.pdk.util.comp_utils import evaluate_bbox, align_comp_to_port +from glayout.pdk.util.port_utils import assert_port_manhattan, set_port_orientation, add_ports_perimeter +from gdstk import rectangle as primitive_rectangle + + +@cell +def straight_route( + pdk: MappedPDK, + edge1: Port, + edge2: Port, + glayer1: Optional[str] = None, + width: Optional[float] = None, + glayer2: Optional[str] = None, + via1_alignment: Optional[tuple[str, str]] = None, + via1_alignment_layer: Optional[str] = None, + via2_alignment: Optional[tuple[str, str]] = None, + via2_alignment_layer: Optional[str] = None, + fullbottom: Optional[bool] = False +) -> Component: + """extends a route from edge1 until perpindicular with edge2, then places a via + This depends on the orientation of edge1 and edge2 + if edge1 has the same orientation as edge2, the generator will rotate edge2 180 degrees + Will not modify edge1 or edge2 + + DOES NOT REQUIRE: + edge2 is directly inline with edge1 + + example: + edge2 + + edge1-------- + + args: + pdk to use + edge1, edge2 Ports + glayer1 = defaults to edge1.layer, layer of the route. + ****If not edge1.layer, a via will be placed + glayer2 = defaults to edge2.layer, end layer of the via + width = defaults to edge1.width + via1_alignment = alignment of the via on edge1 + via2_alignment = alignment of the via on edge2 + ****defaults to an orientation that is aligned to the orientation of the port. + + Ports: + route_...all edges of the rectangle path + """ + #TODO: error checking + width = width if width else edge1.width + glayer1 = glayer1 if glayer1 else pdk.layer_to_glayer(edge1.layer) + front_via = None + if glayer1 != pdk.layer_to_glayer(edge1.layer): + front_via = via_stack(pdk,glayer1,pdk.layer_to_glayer(edge1.layer),fullbottom=fullbottom) + glayer2 = glayer2 if glayer2 else pdk.layer_to_glayer(edge2.layer) + assert_port_manhattan([edge1,edge2]) + if edge1.orientation == edge2.orientation: + edge2 = set_port_orientation(edge2,edge2.orientation,flip180=True) + pdk.activate() + # find extension length and direction + edge1_is_EW = bool(round(edge1.orientation + 90) % 180) + if edge1_is_EW: + startx = edge1.center[0] + endx = edge2.center[0] + extension = endx-startx + viaport_name = "route_E" if extension > 0 else "route_W" + alignment = ("r","c") if extension > 0 else ("l","c") + size = (abs(extension),width) + else: + starty = edge1.center[1] + endy = edge2.center[1] + extension = endy-starty + viaport_name = "route_N" if extension > 0 else "route_S" + alignment = ("c","t") if extension > 0 else ("c","b") + size = (width,abs(extension)) + # create route and via + route = Component() + route.add_polygon(primitive_rectangle((0,0),size,*pdk.get_glayer(glayer1))) + add_ports_perimeter(route,layer=pdk.get_glayer(glayer1),prefix="route_") + out_via = via_stack(pdk,glayer1,glayer2,fullbottom=fullbottom) if glayer1 != glayer2 else None + # place route and via + straightroute = Component() + for i, edge in enumerate([edge1,edge2]): + temp = via1_alignment if i == 0 else via2_alignment + if temp is None: + if round(edge.orientation) == 0:# facing east + temp = ("l", "c") + elif round(edge.orientation) == 180:# facing west + temp = ("r", "c") + elif round(edge.orientation) == 270:# facing south + temp = ("c", "t") + elif round(edge.orientation) == 90:#facing north + temp = ("c", "b") + else: + raise ValueError("port must be vertical or horizontal") + via1_alignment = temp if i == 0 else via1_alignment + via2_alignment = temp if i == 1 else via2_alignment + route_ref = align_comp_to_port(route,edge1,alignment=alignment) + straightroute.add_ports(route_ref.get_ports_list()) + straightroute.add(route_ref) + if out_via is not None: + alignlayer2 = pdk.get_glayer(glayer1) if via2_alignment_layer is None else pdk.get_glayer(via2_alignment_layer) + straightroute.add(align_comp_to_port(out_via,route_ref.ports[viaport_name],layer=alignlayer2,alignment=via2_alignment)) + if front_via is not None: + alignlayer1 = pdk.get_glayer(glayer1) if via1_alignment_layer is None else pdk.get_glayer(via1_alignment_layer) + straightroute.add(align_comp_to_port(front_via,edge1,layer=alignlayer1,alignment=via1_alignment)) + return straightroute.flatten() + + +if __name__ == "__main__": + from glayout.pdk.util.standard_main import pdk + + routebetweentop = rectangle(layer=pdk.get_glayer("met3"),size=(1,1)).ref() + routebetweentop.movex(20) + routebetweenbottom = rectangle(layer=pdk.get_glayer("met1"), size=(1, 1)) + mycomp = straight_route(pdk,routebetweentop.ports["e1"],routebetweenbottom.ports["e3"]) + mycomp.unlock() + mycomp.add(routebetweentop) + mycomp << routebetweenbottom + mycomp.show() diff --git a/openfasoc/generators/gdsfactory-gen/glayout/via_gen.py b/openfasoc/generators/gdsfactory-gen/glayout/via_gen.py new file mode 100644 index 000000000..ce4feca49 --- /dev/null +++ b/openfasoc/generators/gdsfactory-gen/glayout/via_gen.py @@ -0,0 +1,303 @@ +from gdsfactory.cell import cell +from gdsfactory.component import Component +from gdsfactory.components.rectangle import rectangle +from pydantic import validate_arguments +from glayout.pdk.mappedpdk import MappedPDK +from math import floor +from typing import Optional, Union +from glayout.pdk.util.comp_utils import evaluate_bbox, prec_array, to_float, move, prec_ref_center, to_decimal +from glayout.pdk.util.port_utils import rename_ports_by_orientation, print_ports +from glayout.pdk.util.snap_to_grid import component_snap_to_grid +from decimal import Decimal +from typing import Literal + + +@validate_arguments +def __error_check_order_layers( + pdk: MappedPDK, glayer1: str, glayer2: str +) -> tuple[tuple[int, int], tuple[str, str]]: + """correctly order layers (level1 should be lower than level2)""" + pdk.activate() + # check that the generic layers specfied can be routed between + if not all([pdk.is_routable_glayer(met) for met in [glayer1, glayer2]]): + raise ValueError("via_stack: specify between two routable layers") + level1 = int(glayer1[-1]) if "met" in glayer1 else 0 + level2 = int(glayer2[-1]) if "met" in glayer2 else 0 + if level1 > level2: + level1, level2 = level2, level1 + glayer1, glayer2 = glayer2, glayer1 + # check that all layers needed between glayer1-glayer2 are present + required_glayers = [glayer2] + for level in range(level1,level2): + via_name = "mcon" if level==0 else "via"+str(level) + layer_name = glayer1 if level==0 else "met"+str(level) + required_glayers += [via_name,layer_name] + pdk.has_required_glayers(required_glayers) + return ((level1,level2),(glayer1,glayer2)) + + +@validate_arguments +def __get_layer_dim(pdk: MappedPDK, glayer: str, mode: Literal["both","above","below"]="both") -> float: + """Returns the required dimension of a routable layer in a via stack + glayer is the routable glayer + mode is one of [both,below,above] + This specfies the vias to consider. (layer dims may be made smaller if its possible to ignore top/bottom vias) + ****enclosure rules of the via above and below are considered by default, via1<->met2<->via2 + ****using below specfier only considers the enclosure rules for the via below, via1<->met2 + ****using above specfier only considers the enclosure rules for the via above, met2<->via2 + ****specfying both or below for active/poly layer is valid, function knows to ignore below + """ + # error checking + if not pdk.is_routable_glayer(glayer): + raise ValueError("__get_layer_dim: glayer must be a routable layer") + # split into above rules and below rules + consider_above = (mode=="both" or mode=="above") + consider_below = (mode=="both" or mode=="below") + is_lvl0 = any([hint in glayer for hint in ["poly","active"]]) + layer_dim=0 + if consider_below and not is_lvl0: + via_below = "mcon" if glayer=="met1" else "via"+str(int(glayer[-1])-1) + layer_dim = pdk.get_grule(via_below)["width"] + 2*pdk.get_grule(via_below,glayer)["min_enclosure"] + if consider_above: + via_above = "mcon" if is_lvl0 else "via"+str(glayer[-1]) + layer_dim = max(layer_dim, pdk.get_grule(via_above)["width"] + 2*pdk.get_grule(via_above,glayer)["min_enclosure"]) + layer_dim = max(layer_dim, pdk.get_grule(glayer)["min_width"]) + return layer_dim + + +@validate_arguments +def __get_viastack_minseperation(pdk: MappedPDK, viastack: Component, ordered_layer_info) -> tuple[float,float]: + """internal use: return absolute via separation and top_enclosure (top via to top met enclosure)""" + get_sep = lambda _pdk, rule, _lay_, comp : (rule+2*comp.extract(layers=[_pdk.get_glayer(_lay_)]).xmax) + level1, level2 = ordered_layer_info[0] + glayer1, glayer2 = ordered_layer_info[1] + mcon_rule = pdk.get_grule("mcon")["min_separation"] + via_spacing = [] if level1 else [get_sep(pdk,mcon_rule,"mcon",viastack)] + level1_met = level1 if level1 else level1 + 1 + top_enclosure = 0 + for level in range(level1_met, level2): + met_glayer = "met" + str(level) + via_glayer = "via" + str(level) + mrule = pdk.get_grule(met_glayer)["min_separation"] + vrule = pdk.get_grule(via_glayer)["min_separation"] + via_spacing.append(get_sep(pdk, mrule,met_glayer,viastack)) + via_spacing.append(get_sep(pdk, vrule,via_glayer,viastack)) + if level == (level2-1): + top_enclosure = pdk.get_grule(glayer2,via_glayer)["min_enclosure"] + via_spacing = pdk.snap_to_2xgrid(max(via_spacing),return_type="float") + top_enclosure = pdk.snap_to_2xgrid(top_enclosure,return_type="float") + return pdk.snap_to_2xgrid([via_spacing, 2*top_enclosure], return_type="float") + + +@cell +def via_stack( + pdk: MappedPDK, + glayer1: str, + glayer2: str, + centered: bool = True, + fullbottom: bool = False, + fulltop: bool = False, + assume_bottom_via: bool = False, + same_layer_behavior: Literal["lay_nothing","min_square"] = "lay_nothing" +) -> Component: + """produces a single via stack between two layers that are routable (metal, poly, or active) + The via_stack produced is always a square (hieght=width) + + args: + pdk: MappedPDK is the pdk to use + glayer1: str is the glayer to start on + glayer2: str is the glayer to end on + ****NOTE it does not matter what order you pass layers + fullbottom: if True will lay the bottom layer all over the area of the viastack else makes minimum legal size (ignores min area) + assume_bottom_via: legalize viastack assuming the via underneath bottom met is present, e.g. if bottom met is met3, assume via2 is present + fulltop: if True will lay the top layer all over the area of the viastack else makes minimum legal size (ignores min area) + ****NOTE: generator can figure out which layer is top and which is bottom (i.e. met5 is higher than met1) + same_layer_behavior: sometimes (especially when used in other generators) it is unknown what two layers are specfied + this option provides the generator with guidance on how to handle a case where same layer is given + by default, (lay_nothing option) nothing is laid and an empty component is returned + if min_square is specfied, a square of min_width * min_width is laid + + ports, some ports are not layed when it does not make sense (e.g. empty component): + top_met_...all edges + bottom_via_...all edges + bottom_met_...all edges + bottom_layer_...all edges (may be different than bottom met if on diff/poly) + """ + ordered_layer_info = __error_check_order_layers(pdk, glayer1, glayer2) + level1, level2 = ordered_layer_info[0] + glayer1, glayer2 = ordered_layer_info[1] + viastack = Component() + # if same level return component with min_width rectangle on that layer + if level1 == level2: + if same_layer_behavior=="lay_nothing": + return viastack + min_square = viastack << rectangle(size=2*[pdk.get_grule(glayer1)["min_width"]],layer=pdk.get_glayer(glayer1), centered=centered) + # update ports + if level1==0:# both poly or active + viastack.add_ports(min_square.get_ports_list(),prefix="bottom_layer_") + else:# both mets + viastack.add_ports(min_square.get_ports_list(),prefix="top_met_") + viastack.add_ports(min_square.get_ports_list(),prefix="bottom_met_") + else: + ports_to_add = dict() + for level in range(level1,level2+1): + via_name = "mcon" if level==0 else "via"+str(level) + layer_name = glayer1 if level==0 else "met"+str(level) + # get layer sizing + mode = "below" if level==level2 else ("above" if level==level1 else "both") + mode = "both" if assume_bottom_via and level==level1 else mode + layer_dim = __get_layer_dim(pdk, layer_name, mode=mode) + # place met/via, do not place via if on top layer + if level != level2: + via_dim = pdk.get_grule(via_name)["width"] + via_ref = viastack << rectangle(size=[via_dim,via_dim],layer=pdk.get_glayer(via_name), centered=True) + lay_ref = viastack << rectangle(size=[layer_dim,layer_dim],layer=pdk.get_glayer(layer_name), centered=True) + # update ports + if layer_name == glayer1: + ports_to_add["bottom_layer_"] = lay_ref.get_ports_list() + ports_to_add["bottom_via_"] = via_ref.get_ports_list() + if (level1==0 and level==1) or (level1>0 and layer_name==glayer1): + ports_to_add["bottom_met_"] = lay_ref.get_ports_list() + if layer_name == glayer2: + ports_to_add["top_met_"] = lay_ref.get_ports_list() + # implement fulltop and fullbottom options. update ports_to_add accordingly + if fullbottom: + bot_ref = viastack << rectangle(size=evaluate_bbox(viastack),layer=pdk.get_glayer(glayer1), centered=True) + if level1!=0: + ports_to_add["bottom_met_"] = bot_ref.get_ports_list() + ports_to_add["bottom_layer_"] = bot_ref.get_ports_list() + if fulltop: + ports_to_add["top_met_"] = (viastack << rectangle(size=evaluate_bbox(viastack),layer=pdk.get_glayer(glayer2), centered=True)).get_ports_list() + # add all ports in ports_to_add + for prefix, ports_list in ports_to_add.items(): + viastack.add_ports(ports_list,prefix=prefix) + # move SW corner to 0,0 if centered=False + if not centered: + viastack = move(viastack,(viastack.xmax,viastack.ymax)) + return rename_ports_by_orientation(viastack.flatten()) + + +@cell +def via_array( + pdk: MappedPDK, + glayer1: str, + glayer2: str, + size: Optional[tuple[Optional[float],Optional[float]]] = None, + minus1: bool = False, + num_vias: Optional[tuple[Optional[int],Optional[int]]] = None, + lay_bottom: bool = True, + fullbottom: bool = False, + no_exception: bool = False, +) -> Component: + """Fill a region with vias. Will automatically decide num rows and columns + args: + pdk: MappedPDK is the pdk to use + glayer1: str is the glayer to start on + glayer2: str is the glayer to end on + lay_bottom: bool if true will lay bottom layer (by default only lays top layer) + ****NOTE it does not matter what order you pass layers + ****NOTE will lay bottom only over the minimum area required to make legal + size: tuple is the (width, hieght) of the area to enclose + ****NOTE: the size will be the dimensions of the top metal + minus1: if true removes 1 via from rows/cols num vias + ****use if you want extra space at the edges of the array, does not apply to num_vias + num_vias: number of rows/cols in the via array. Overrides size option + ****NOTE: you can specify size for one dim and num_vias for another by setting one element to None + ****NOTE: num_vias overides size option + fullbottom: True specifies that the bottom layer should extend over the entire via_array region + ****NOTE: fullbottom=True implies lay_bottom and overrides if False + no_exception: True specfies that the function should change size such that min size is met + + ports, some ports are not layed when it does not make sense (e.g. empty component): + top_met_...all edges + bottom_lay_...all edges (only if lay_bottom is specified) + array_...all ports associated with via array + """ + # setup + ordered_layer_info = __error_check_order_layers(pdk, glayer1, glayer2) + level1, level2 = ordered_layer_info[0] + glayer1, glayer2 = ordered_layer_info[1] + viaarray = Component() + # if same level return empty component + if level1 == level2: + return viaarray + # figure out min space between via stacks + viastack = via_stack(pdk, glayer1, glayer2) + viadim = evaluate_bbox(viastack)[0] + via_abs_spacing, top_enclosure = __get_viastack_minseperation(pdk, viastack, ordered_layer_info) + # error check size and determine num_vias, cnum_vias[0]=x, cnum_vias[1]=y + cnum_vias = 2*[None] + for i in range(2): + if (num_vias[i] if num_vias else False): + cnum_vias[i] = num_vias[i] + elif (size[i] if size else False): + dim = pdk.snap_to_2xgrid(size[i],return_type="float") + fltnum = floor((dim - top_enclosure) / (via_abs_spacing)) or 1 + fltnum = 1 if fltnum < 1 else fltnum + cnum_vias[i] = ((fltnum - 1) or 1) if minus1 else fltnum + if to_decimal(viadim) > to_decimal(dim) and not no_exception: + raise ValueError(f"via_array,size:dim#{i}={dim} < {viadim}") + else: + raise ValueError("give at least 1: num_vias or size for each dim") + # create array + viaarray_ref = prec_ref_center(prec_array(viastack, columns=cnum_vias[0], rows=cnum_vias[1], spacing=2*[via_abs_spacing],absolute_spacing=True)) + viaarray.add(viaarray_ref) + viaarray.add_ports(viaarray_ref.get_ports_list(),prefix="array_") + # find the what should be used as full dims + viadims = evaluate_bbox(viaarray) + if not size: + size = 2*[None] + size = [size[i] if size[i] else viadims[i] for i in range(2)] + size = [viadims[i] if viadims[i]>size[i] else size[i] for i in range(2)] + # place bottom layer and add bot_lay_ ports + if lay_bottom or fullbottom: + bdims = evaluate_bbox(viaarray.extract(layers=[pdk.get_glayer(glayer1)])) + bref = viaarray << rectangle(size=(size if fullbottom else bdims), layer=pdk.get_glayer(glayer1), centered=True) + viaarray.add_ports(bref.get_ports_list(), prefix="bottom_lay_") + else: + viaarray = viaarray.remove_layers(layers=[pdk.get_glayer(glayer1)]) + # place top met + tref = viaarray << rectangle(size=size, layer=pdk.get_glayer(glayer2), centered=True) + viaarray.add_ports(tref.get_ports_list(), prefix="top_met_") + return component_snap_to_grid(rename_ports_by_orientation(viaarray)) + + +if __name__ == "__main__": + from .pdk.util.standard_main import pdk, parser + from .pdk.util.custom_comp_utils import print_ports + from pathlib import Path + + # default behavoir is to run one design and exit + parser.add_argument("--all", "-a", action="store_true", help="runs all tests") + parser.add_argument("--viastack", "-s", action="store_true", help="runs all via_stack tests") + parser.add_argument("--viaarray", "-v", action="store_true", help="runs all via_array tests") + parser.add_argument("--write", "-w", help="writes all gds files to directory specfied") + parser.add_argument("--ports", action="store_true", help="print ports") + args = parser.parse_args() + # run comps + comps = list() + if args.viaarray or args.all: + layers = ["poly", "met1", "met2", "met3"] + for lay1 in layers: + for lay2 in layers: + comps.append(via_array(pdk, lay1, lay2, lay_bottom=True)) + elif args.viastack or args.all: + layers = ["poly", "met1", "met2", "met3"] + for lay1 in layers: + for lay2 in layers: + comps.append(via_stack(pdk, lay1, lay2,fullbottom=True,fulltop=True)) + else: + myarray = via_array(pdk, "poly", "met2",size=(5,4)) + # show and write (if write is specfied) + if args.write: + gds_write_path = Path(args.write) + if not gds_write_path.is_dir(): + raise ValueError("gds write must be a dir path") + for comp in comps: + comp.write_gds(comp.name+".gds") + for comp in comps: + comp.show() + # print_ports + if args.ports: + for comp in comps: + print_ports(myarray) diff --git a/openfasoc/generators/gdsfactory-gen/requirements.txt b/openfasoc/generators/gdsfactory-gen/requirements.txt new file mode 100644 index 000000000..da17ee38e --- /dev/null +++ b/openfasoc/generators/gdsfactory-gen/requirements.txt @@ -0,0 +1,7 @@ +gf180 +sky130 +scikit-learn +matplotlib +scipy +seaborn +prettyprinttree diff --git a/openfasoc/generators/gdsfactory-gen/tapeout_and_RL/README.md b/openfasoc/generators/gdsfactory-gen/tapeout_and_RL/README.md new file mode 100644 index 000000000..35cf4a510 --- /dev/null +++ b/openfasoc/generators/gdsfactory-gen/tapeout_and_RL/README.md @@ -0,0 +1,134 @@ +# sky130 NIST Tapeout Macros +This directory contains the `sky130_nist_tapeout.py` file which is a python program containing all functions and utils neccessary to produce the circuits, simulation info, and statistics used in the sky130 NIST tapeout. +`sky130_nist_tapeout.py` has a command line interface. use the `-h` option to see all args for this program. help output is replicated below. + +## NOTE +Before using `sky130_nist_tapeout.py` file you should set env variable `PDK_ROOT` to the root directory of the sky130 pdk on your system. You should also change the pdk path in `opamp_perf_eval.sp` and `extract.bash`. + +## general help +``` +usage: sky130_nist_tapeout.py [-h] + {extract_stats,get_training_data,gen_opamp,test,create_opamp_matrix} ... + +sky130 nist tapeout sample, RL generation, and statistics utility. + +options: + -h, --help show this help message and exit + +mode: + {extract_stats,get_training_data,gen_opamp,test,create_opamp_matrix} + extract_stats Run the extract_stats function. + get_training_data Run the get_training_data function. + gen_opamp Run the gen_opamp function. optional parameters for transistors are + width,length,fingers,mults + test Test mode + create_opamp_matrix + create a matrix of opamps +``` + +## extract_stats mode +``` +usage: sky130_nist_tapeout.py extract_stats [-h] [-p PARAMS] [-r RESULTS] + +options: + -h, --help show this help message and exit + -p PARAMS, --params PARAMS + File path for params (default: training_params.npy) + -r RESULTS, --results RESULTS + File path for results (default: training_results.npy) +``` + +## gen_opamp mode +``` +usage: sky130_nist_tapeout.py gen_opamp [-h] + [--diffpair_params DIFFPAIR_PARAMS DIFFPAIR_PARAMS DIFFPAIR_PARAMS] + [--diffpair_bias DIFFPAIR_BIAS DIFFPAIR_BIAS DIFFPAIR_BIAS] + [--half_common_source_params HALF_COMMON_SOURCE_PARAMS HALF_COMMON_SOURCE_PARAMS HALF_COMMON_SOURCE_PARAMS HALF_COMMON_SOURCE_PARAMS] + [--half_common_source_bias HALF_COMMON_SOURCE_BIAS HALF_COMMON_SOURCE_BIAS HALF_COMMON_SOURCE_BIAS HALF_COMMON_SOURCE_BIAS] + [--output_stage_params OUTPUT_STAGE_PARAMS OUTPUT_STAGE_PARAMS OUTPUT_STAGE_PARAMS] + [--output_stage_bias OUTPUT_STAGE_BIAS OUTPUT_STAGE_BIAS OUTPUT_STAGE_BIAS] + [--mim_cap_size MIM_CAP_SIZE MIM_CAP_SIZE] + [--mim_cap_rows MIM_CAP_ROWS] [--rmult RMULT] [--add_pads] + [--output_gds OUTPUT_GDS] + +options: + -h, --help show this help message and exit + --diffpair_params DIFFPAIR_PARAMS DIFFPAIR_PARAMS DIFFPAIR_PARAMS + diffpair_params (default: 6 1 4) + --diffpair_bias DIFFPAIR_BIAS DIFFPAIR_BIAS DIFFPAIR_BIAS + diffpair_bias (default: 6 2 4) + --half_common_source_params HALF_COMMON_SOURCE_PARAMS HALF_COMMON_SOURCE_PARAMS HALF_COMMON_SOURCE_PARAMS HALF_COMMON_SOURCE_PARAMS + half_common_source_params (default: 7 1 10 3) + --half_common_source_bias HALF_COMMON_SOURCE_BIAS HALF_COMMON_SOURCE_BIAS HALF_COMMON_SOURCE_BIAS HALF_COMMON_SOURCE_BIAS + half_common_source_bias (default: 6 2 8 3) + --output_stage_params OUTPUT_STAGE_PARAMS OUTPUT_STAGE_PARAMS OUTPUT_STAGE_PARAMS + pamp_hparams (default: 5 1 16) + --output_stage_bias OUTPUT_STAGE_BIAS OUTPUT_STAGE_BIAS OUTPUT_STAGE_BIAS + pamp_hparams (default: 6 2 4) + --mim_cap_size MIM_CAP_SIZE MIM_CAP_SIZE + mim_cap_size (default: 12 12) + --mim_cap_rows MIM_CAP_ROWS + mim_cap_rows (default: 3) + --rmult RMULT rmult (default: 2) + --add_pads add pads (gen_opamp mode only) + --output_gds OUTPUT_GDS + Filename for outputing opamp (gen_opamp mode only) +``` + +## get_training_data mode +``` +usage: sky130_nist_tapeout.py get_training_data [-h] [-t] [--temp TEMP] [--cload CLOAD] + [--noparasitics] [--nparray NPARRAY] [--saverawsims] + [--get_tset_len] [--output_second_stage] + +options: + -h, --help show this help message and exit + -t, --test-mode Set test_mode to True (default: False) + --temp TEMP Simulation temperature + --cload CLOAD run simulation with load capacitance units=pico Farads + --noparasitics specify that parasitics should be removed when simulating + --nparray NPARRAY overrides the test parameters and takes the ones you provide (file path to .npy file). + MUST HAVE LEN > 1 + --saverawsims specify that the raw simulation directories should be saved (default saved + under save_gds_by_index/...) + --get_tset_len print the length of the default parameter set and quit + --output_second_stage + measure relevant sim metrics at the output of the second stage rather than + output of third stage +``` + +## test mode +``` +usage: sky130_nist_tapeout.py test [-h] [--output_dir OUTPUT_DIR] [--temp TEMP] [--cload CLOAD] + [--noparasitics] [--output_second_stage] + +options: + -h, --help show this help message and exit + --output_dir OUTPUT_DIR + Directory for output GDS file + --temp TEMP Simulation temperature + --cload CLOAD run simulation with load capacitance units=pico Farads + --noparasitics specify that parasitics should be removed when simulating + --output_second_stage + measure relevant sim metrics at the output of the second stage rather than + output of third stage +``` + +## create_opamp_matrix mode +``` +usage: sky130_nist_tapeout.py create_opamp_matrix [-h] [-p PARAMS] [-r RESULTS] + [--indices INDICES [INDICES ...]] + [--output_dir OUTPUT_DIR] + +options: + -h, --help show this help message and exit + -p PARAMS, --params PARAMS + File path for params (default: params.npy) + -r RESULTS, --results RESULTS + Optional File path for results + --indices INDICES [INDICES ...] + list of int indices to pick from the opamp param.npy and add to the matrix + (default: the entire params list) + --output_dir OUTPUT_DIR + Directory for output files (default: ./opampmatrix) +``` \ No newline at end of file diff --git a/openfasoc/generators/gdsfactory-gen/tapeout_and_RL/extract.bash b/openfasoc/generators/gdsfactory-gen/tapeout_and_RL/extract.bash new file mode 100644 index 000000000..baa3fc8cc --- /dev/null +++ b/openfasoc/generators/gdsfactory-gen/tapeout_and_RL/extract.bash @@ -0,0 +1,50 @@ +#!/bin/bash + +# Actual +export PDK_ROOT=/usr/bin/miniconda3/share/pdk/ + +# args: +# first arg = gds file to read +# second arg = name of top cell in gds file to read + +# generate lvs netlist using magic +magic -rcfile ./sky130A/sky130A.magicrc -noconsole -dnull << EOF +gds read $1 +load $2 +flatten $2_flat +load $2_flat +gds write $2_flat.gds +EOF + +magic -rcfile ./sky130A/sky130A.magicrc -noconsole -dnull << EOF +gds read $2_flat.gds +load $2_flat +flatten $2 +load $2 +gds write $2.gds +EOF + + +magic -rcfile ./sky130A/sky130A.magicrc -noconsole -dnull << EOF +gds read $2.gds +load $2 +extract all +ext2spice lvs +ext2spice merge aggressive +ext2spice cthresh 0 +ext2spice rthresh 0 +ext2spice -o $2_pex.spice +exit +EOF + +magic -rcfile ./sky130A/sky130A.magicrc -noconsole -dnull << EOF +gds read $2.gds +load $2 +extract all +ext2spice merge aggressive +ext2spice -o $2_pex.spice +exit +EOF + +rm -f $2_flat.gds +rm -f $2.ext diff --git a/openfasoc/generators/gdsfactory-gen/tapeout_and_RL/opamp_perf_eval.sp b/openfasoc/generators/gdsfactory-gen/tapeout_and_RL/opamp_perf_eval.sp new file mode 100644 index 000000000..e665906de --- /dev/null +++ b/openfasoc/generators/gdsfactory-gen/tapeout_and_RL/opamp_perf_eval.sp @@ -0,0 +1,169 @@ +* opamp_perf_eval.sp +** OpenFASOC Team, Ryan Wans 2023 +.param mc_mm_switch=0 + +** IMPORTANT: Temperature setting is added automatically in the reading +** of this file on line 6 as 25. DO NOT OVERRIDE. +.temp {@@TEMP} + +.save all +** Define global parameters for altering +.param bdp = 5u +.param bcs = 5u +.param bo = 5u + +** Define netlist +Vsupply VDD GND 1.8 +Vindc net1 GND 0.9 +V2 vin net1 AC 0.5 +V3 vip net1 AC -0.5 +.save i(vindc) +.save i(vsupply) +.save i(v2) +.save i(v3) + +* bias currents +Ibiasdp VDD biasdpn {bdp} +Ibiascs VDD biascsn {bcs} +Ibiaso VDD biason {bo} + +** Import SKY130 libs (this should be replaced with a path relative to some env variable) +* the ones with double * will not be used. The one with only 1 * will be used + +** example not used +**@@stp .include /home/rw/work/open_pdks/sky130/sky130A/libs.ref/sky130_fd_sc_hvl/spice/sky130_fd_sc_hvl.spice + +** GCP machine +.lib /usr/bin/miniconda3/share/pdk/sky130A/libs.tech/ngspice/sky130.lib.spice tt +*@@stp .include /usr/bin/miniconda3/share/pdk/sky130A/libs.ref/sky130_fd_sc_hvl/spice/sky130_fd_sc_hvl.spice + + +** Import cryo libs (these are stored in the sky130A folder) +*@@cryo .include ./sky130A/cryo_models/nshort.spice +*@@cryo .include ./sky130A/cryo_models/nshortlvth.spice +*@@cryo .include ./sky130A/cryo_models/pmos.spice + +** Import opamp subcircuit +.include opamp_pex.spice +XDUT vo VDD vip vin biascsn biason biasdpn GND csoutputnetNC opamp +* parameter sweep +** Run initial analysis +.save all +.options savecurrents +.ac dec 100 1k 10G +.control +** Set initial values +set filetype = ascii +let maxFOM = -1 +let maxUGB = -1 +let maxBics = -1 +let maxBidp = -1 +let maxBio = -1 +let savedPhaseMargin = -1 +let savedDCGain = -1 +let savedthreedbBW = -1 + +* dp and cs bias log step +let linear_step_until = 60u +let linear_step_default = 7u +let bias_dp_Min = 10u +let bias_dp_Max = 400u +let bias_dp_logStep = 1.1 +let bias_cs_Min = 10u +let bias_cs_Max = 300u +let bias_cs_logStep = 1.1 + +* output bias linear step +let bias_o_Min = 93.5u +let bias_o_Max = 94u +let bias_o_Step = 2u + +let bias_dp = bias_dp_Min +let bias_cs = bias_cs_Min +let bias_o = bias_o_Min + +let absolute_counter = 0 + +** Sweep bias voltages +while bias_cs le bias_cs_Max + while bias_dp le bias_dp_Max + while bias_o le bias_o_Max + reset + alter ibiascs = $&bias_cs + alter ibiasdp = $&bias_dp + alter ibiaso = $&bias_o + echo "-- Run # $&absolute_counter -- " + echo "CS: $&bias_cs" + echo "Diff: $&bias_dp" + echo "Out: $&bias_o" + + run + ** Find unity-gain bw point + meas ac ugb_f when vdb(vo)=0 + ** Measure phase margin + let phase = (180/PI)*vp(vo) + meas ac pm find phase when vdb(vo)=0 + ** Measure DC(ish) gain + meas ac dcg find vdb(vo) at=1k + ** Measure 3db BW + let threedbabsgain = dcg - 3 + meas ac threedb when vdb(vo)=threedbabsgain FALL=1 + ** if FOM is better than previous max save results + let FOM = ugb_f / (bias_cs + bias_dp) + if ( FOM ge maxFOM ) + let maxFOM = FOM + let maxUGB = ugb_f + let maxBics = bias_cs + let maxBidp = bias_dp + let maxBio = bias_o + let savedPhaseMargin = pm % 360 + let savedDCGain = dcg + let savedthreedbBW = threedb + end + + let absolute_counter = absolute_counter + 1 + let bias_o = bias_o + bias_o_Step + end + ** Reset biasCurrent_o for next value of biasCurrent_dp + let bias_o = bias_o_Min + if ( linear_step_until ge bias_dp ) + let bias_dp = bias_dp + linear_step_default + else + let bias_dp = bias_dp * bias_dp_logStep + end + end + ** Reset biasCurrent_dp for next value of biasCurrent_cs + let bias_dp = bias_dp_Min + if ( linear_step_until ge bias_cs ) + let bias_cs = bias_cs + linear_step_default + else + let bias_cs = bias_cs * bias_cs_logStep + end +end +** Export global maxima +wrdata result_ac.txt maxUGB maxBidp maxBics maxBio savedPhaseMargin savedDCGain savedthreedbBW + +** Export power usage of correctly biased opamp +alterparam bcs = $&maxBics +alterparam bdp = $&maxBidp +alterparam bo = $&maxBio +reset + +op +let estimated_output_1to1_ref = @Ibiaso[current]*1.8 +let ptotal_exact = -i(vsupply)*1.8 +let estimated_two_stagepwr = ptotal_exact - estimated_output_1to1_ref +wrdata result_power.txt ptotal_exact estimated_two_stagepwr + +** Run noise analysis on opamp w/ best gain +reset +noise V(vo) v2 dec 100 1k 10G +setplot previous +let integ = integ(onoise_spectrum) +let totalNoise = sqrt(integ[length(integ)-1]) +wrdata result_noise.txt totalNoise + +.endc +.GLOBAL GND +.GLOBAL VDD +.end diff --git a/openfasoc/generators/gdsfactory-gen/tapeout_and_RL/pads/pad_60um_flat.gds b/openfasoc/generators/gdsfactory-gen/tapeout_and_RL/pads/pad_60um_flat.gds new file mode 100644 index 000000000..29ff959fb Binary files /dev/null and b/openfasoc/generators/gdsfactory-gen/tapeout_and_RL/pads/pad_60um_flat.gds differ diff --git a/openfasoc/generators/gdsfactory-gen/tapeout_and_RL/pads/sky130_mpw5_pad.gds b/openfasoc/generators/gdsfactory-gen/tapeout_and_RL/pads/sky130_mpw5_pad.gds new file mode 100644 index 000000000..d6cd9b9d2 Binary files /dev/null and b/openfasoc/generators/gdsfactory-gen/tapeout_and_RL/pads/sky130_mpw5_pad.gds differ diff --git a/openfasoc/generators/gdsfactory-gen/tapeout_and_RL/pads/sky130_nano_pad.gds b/openfasoc/generators/gdsfactory-gen/tapeout_and_RL/pads/sky130_nano_pad.gds new file mode 100644 index 000000000..3a84d9a23 Binary files /dev/null and b/openfasoc/generators/gdsfactory-gen/tapeout_and_RL/pads/sky130_nano_pad.gds differ diff --git a/openfasoc/generators/gdsfactory-gen/tapeout_and_RL/sky130A/cryo_models/nshort.spice b/openfasoc/generators/gdsfactory-gen/tapeout_and_RL/sky130A/cryo_models/nshort.spice new file mode 100644 index 000000000..c9febdad0 --- /dev/null +++ b/openfasoc/generators/gdsfactory-gen/tapeout_and_RL/sky130A/cryo_models/nshort.spice @@ -0,0 +1,420 @@ +* +* 4k spice models for n-channel thin oxide mosfets (std VTH). +* +* +* this scaled model was extracted by coolcad electroncis llc, +* akin akturk, akin.akturk@coolcadelectronics.com +* +* measurements used in model extraction correspond to the +* following W (um)/L (um) devices: +*'nshort; w=1.68; l=0.15; m=1'; +*'nshort; w=7.0; l=8.0; m=1'; +*'nshort; w=7.0; l=0.15; m=1'; +*'nshort; w=0.42; l=8.0; m=1'; +*'nshort; w=0.42; l=0.15; m=1'; +* +* to use the models, please set the circuit temperature +* to -269 in celcius. for standard spice, this can be done +* by adding the following line to netlist: .options temp=-269 +* +* +* +* +.MODEL nshort NMOS ++ LEVEL = 54 ++ VERSION = 4.6.5 ++ BINUNIT = 2 ++ PARAMCHK = 1 ++ MOBMOD = 1 ++ MTRLMOD = 0 ++ RDSMOD = 0 ++ IGCMOD = 0 ++ IGBMOD = 0 ++ CVCHARGEMOD = 0 ++ CAPMOD = 2 ++ RGATEMOD = 0 ++ RBODYMOD = 0 ++ TRNQSMOD = 0 ++ ACNQSMOD = 0 ++ FNOIMOD = 1 ++ TNOIMOD = 0 ++ DIOMOD = 1 ++ TEMPMOD = 0 ++ PERMOD = 1 ++ GEOMOD = 0 ++ WPEMOD = 0 ++ EPSROX = 3.9 ++ TOXE = 4.0840E-009 ++ EOT = 4.0840E-009 ++ TOXP = 4.0840E-009 ++ TOXM = 4.0840E-009 ++ DTOX = 0 ++ XJ = 1.5E-007 ++ NDEP = 1.7E+017 ++ NGATE = 1E+022 ++ NSD = 1E+020 ++ XT = 1.55E-007 ++ RSH = 0 ++ RSHG = 0.1 ++ VTH0 = 0.665 ++ WVTH0 = -0.02E-7 ++ LVTH0 = 0.22E-7 ++ PVTH0 = 0.08E-14 ++ VDDEOT = 1.5 ++ LEFFEOT = 1 ++ WEFFEOT = 10 ++ TEMPEOT = 300.1 ++ PHIN = 0 ++ EASUB = 4.05 ++ EPSRSUB = 11.7 ++ NI0SUB = 1.45E+010 ++ BG0SUB = 1.16 ++ TBGASUB = 0.000702 ++ TBGBSUB = 1108 ++ ADOS = 1 ++ BDOS = 1 ++ VFB = -1 ++ K1 = 0.4 ++ K2 = 0.01 ++ LK2 = 0.01E-6 ++ PK2 = 0.008E-13 ++ K3 = 15 ++ K3B = 0 ++ WK1 = -0.0225E-6 ++ LK1 = -0.045E-6 ++ PK1 = -0.5E-15 ++ W0 = 9.222E-007 ++ LPE0 = 1.899E-008 ++ LPEB = 6.702E-008 ++ VBM = -3 ++ DVT0 = 0.001 ++ DVT1 = 0.1135 ++ DVT2 = -2.864 ++ DVTP0 = 5.919E-009 ++ DVTP1 = 2.966 ++ DVT0W = -10.37 ++ DVT1W = 5.3E+006 ++ DVT2W = -0.032 ++ U0 = 0.25 ++ LU0 = -0.036E-6 ++ WU0 = -0.02E-6 ++ PU0 = 0.01E-12 ++ UA = -1.986E-009 ++ LUA = -5E-0117 ++ UB = 0.47E-017 ++ WUB = -2E-025 ++ LUB = -5E-025 ++ PUB = 12E-032 ++ UC = -0.07076 ++ UD = 3.228 ++ UCS = 1.67 ++ UP = 0.3928 ++ LP = 1.39E-005 ++ EU = 1.6 ++ VSAT = 1.8e+004 ++ WVSAT = -1.8E-3 ++ LVSAT = 20E-3 ++ PVSAT = 1.5e-9 ++ A0 = 2.2 ++ AGS = 1.4 ++ B0 = 0 ++ B1 = 0 ++ KETA = -0.02134 ++ A1 = 0 ++ A2 = 0.8779 ++ WINT = -3.6E-008 ++ LINT = -2.4E-008 ++ DWG = 6.974E-009 ++ LDWG = -5E-015 ++ DWB = 0 ++ VOFF = -0.1 ++ VOFFL = 15E-009 ++ MINV = -7 ++ LMINV = -15e-7 ++ NFACTOR = 2 ++ ETA0 = 2.686 ++ ETAB = -1.412 ++ DSUB = 0.6654 ++ CIT = 0 ++ CDSC = 4.441E-016 ++ CDSCB = -6.337E-006 ++ CDSCD = 0 ++ PCLM = 0.5 ++ LPCLM = 0.7E-6 ++ WPCLM = -0.1E-6 ++ PDIBLC1 = 0.001E-10 ++ PDIBLC2 = 1E-006 ++ PDIBLCB = 0 ++ DROUT = 0.56 ++ PSCBE1 = 1.5E+008 ++ PSCBE2 = 0.15E-006 ++ PVAG = 5 ++ DELTA = 0.01 ++ FPROUT = 0 ++ PDITS = 0.01 ++ PDITSL = 1.392E+006 ++ PDITSD = 1 ++ LAMBDA = 0 ++ VTL = 2E+005 ++ LC = 0 ++ XN = 4 ++ PHIG = 4.05 ++ EPSRGATE = 11.7 ++ RDSW = 0.0 ++ RDSWMIN = 5.0 ++ RDW = 100 ++ RDWMIN = 0 ++ RSW = 100 ++ RSWMIN = 0 ++ PRWG = 0.4 ++ PRWB = -0.1169 ++ WR = 8.882E-016 ++ ALPHA0 = 1E-005 ++ ALPHA1 = 0 ++ BETA0 = 15 ++ AGIDL = 1E-015 ++ BGIDL = 2.3E+009 ++ CGIDL = 0.5 ++ EGIDL = 0.8 ++ AGISL = 0 ++ BGISL = 2.3E+009 ++ CGISL = 0.5 ++ EGISL = 0.8 ++ AIGBACC = 0.43 ++ BIGBACC = 0.054 ++ CIGBACC = 0.075 ++ NIGBACC = 1 ++ AIGBINV = 0.35 ++ BIGBINV = 0.03 ++ CIGBINV = 0.006 ++ EIGBINV = 1.1 ++ NIGBINV = 3 ++ AIGC = 0.54 ++ BIGC = 0.054 ++ CIGC = 0.075 ++ AIGSD = 0.43 ++ BIGSD = 0.054 ++ CIGSD = 0.075 ++ DLCIG = 1.051E-008 ++ AIGS = 0.0136 ++ BIGS = 0.00171 ++ CIGS = 0.075 ++ AIGD = 0.0136 ++ BIGD = 0.00171 ++ CIGD = 0.075 ++ DLCIGD = 0 ++ NIGC = 1 ++ POXEDGE = 1 ++ PIGCD = 1 ++ NTOX = 1 ++ TOXREF = 4.0840E-009 ++ VFBSDOFF = 0 ++ XPART = 0 ++ CGSO = 3E-011 ++ CGDO = 3E-011 ++ CGBO = 0 ++ CGSL = 1.343E-010 ++ CGDL = 1.343E-010 ++ CKAPPAS = 0.6 ++ CKAPPAD = 0.6 ++ CF = 2.977E-010 ++ CLC = 1E-007 ++ CLE = 0.6 ++ DLC = 1.051E-008 ++ DWC = 0 ++ VFBCV = -1 ++ NOFF = 2 ++ VOFFCV = 0.051 ++ VOFFCVL = 0 ++ MINVCV = 0 ++ ACDE = 1 ++ MOIN = 15 ++ XRCRG1 = 12 ++ XRCRG2 = 1 ++ RBPB = 50 ++ RBPD = 50 ++ RBPS = 15 ++ RBDB = 50 ++ RBSB = 50 ++ GBMIN = 1E-012 ++ RBPS0 = 50 ++ RBPSL = 0 ++ RBPSW = 0 ++ RBPSNF = 0 ++ RBPD0 = 50 ++ RBPDL = 0 ++ RBPDW = 0 ++ RBPDNF = 0 ++ RBPBX0 = 100 ++ RBPBXL = 0 ++ RBPBXW = 0 ++ RBPBXNF = 0 ++ RBPBY0 = 100 ++ RBPBYL = 0 ++ RBPBYW = 0 ++ RBPBYNF = 0 ++ RBSBX0 = 100 ++ RBSBY0 = 100 ++ RBDBX0 = 100 ++ RBDBY0 = 100 ++ RBSDBXL = 0 ++ RBSDBXW = 0 ++ RBSDBXNF = 0 ++ RBSDBYL = 0 ++ RBSDBYW = 0 ++ RBSDBYNF = 0 ++ NOIA = 6.25E+041 ++ NOIB = 3.125E+026 ++ NOIC = 8.75 ++ EM = 4.1E+007 ++ AF = 1 ++ EF = 1 ++ KF = 0 ++ LINTNOI = 0 ++ NTNOI = 1 ++ TNOIA = 1.5 ++ TNOIB = 3.5 ++ RNOIA = 0.577 ++ RNOIB = 0.5164 ++ DMCG = 0 ++ DMCI = 0 ++ DMDG = 0 ++ DMCGT = 0 ++ DWJ = 0 ++ XGW = 0 ++ XGL = 0 ++ XL = 0 ++ XW = 5E-8 ++ NGCON = 1 ++ IJTHSREV = 0.0044 ++ IJTHSFWD = 0.0044 ++ XJBVS = 1 ++ BVS = 10 ++ JSS = 1.487E-8 ++ JSWS = 1E-18 ++ JSWGS = 0 ++ JTSS = 0 ++ JTSSWS = 0 ++ JTSSWGS = 0 ++ JTWEFF = 0 ++ NJS = 15 ++ NJTS = 20 ++ NJTSSW = 20 ++ NJTSSWG = 20 ++ XTSS = 0.02 ++ XTSSWS = 0.02 ++ XTSSWGS = 0.02 ++ VTSS = 10 ++ VTSSWS = 10 ++ VTSSWGS = 10 ++ TNJTS = 0 ++ TNJTSSW = 0 ++ TNJTSSWG = 0 ++ CJS = 0.001283 ++ MJS = 0.3296 ++ MJSWS = 0.33 ++ CJSWS = 3.5E-011 ++ CJSWGS = 3.5E-011 ++ MJSWGS = 0.33 ++ PBS = 0.9641 ++ PBSWS = 1 ++ PBSWGS = 1 ++ IJTHDREV = 0.0044 ++ IJTHDFWD = 0.0044 ++ XJBVD = 1 ++ BVD = 10 ++ JSD = 1.487E-8 ++ JSWD = 1E-18 ++ JSWGD = 0 ++ JTSD = 0 ++ JTSSWD = 0 ++ JTSSWGD = 0 ++ NJD = 15 ++ NJTSD = 20 ++ NJTSSWD = 20 ++ NJTSSWGD = 20 ++ XTSD = 0.02 ++ XTSSWD = 0.02 ++ XTSSWGD = 0.02 ++ VTSD = 10 ++ VTSSWD = 10 ++ VTSSWGD = 10 ++ TNJTSD = 0 ++ TNJTSSWD = 0 ++ TNJTSSWGD = 0 ++ CJD = 0.001283 ++ MJD = 0.3296 ++ MJSWD = 0.33 ++ CJSWD = 3.5E-011 ++ CJSWGD = 3.5E-011 ++ MJSWGD = 0.33 ++ PBD = 0.9641 ++ PBSWD = 1 ++ PBSWGD = 1 ++ TNOM = -253 ++ UTE = 0 ++ UCSTE = -0.004775 ++ KT1 = 0 ++ KT1L = 0 ++ KT2 = 0 ++ UA1 = 0 ++ UB1 = 0 ++ UC1 = 0 ++ UD1 = 0 ++ AT = 0 ++ PRT = 0 ++ XTIS = 3 ++ XTID = 3 ++ TPB = 0 ++ TPBSW = 0 ++ TPBSWG = 0 ++ TCJ = 0 ++ TCJSW = 0 ++ TCJSWG = 0 ++ TVOFF = 0 ++ TVFBSDOFF = 0 ++ SAREF = 0 ++ SBREF = 0 ++ WLOD = 2E-006 ++ KU0 = 4E-006 ++ KVSAT = 0.0 ++ TKU0 = 0 ++ LKU0 = 1E-006 ++ WKU0 = 1E-006 ++ PKU0 = 0 ++ LLODKU0 = 1.1 ++ WLODKU0 = 1.1 ++ KVTH0 = -2E-008 ++ LKVTH0 = 1.1E-006 ++ WKVTH0 = 1.1E-006 ++ PKVTH0 = 0 ++ LLODVTH = 1 ++ WLODVTH = 1 ++ STK2 = 0 ++ LODK2 = 1 ++ STETA0 = 0 ++ LODETA0 = 1 ++ WEB = 0 ++ WEC = 0 ++ KVTH0WE = 0 ++ K2WE = 0 ++ KU0WE = 0 ++ SCREF = 1E-006 ++ WL = 1E-014 ++ WLN = 1.056 ++ WW = 10.807E-015 ++ WWN = 1.03 ++ WWL = -1.419E-021 ++ LL = -1.609E-015 ++ LLN = 0.9 ++ LW = -7.92E-015 ++ LWN = 1.012 ++ LWL = 6.569E-021 ++ LLC = 0 ++ LWC = 0 ++ LWLC = 0 ++ WLC = 0 ++ WWC = 0 ++ WWLC = 0 +* +* diff --git a/openfasoc/generators/gdsfactory-gen/tapeout_and_RL/sky130A/cryo_models/nshortlvth.spice b/openfasoc/generators/gdsfactory-gen/tapeout_and_RL/sky130A/cryo_models/nshortlvth.spice new file mode 100644 index 000000000..d201c7890 --- /dev/null +++ b/openfasoc/generators/gdsfactory-gen/tapeout_and_RL/sky130A/cryo_models/nshortlvth.spice @@ -0,0 +1,420 @@ +* +* 4k spice models for n-channel thin oxide mosfets (low VTH). +* +* +* this scaled model was extracted by coolcad electroncis llc, +* akin akturk, akin.akturk@coolcadelectronics.com +* +* measurements used in model extraction correspond to the +* following W (um)/L (um) devices: +*'nlowvt; w=7.0; l=8.0; m=1'; +*'nlowvt; w=7.0; l=0.15; m=1'; +*'nlowvt; w=0.42; l=1.0; m=1'; +*'nlowvt; w=0.42; l=0.15; m=1'; +*'nlowvt; w=0.84; l=0.15; m=1';* +* to use the models, please set the circuit temperature +* to -269 in celcius. for standard spice, this can be done +* by adding the following line to netlist: .options temp=-269 +* +* +* +* +.MODEL nshortlvth NMOS ++ LEVEL = 54 ++ VERSION = 4.6.5 ++ BINUNIT = 2 ++ PARAMCHK = 1 ++ MOBMOD = 1 ++ MTRLMOD = 0 ++ RDSMOD = 0 ++ IGCMOD = 0 ++ IGBMOD = 0 ++ CVCHARGEMOD = 0 ++ CAPMOD = 2 ++ RGATEMOD = 0 ++ RBODYMOD = 0 ++ TRNQSMOD = 0 ++ ACNQSMOD = 0 ++ FNOIMOD = 1 ++ TNOIMOD = 0 ++ DIOMOD = 1 ++ TEMPMOD = 0 ++ PERMOD = 1 ++ GEOMOD = 0 ++ WPEMOD = 0 ++ EPSROX = 3.9 ++ TOXE = 4.0840E-009 ++ EOT = 4.0840E-009 ++ TOXP = 4.0840E-009 ++ TOXM = 4.0840E-009 ++ DTOX = 0 ++ XJ = 1.5E-007 ++ NDEP = 1.7E+017 ++ NGATE = 1E+022 ++ NSD = 1E+020 ++ XT = 1.55E-007 ++ RSH = 0 ++ RSHG = 0.1 ++ VTH0 = 0.585 ++ WVTH0 = -0.02E-7 ++ LVTH0 = 0.27E-7 ++ PVTH0 = 0.08E-14 ++ VDDEOT = 1.5 ++ LEFFEOT = 1 ++ WEFFEOT = 10 ++ TEMPEOT = 300.1 ++ PHIN = 0 ++ EASUB = 4.05 ++ EPSRSUB = 11.7 ++ NI0SUB = 1.45E+010 ++ BG0SUB = 1.16 ++ TBGASUB = 0.000702 ++ TBGBSUB = 1108 ++ ADOS = 1 ++ BDOS = 1 ++ VFB = -1 ++ K1 = 0.35 ++ K2 = 0.01 ++ LK2 = 0.01E-6 ++ K3 = 15 ++ K3B = 0 ++ WK1 = -0.0225E-6 ++ LK1 = -0.045E-6 ++ PK1 = -0.5E-15 ++ W0 = 9.222E-007 ++ LPE0 = 1.899E-008 ++ LPEB = 6.702E-008 ++ VBM = -3 ++ DVT0 = 0.001 ++ DVT1 = 0.1135 ++ DVT2 = -2.864 ++ DVTP0 = 5.919E-009 ++ DVTP1 = 2.966 ++ DVT0W = -10.37 ++ DVT1W = 5.3E+006 ++ DVT2W = -0.032 ++ U0 = 0.65 ++ LU0 = -0.05E-6 ++ WU0 = -0.02E-6 ++ PU0 = 0.01E-12 ++ UA = -3.5E-009 ++ LUA = -5E-0117 ++ UB = 1.4E-017 ++ WUB = -2E-025 ++ LUB = -5E-025 ++ PUB = 12E-032 ++ UC = -0.07076 ++ UD = 3.228 ++ UCS = 1.67 ++ UP = 0.3928 ++ LP = 2E-005 ++ EU = 1.6 ++ VSAT = 3.2e+004 ++ WVSAT = 8E-3 ++ LVSAT = 20E-3 ++ PVSAT = -1.5e-9 ++ A0 = 2.2 ++ AGS = 1.4 ++ B0 = 0 ++ B1 = 0 ++ KETA = -0.02134 ++ A1 = 0 ++ A2 = 0.8779 ++ WINT = -5E-008 ++ LINT = -2.0E-008 ++ DWG = 6.974E-009 ++ LDWG = -5E-015 ++ DWB = 0 ++ VOFF = -0.05 ++ VOFFL = -11E-009 ++ MINV = -8 ++ LMINV = -15e-7 ++ WMINV = -10e-7 ++ NFACTOR = 5 ++ ETA0 = 2.686 ++ ETAB = -1.412 ++ DSUB = 0.6654 ++ CIT = 0 ++ CDSC = 4.441E-016 ++ CDSCB = -6.337E-006 ++ CDSCD = 0 ++ PCLM = 0.5 ++ LPCLM = 0.3E-6 ++ WPCLM = -0.1E-6 ++ PDIBLC1 = 0.001E-10 ++ PDIBLC2 = 1E-006 ++ PDIBLCB = 0 ++ DROUT = 0.56 ++ PSCBE1 = 1.5E+008 ++ PSCBE2 = 0.05E-006 ++ PVAG = 5 ++ DELTA = 0.002 ++ WDELTA = 2E-8 ++ FPROUT = 0 ++ PDITS = 0.01 ++ PDITSL = 1.392E+006 ++ PDITSD = 1 ++ LAMBDA = 0 ++ VTL = 2E+005 ++ LC = 0 ++ XN = 4 ++ PHIG = 4.05 ++ EPSRGATE = 11.7 ++ RDSW = 0.0 ++ RDSWMIN = 11.0 ++ RDW = 100 ++ RDWMIN = 0 ++ RSW = 100 ++ RSWMIN = 0 ++ PRWG = 0.4 ++ PRWB = -0.1169 ++ WR = 8.882E-016 ++ ALPHA0 = 1E-005 ++ ALPHA1 = 0 ++ BETA0 = 15 ++ AGIDL = 1E-015 ++ BGIDL = 2.3E+009 ++ CGIDL = 0.5 ++ EGIDL = 0.8 ++ AGISL = 0 ++ BGISL = 2.3E+009 ++ CGISL = 0.5 ++ EGISL = 0.8 ++ AIGBACC = 0.43 ++ BIGBACC = 0.054 ++ CIGBACC = 0.075 ++ NIGBACC = 1 ++ AIGBINV = 0.35 ++ BIGBINV = 0.03 ++ CIGBINV = 0.006 ++ EIGBINV = 1.1 ++ NIGBINV = 3 ++ AIGC = 0.54 ++ BIGC = 0.054 ++ CIGC = 0.075 ++ AIGSD = 0.43 ++ BIGSD = 0.054 ++ CIGSD = 0.075 ++ DLCIG = 1.051E-008 ++ AIGS = 0.0136 ++ BIGS = 0.00171 ++ CIGS = 0.075 ++ AIGD = 0.0136 ++ BIGD = 0.00171 ++ CIGD = 0.075 ++ DLCIGD = 0 ++ NIGC = 1 ++ POXEDGE = 1 ++ PIGCD = 1 ++ NTOX = 1 ++ TOXREF = 4.0840E-009 ++ VFBSDOFF = 0 ++ XPART = 0 ++ CGSO = 3E-011 ++ CGDO = 3E-011 ++ CGBO = 0 ++ CGSL = 1.343E-010 ++ CGDL = 1.343E-010 ++ CKAPPAS = 0.6 ++ CKAPPAD = 0.6 ++ CF = 2.977E-010 ++ CLC = 1E-007 ++ CLE = 0.6 ++ DLC = 1.051E-008 ++ DWC = 0 ++ VFBCV = -1 ++ NOFF = 2 ++ VOFFCV = 0.051 ++ VOFFCVL = 0 ++ MINVCV = 0 ++ ACDE = 1 ++ MOIN = 15 ++ XRCRG1 = 12 ++ XRCRG2 = 1 ++ RBPB = 50 ++ RBPD = 50 ++ RBPS = 15 ++ RBDB = 50 ++ RBSB = 50 ++ GBMIN = 1E-012 ++ RBPS0 = 50 ++ RBPSL = 0 ++ RBPSW = 0 ++ RBPSNF = 0 ++ RBPD0 = 50 ++ RBPDL = 0 ++ RBPDW = 0 ++ RBPDNF = 0 ++ RBPBX0 = 100 ++ RBPBXL = 0 ++ RBPBXW = 0 ++ RBPBXNF = 0 ++ RBPBY0 = 100 ++ RBPBYL = 0 ++ RBPBYW = 0 ++ RBPBYNF = 0 ++ RBSBX0 = 100 ++ RBSBY0 = 100 ++ RBDBX0 = 100 ++ RBDBY0 = 100 ++ RBSDBXL = 0 ++ RBSDBXW = 0 ++ RBSDBXNF = 0 ++ RBSDBYL = 0 ++ RBSDBYW = 0 ++ RBSDBYNF = 0 ++ NOIA = 6.25E+041 ++ NOIB = 3.125E+026 ++ NOIC = 8.75 ++ EM = 4.1E+007 ++ AF = 1 ++ EF = 1 ++ KF = 0 ++ LINTNOI = 0 ++ NTNOI = 1 ++ TNOIA = 1.5 ++ TNOIB = 3.5 ++ RNOIA = 0.577 ++ RNOIB = 0.5164 ++ DMCG = 0 ++ DMCI = 0 ++ DMDG = 0 ++ DMCGT = 0 ++ DWJ = 0 ++ XGW = 0 ++ XGL = 0 ++ XL = 0 ++ XW = 5E-8 ++ NGCON = 1 ++ IJTHSREV = 0.0044 ++ IJTHSFWD = 0.0044 ++ XJBVS = 1 ++ BVS = 10 ++ JSS = 1.487E-8 ++ JSWS = 1E-18 ++ JSWGS = 0 ++ JTSS = 0 ++ JTSSWS = 0 ++ JTSSWGS = 0 ++ JTWEFF = 0 ++ NJS = 15 ++ NJTS = 20 ++ NJTSSW = 20 ++ NJTSSWG = 20 ++ XTSS = 0.02 ++ XTSSWS = 0.02 ++ XTSSWGS = 0.02 ++ VTSS = 10 ++ VTSSWS = 10 ++ VTSSWGS = 10 ++ TNJTS = 0 ++ TNJTSSW = 0 ++ TNJTSSWG = 0 ++ CJS = 0.001283 ++ MJS = 0.3296 ++ MJSWS = 0.33 ++ CJSWS = 3.5E-011 ++ CJSWGS = 3.5E-011 ++ MJSWGS = 0.33 ++ PBS = 0.9641 ++ PBSWS = 1 ++ PBSWGS = 1 ++ IJTHDREV = 0.0044 ++ IJTHDFWD = 0.0044 ++ XJBVD = 1 ++ BVD = 10 ++ JSD = 1.487E-8 ++ JSWD = 1E-18 ++ JSWGD = 0 ++ JTSD = 0 ++ JTSSWD = 0 ++ JTSSWGD = 0 ++ NJD = 15 ++ NJTSD = 20 ++ NJTSSWD = 20 ++ NJTSSWGD = 20 ++ XTSD = 0.02 ++ XTSSWD = 0.02 ++ XTSSWGD = 0.02 ++ VTSD = 10 ++ VTSSWD = 10 ++ VTSSWGD = 10 ++ TNJTSD = 0 ++ TNJTSSWD = 0 ++ TNJTSSWGD = 0 ++ CJD = 0.001283 ++ MJD = 0.3296 ++ MJSWD = 0.33 ++ CJSWD = 3.5E-011 ++ CJSWGD = 3.5E-011 ++ MJSWGD = 0.33 ++ PBD = 0.9641 ++ PBSWD = 1 ++ PBSWGD = 1 ++ TNOM = -253 ++ UTE = 0 ++ UCSTE = -0.004775 ++ KT1 = 0 ++ KT1L = 0 ++ KT2 = 0 ++ UA1 = 0 ++ UB1 = 0 ++ UC1 = 0 ++ UD1 = 0 ++ AT = 0 ++ PRT = 0 ++ XTIS = 3 ++ XTID = 3 ++ TPB = 0 ++ TPBSW = 0 ++ TPBSWG = 0 ++ TCJ = 0 ++ TCJSW = 0 ++ TCJSWG = 0 ++ TVOFF = 0 ++ TVFBSDOFF = 0 ++ SAREF = 0 ++ SBREF = 0 ++ WLOD = 2E-006 ++ KU0 = 4E-006 ++ KVSAT = 0.0 ++ TKU0 = 0 ++ LKU0 = 1E-006 ++ WKU0 = 1E-006 ++ PKU0 = 0 ++ LLODKU0 = 1.1 ++ WLODKU0 = 1.1 ++ KVTH0 = -2E-008 ++ LKVTH0 = 1.1E-006 ++ WKVTH0 = 1.1E-006 ++ PKVTH0 = 0 ++ LLODVTH = 1 ++ WLODVTH = 1 ++ STK2 = 0 ++ LODK2 = 1 ++ STETA0 = 0 ++ LODETA0 = 1 ++ WEB = 0 ++ WEC = 0 ++ KVTH0WE = 0 ++ K2WE = 0 ++ KU0WE = 0 ++ SCREF = 1E-006 ++ WL = 1E-014 ++ WLN = 1.056 ++ WW = 10.807E-015 ++ WWN = 1.03 ++ WWL = -1.419E-021 ++ LL = -1.609E-015 ++ LLN = 0.9 ++ LW = -7.92E-015 ++ LWN = 1.012 ++ LWL = 6.569E-021 ++ LLC = 0 ++ LWC = 0 ++ LWLC = 0 ++ WLC = 0 ++ WWC = 0 ++ WWLC = 0 +* +* diff --git a/openfasoc/generators/gdsfactory-gen/tapeout_and_RL/sky130A/cryo_models/pmos.spice b/openfasoc/generators/gdsfactory-gen/tapeout_and_RL/sky130A/cryo_models/pmos.spice new file mode 100644 index 000000000..fe1cb7786 --- /dev/null +++ b/openfasoc/generators/gdsfactory-gen/tapeout_and_RL/sky130A/cryo_models/pmos.spice @@ -0,0 +1,419 @@ +* +* 4k spice models for p-channel thin oxide mosfets (low VTH). +* +* +* this scaled model was extracted by coolcad electroncis llc, +* akin akturk, akin.akturk@coolcadelectronics.com +* +* measurements used in model extraction correspond to the +* following W (um)/L (um) devices: +*'plowvt; w=3.0; l=1.0; m=1'; +*'plowvt; w=7.0; l=8.0; m=1'; +*'plowvt; w=7.0; l=0.35; m=1'; +*'plowvt; w=0.42; l=8.0; m=1'; +*'plowvt; w=0.42; l=0.35; m=1'; +* +* to use the models, please set the circuit temperature +* to -269 in celcius. for standard spice, this can be done +* by adding the following line to netlist: .options temp=-269 +* +* +* +* +.MODEL pshort PMOS ++ LEVEL = 54 ++ VERSION = 4.6.5 ++ BINUNIT = 2 ++ PARAMCHK = 1 ++ MOBMOD = 1 ++ MTRLMOD = 0 ++ RDSMOD = 0 ++ IGCMOD = 0 ++ IGBMOD = 0 ++ CVCHARGEMOD = 0 ++ CAPMOD = 2 ++ RGATEMOD = 0 ++ RBODYMOD = 0 ++ TRNQSMOD = 0 ++ ACNQSMOD = 0 ++ FNOIMOD = 1 ++ TNOIMOD = 0 ++ DIOMOD = 1 ++ TEMPMOD = 0 ++ PERMOD = 1 ++ GEOMOD = 0 ++ WPEMOD = 0 ++ EPSROX = 3.9 ++ TOXE = 4.0349E-009 ++ EOT = 4.0349E-009 ++ TOXP = 4.0349E-009 ++ TOXM = 4.0349E-009 ++ DTOX = 0 ++ XJ = 1.5E-007 ++ NDEP = 4.0E+017 ++ NGATE = 1E+022 ++ NSD = 1E+020 ++ XT = 1.55E-007 ++ RSH = 0 ++ RSHG = 0.1 ++ VTH0 = -1.17 ++ LVTH0 = 0.13e-7 ++ WVTH0 = -0.23e-7 ++ PVTH0 = 20e-16 ++ VDDEOT = -1.5 ++ LEFFEOT = 1 ++ WEFFEOT = 10 ++ TEMPEOT = 300.1 ++ PHIN = 0 ++ EASUB = 4.05 ++ EPSRSUB = 11.7 ++ NI0SUB = 1.45E+010 ++ BG0SUB = 1.16 ++ TBGASUB = 0.000702 ++ TBGBSUB = 1108 ++ ADOS = 1 ++ BDOS = 1 ++ VFB = -1 ++ K1 = 0.618 ++ K2 = 0.039145 ++ K3 = -14.37 ++ K3B = -4.92 ++ WK1 = -4E-8 ++ LK1 = -4E-8 ++ PK1 = 8E-15 ++ W0 = 4.104E-007 ++ LPE0 = 7.535E-016 ++ LPEB = 2.387E-011 ++ VBM = -0.5 ++ DVT0 = 0.007 ++ DVT1 = 0.01 ++ DVT2 = -0.05872 ++ DVTP0 = 0 ++ DVTP1 = 0.001 ++ DVT0W = -100 ++ DVT1W = 5.3E+006 ++ DVT2W = -0.032 ++ U0 = 0.17 ++ LU0 = -6E-9 ++ PU0 = 0E-17 ++ WU0 = 0E-9 ++ UA = -3.272E-010 ++ UB = 2.2E-018 ++ WUB = -3E-026 ++ UC = 0.1 ++ UD = 1.762E+017 ++ UCS = 1 ++ UP = -0.004472 ++ LP = 1E-008 ++ EU = 1.67 ++ VSAT = 25E+004 ++ WVSAT = 3.0 ++ LVSAT = -10E-12 ++ PVSAT = 1E-2 ++ A0 = 0.5 ++ AGS = 0.5529 ++ B0 = 0 ++ B1 = 0 ++ KETA = -0.06744 ++ A1 = 0.07557 ++ A2 = 0.376 ++ WINT = 7E-008 ++ LINT = 1.5E-008 ++ DWG = -1E-008 ++ DWB = -1E-008 ++ VOFF = -0.09 ++ VOFFL = -0.4E-7 ++ MINV = -9 ++ WMINV = 0.39e-6 ++ LMINV = 0.0e-6 ++ PMINV = -0.7e-13 ++ NFACTOR = 4 ++ ETA0 = 12E-005 ++ ETAB = 0.687 ++ DSUB = 0.9 ++ CIT = 0 ++ CDSC = 4.441E-016 ++ CDSCB = -0.001393 ++ CDSCD = 0 ++ PCLM = 0.839 ++ LPCLM = 6E-7 ++ PDIBLC1 = 2 ++ PDIBLC2 = 0.001192 ++ PDIBLCB = 0 ++ DROUT = 0.4 ++ PSCBE1 = 9.24E+008 ++ PSCBE2 = 0.001 ++ PVAG = 1 ++ DELTA = 0.07 ++ PDELTA = -5E-15 ++ FPROUT = 0 ++ PDITS = 0.1 ++ PDITSL = 6.754E+006 ++ PDITSD = 1 ++ LAMBDA = 0 ++ VTL = 3.7E+004 ++ WVTL = 1E-2 ++ LC = 0.2 ++ XN = 10 ++ PHIG = 4.05 ++ EPSRGATE = 11.7 ++ RDSW = 3000 ++ LRDSW = 4e-5 ++ WRDSW = 0e-5 ++ RDSWMIN = 00 ++ RDW = 100 ++ RDWMIN = 0 ++ RSW = 100 ++ RSWMIN = 0 ++ PRWG = 8.421E-005 ++ PRWB = -0.814 ++ WR = 1.21 ++ ALPHA0 = 1E-005 ++ ALPHA1 = 0 ++ BETA0 = 15 ++ AGIDL = 1E-015 ++ BGIDL = 2.3E+009 ++ CGIDL = 0.5 ++ EGIDL = 0.8 ++ AGISL = 0 ++ BGISL = 2.3E+009 ++ CGISL = 0.5 ++ EGISL = 0.8 ++ AIGBACC = 0.43 ++ BIGBACC = 0.054 ++ CIGBACC = 0.075 ++ NIGBACC = 1 ++ AIGBINV = 0.35 ++ BIGBINV = 0.03 ++ CIGBINV = 0.006 ++ EIGBINV = 1.1 ++ NIGBINV = 3 ++ AIGC = 0.54 ++ BIGC = 0.054 ++ CIGC = 0.075 ++ AIGSD = 0.43 ++ BIGSD = 0.054 ++ CIGSD = 0.075 ++ DLCIG = 1.124E-008 ++ AIGS = 0.0136 ++ BIGS = 0.00171 ++ CIGS = 0.075 ++ AIGD = 0.0136 ++ BIGD = 0.00171 ++ CIGD = 0.075 ++ DLCIGD = 0 ++ NIGC = 1 ++ POXEDGE = 1 ++ PIGCD = 1 ++ NTOX = 1 ++ TOXREF = 4.0349E-009 ++ VFBSDOFF = 0 ++ XPART = 0 ++ CGSO = 3.161E-011 ++ CGDO = 3.161E-011 ++ CGBO = 0 ++ CGSL = 1.121E-010 ++ CGDL = 1.121E-010 ++ CKAPPAS = 0.6 ++ CKAPPAD = 0.6 ++ CF = 2.854E-010 ++ CLC = 1E-007 ++ CLE = 0.6 ++ DLC = 1.124E-008 ++ DWC = 0 ++ VFBCV = -1 ++ NOFF = 2 ++ VOFFCV = -0.2538 ++ VOFFCVL = 0 ++ MINVCV = 0 ++ ACDE = 1 ++ MOIN = 15 ++ XRCRG1 = 12 ++ XRCRG2 = 1 ++ RBPB = 50 ++ RBPD = 50 ++ RBPS = 15 ++ RBDB = 50 ++ RBSB = 50 ++ GBMIN = 1E-012 ++ RBPS0 = 50 ++ RBPSL = 0 ++ RBPSW = 0 ++ RBPSNF = 0 ++ RBPD0 = 50 ++ RBPDL = 0 ++ RBPDW = 0 ++ RBPDNF = 0 ++ RBPBX0 = 100 ++ RBPBXL = 0 ++ RBPBXW = 0 ++ RBPBXNF = 0 ++ RBPBY0 = 100 ++ RBPBYL = 0 ++ RBPBYW = 0 ++ RBPBYNF = 0 ++ RBSBX0 = 100 ++ RBSBY0 = 100 ++ RBDBX0 = 100 ++ RBDBY0 = 100 ++ RBSDBXL = 0 ++ RBSDBXW = 0 ++ RBSDBXNF = 0 ++ RBSDBYL = 0 ++ RBSDBYW = 0 ++ RBSDBYNF = 0 ++ NOIA = 6.25E+041 ++ NOIB = 3.125E+026 ++ NOIC = 8.75 ++ EM = 4.1E+007 ++ AF = 1 ++ EF = 1 ++ KF = 0 ++ LINTNOI = 0 ++ NTNOI = 1 ++ TNOIA = 1.5 ++ TNOIB = 3.5 ++ RNOIA = 0.577 ++ RNOIB = 0.5164 ++ DMCG = 0 ++ DMCI = 0 ++ DMDG = 0 ++ DMCGT = 0 ++ DWJ = 0 ++ XGW = 0 ++ XGL = 0 ++ XL = 0 ++ XW = 0 ++ NGCON = 1 ++ IJTHSREV = 0.1 ++ IJTHSFWD = 0.1 ++ XJBVS = 1 ++ BVS = 10 ++ JSS = 0.0001 ++ JSWS = 0 ++ JSWGS = 0 ++ JTSS = 0 ++ JTSSWS = 0 ++ JTSSWGS = 0 ++ JTWEFF = 0 ++ NJS = 15 ++ NJTS = 20 ++ NJTSSW = 20 ++ NJTSSWG = 20 ++ XTSS = 0.02 ++ XTSSWS = 0.02 ++ XTSSWGS = 0.02 ++ VTSS = 10 ++ VTSSWS = 10 ++ VTSSWGS = 10 ++ TNJTS = 0 ++ TNJTSSW = 0 ++ TNJTSSWG = 0 ++ CJS = 0.0005 ++ MJS = 0.5 ++ MJSWS = 0.33 ++ CJSWS = 5E-010 ++ CJSWGS = 5E-010 ++ MJSWGS = 0.33 ++ PBS = 1 ++ PBSWS = 1 ++ PBSWGS = 1 ++ IJTHDREV = 4.878E-003 ++ IJTHDFWD = 4.878E-003 ++ XJBVD = 1 ++ BVD = 10 ++ JSD = 1.004E-014 ++ JSWD = 2.467E-018 ++ JSWGD = 0 ++ JTSD = 0 ++ JTSSWD = 0 ++ JTSSWGD = 0 ++ NJD = 15 ++ NJTSD = 20 ++ NJTSSWD = 20 ++ NJTSSWGD = 20 ++ XTSD = 0.02 ++ XTSSWD = 0.02 ++ XTSSWGD = 0.02 ++ VTSD = 10 ++ VTSSWD = 10 ++ VTSSWGD = 10 ++ TNJTSD = 0 ++ TNJTSSWD = 0 ++ TNJTSSWGD = 0 ++ CJD = 0.0009368 ++ MJD = 0.3545 ++ MJSWD = 0.3141 ++ CJSWD = 7.65E-011 ++ CJSWGD = 5E-010 ++ MJSWGD = 0.33 ++ PBD = 0.9594 ++ PBSWD = 0.9109 ++ PBSWGD = 1 ++ TNOM = -253 ++ UTE = 0 ++ UCSTE = -0.004775 ++ KT1 = -0.11 ++ KT1L = 0 ++ KT2 = 0.022 ++ UA1 = 1E-009 ++ UB1 = -1E-018 ++ UC1 = -0.056 ++ UD1 = 0 ++ AT = 3.3E+004 ++ PRT = 0 ++ XTIS = 3 ++ XTID = 3 ++ TPB = 0 ++ TPBSW = 0 ++ TPBSWG = 0 ++ TCJ = 0 ++ TCJSW = 0 ++ TCJSWG = 0 ++ TVOFF = 0 ++ TVFBSDOFF = 0 ++ SAREF = 0 ++ SBREF = 0 ++ WLOD = 2E-006 ++ KU0 = 4E-006 ++ KVSAT = 0.2 ++ TKU0 = 0 ++ LKU0 = 1E-006 ++ WKU0 = 1E-006 ++ PKU0 = 0 ++ LLODKU0 = 1.1 ++ WLODKU0 = 1.1 ++ KVTH0 = -2E-008 ++ LKVTH0 = 1.1E-006 ++ WKVTH0 = 1.1E-006 ++ PKVTH0 = 0 ++ LLODVTH = 1 ++ WLODVTH = 1 ++ STK2 = 0 ++ LODK2 = 1 ++ STETA0 = 0 ++ LODETA0 = 1 ++ WEB = 0 ++ WEC = 0 ++ KVTH0WE = 0 ++ K2WE = 0 ++ KU0WE = 0 ++ SCREF = 1E-006 ++ WL = 7.641E-015 ++ WLN = 0.9975 ++ WW = -1E-014 ++ WWN = 1.012 ++ WWL = -1.218E-021 ++ LL = -3.165E-015 ++ LLN = 1 ++ LW = -4.827E-015 ++ LWN = 1 ++ LWL = 1.182E-021 ++ LLC = 0 ++ LWC = 0 ++ LWLC = 0 ++ WLC = 0 ++ WWC = 0 ++ WWLC = 0 +* +* diff --git a/openfasoc/generators/gdsfactory-gen/tapeout_and_RL/sky130A/sky130A.magicrc b/openfasoc/generators/gdsfactory-gen/tapeout_and_RL/sky130A/sky130A.magicrc new file mode 100644 index 000000000..9bb1dbd6c --- /dev/null +++ b/openfasoc/generators/gdsfactory-gen/tapeout_and_RL/sky130A/sky130A.magicrc @@ -0,0 +1,86 @@ +puts stdout "Sourcing design .magicrc for technology sky130A ..." + +# Put grid on 0.005 pitch. This is important, as some commands don't +# rescale the grid automatically (such as lef read?). + +set scalefac [tech lambda] +if {[lindex $scalefac 1] < 2} { + scalegrid 1 2 +} + +# drc off +drc euclidean on +# Change this to a fixed number for repeatable behavior with GDS writes +# e.g., "random seed 12345" +catch {random seed} + +# Turn off the scale option on ext2spice or else it conflicts with the +# scale in the model files. +ext2spice scale off + +# Allow override of PDK path from environment variable PDKPATH +if {[catch {set PDKPATH $env(PDKPATH)}]} { + set PDKPATH $env(PDK_ROOT)/sky130A +} + +# loading technology +tech load $PDKPATH/libs.tech/magic/sky130A.tech + +# load device generator +source $PDKPATH/libs.tech/magic/sky130A.tcl + +# load bind keys (optional) +# source $PDKPATH/libs.tech/magic/sky130A-BindKeys + +# set units to lambda grid +snap lambda + +# set sky130 standard power, ground, and substrate names +set VDD VPWR +set GND VGND +set SUB VSUBS + +# Allow override of type of magic library views used, "mag" or "maglef", +# from environment variable MAGTYPE + +if {[catch {set MAGTYPE $env(MAGTYPE)}]} { + set MAGTYPE mag +} + +# add path to reference cells +if {[file isdir ${PDKPATH}/libs.ref/${MAGTYPE}]} { + addpath ${PDKPATH}/libs.ref/${MAGTYPE}/sky130_fd_pr + addpath ${PDKPATH}/libs.ref/${MAGTYPE}/sky130_fd_io + addpath ${PDKPATH}/libs.ref/${MAGTYPE}/sky130_fd_sc_hd + addpath ${PDKPATH}/libs.ref/${MAGTYPE}/sky130_fd_sc_hdll + addpath ${PDKPATH}/libs.ref/${MAGTYPE}/sky130_fd_sc_hs + addpath ${PDKPATH}/libs.ref/${MAGTYPE}/sky130_fd_sc_hvl + addpath ${PDKPATH}/libs.ref/${MAGTYPE}/sky130_fd_sc_lp + addpath ${PDKPATH}/libs.ref/${MAGTYPE}/sky130_fd_sc_ls + addpath ${PDKPATH}/libs.ref/${MAGTYPE}/sky130_fd_sc_ms + addpath ${PDKPATH}/libs.ref/${MAGTYPE}/sky130_osu_sc + addpath ${PDKPATH}/libs.ref/${MAGTYPE}/sky130_osu_sc_t18 + addpath ${PDKPATH}/libs.ref/${MAGTYPE}/sky130_ml_xx_hd + addpath ${PDKPATH}/libs.ref/${MAGTYPE}/sky130_sram_macros +} else { + addpath ${PDKPATH}/libs.ref/sky130_fd_pr/${MAGTYPE} + addpath ${PDKPATH}/libs.ref/sky130_fd_io/${MAGTYPE} + addpath ${PDKPATH}/libs.ref/sky130_fd_sc_hd/${MAGTYPE} + addpath ${PDKPATH}/libs.ref/sky130_fd_sc_hdll/${MAGTYPE} + addpath ${PDKPATH}/libs.ref/sky130_fd_sc_hs/${MAGTYPE} + addpath ${PDKPATH}/libs.ref/sky130_fd_sc_hvl/${MAGTYPE} + addpath ${PDKPATH}/libs.ref/sky130_fd_sc_lp/${MAGTYPE} + addpath ${PDKPATH}/libs.ref/sky130_fd_sc_ls/${MAGTYPE} + addpath ${PDKPATH}/libs.ref/sky130_fd_sc_ms/${MAGTYPE} + addpath ${PDKPATH}/libs.ref/sky130_osu_sc/${MAGTYPE} + addpath ${PDKPATH}/libs.ref/sky130_osu_sc_t18/${MAGTYPE} + addpath ${PDKPATH}/libs.ref/sky130_ml_xx_hd/${MAGTYPE} + addpath ${PDKPATH}/libs.ref/sky130_sram_macros/${MAGTYPE} +} + +# add path to GDS cells + +# add path to IP from catalog. This procedure defined in the PDK script. +catch {magic::query_mylib_ip} +# add path to local IP from user design space. Defined in the PDK script. +catch {magic::query_my_projects} diff --git a/openfasoc/generators/gdsfactory-gen/tapeout_and_RL/sky130A/sky130A_setup.tcl b/openfasoc/generators/gdsfactory-gen/tapeout_and_RL/sky130A/sky130A_setup.tcl new file mode 100644 index 000000000..ede844e37 --- /dev/null +++ b/openfasoc/generators/gdsfactory-gen/tapeout_and_RL/sky130A/sky130A_setup.tcl @@ -0,0 +1,419 @@ +#--------------------------------------------------------------- +# Setup file for netgen LVS +# SkyWater sky130A +#--------------------------------------------------------------- +permute default +property default +property parallel none + +# Allow override of default #columns in the output format. +catch {format $env(NETGEN_COLUMNS)} + +#--------------------------------------------------------------- +# For the following, get the cell lists from +# circuit1 and circuit2. +#--------------------------------------------------------------- + +set cells1 [cells list -all -circuit1] +set cells2 [cells list -all -circuit2] + +# NOTE: In accordance with the LVS manager GUI, the schematic is +# always circuit2, so some items like property "par1" only need to +# be specified for circuit2. + +#------------------------------------------- +# Resistors (except metal) +#------------------------------------------- + +set devices {} +lappend devices sky130_fd_pr__res_iso_pw +lappend devices sky130_fd_pr__res_high_po_0p35 +lappend devices sky130_fd_pr__res_high_po_0p69 +lappend devices sky130_fd_pr__res_high_po_1p41 +lappend devices sky130_fd_pr__res_high_po_2p85 +lappend devices sky130_fd_pr__res_high_po_5p73 +lappend devices sky130_fd_pr__res_high_po +lappend devices sky130_fd_pr__res_xhigh_po_0p35 +lappend devices sky130_fd_pr__res_xhigh_po_0p69 +lappend devices sky130_fd_pr__res_xhigh_po_1p41 +lappend devices sky130_fd_pr__res_xhigh_po_2p85 +lappend devices sky130_fd_pr__res_xhigh_po_5p73 +lappend devices sky130_fd_pr__res_xhigh_po +lappend devices sky130_fd_pr__res_generic_nd +lappend devices sky130_fd_pr__res_generic_pd +lappend devices sky130_fd_pr__res_generic_nd__hv +lappend devices sky130_fd_pr__res_generic_pd__hv +lappend devices mrdn_hv mrdp_hv + +foreach dev $devices { + if {[lsearch $cells1 $dev] >= 0} { + permute "-circuit1 $dev" 1 2 + property "-circuit1 $dev" series enable + property "-circuit1 $dev" series {w critical} + property "-circuit1 $dev" series {l add} + property "-circuit1 $dev" parallel enable + property "-circuit1 $dev" parallel {l critical} + property "-circuit1 $dev" parallel {w add} + property "-circuit1 $dev" parallel {value par} + property "-circuit1 $dev" tolerance {l 0.01} {w 0.01} + # Ignore these properties + property "-circuit1 $dev" delete mult + } + if {[lsearch $cells2 $dev] >= 0} { + permute "-circuit2 $dev" 1 2 + property "-circuit2 $dev" series enable + property "-circuit2 $dev" series {w critical} + property "-circuit2 $dev" series {l add} + property "-circuit2 $dev" parallel enable + property "-circuit2 $dev" parallel {l critical} + property "-circuit2 $dev" parallel {w add} + property "-circuit2 $dev" parallel {value par} + property "-circuit2 $dev" tolerance {l 0.01} {w 0.01} + # Ignore these properties + property "-circuit2 $dev" delete mult + } +} + +#------------------------------------------- +# MRM (metal) resistors and poly resistor +#------------------------------------------- + +set devices {} +lappend devices sky130_fd_pr__res_generic_po +lappend devices sky130_fd_pr__res_generic_l1 +lappend devices sky130_fd_pr__res_generic_m1 +lappend devices sky130_fd_pr__res_generic_m2 +lappend devices sky130_fd_pr__res_generic_m3 +lappend devices sky130_fd_pr__res_generic_m4 +lappend devices sky130_fd_pr__res_generic_m5 + +foreach dev $devices { + if {[lsearch $cells1 $dev] >= 0} { + permute "-circuit1 $dev" end_a end_b + property "-circuit1 $dev" series enable + property "-circuit1 $dev" series {w critical} + property "-circuit1 $dev" series {l add} + property "-circuit1 $dev" parallel enable + property "-circuit1 $dev" parallel {l critical} + property "-circuit1 $dev" parallel {w add} + property "-circuit1 $dev" parallel {value par} + property "-circuit1 $dev" tolerance {l 0.01} {w 0.01} + # Ignore these properties + property "-circuit1 $dev" delete mult + } + if {[lsearch $cells2 $dev] >= 0} { + permute "-circuit2 $dev" end_a end_b + property "-circuit2 $dev" series enable + property "-circuit2 $dev" series {w critical} + property "-circuit2 $dev" series {l add} + property "-circuit2 $dev" parallel enable + property "-circuit2 $dev" parallel {l critical} + property "-circuit2 $dev" parallel {w add} + property "-circuit2 $dev" parallel {value par} + property "-circuit2 $dev" tolerance {l 0.01} {w 0.01} + # Ignore these properties + property "-circuit2 $dev" delete mult + } +} + +#------------------------------------------- +# (MOS) transistors +#------------------------------------------- + +set devices {} +lappend devices sky130_fd_pr__nfet_01v8 +lappend devices sky130_fd_pr__nfet_01v8_lvt +lappend devices sky130_fd_bs_flash__special_sonosfet_star +lappend devices sky130_fd_pr__nfet_g5v0d10v5 +lappend devices sky130_fd_pr__nfet_05v0_nvt +lappend devices sky130_fd_pr__pfet_01v8 +lappend devices sky130_fd_pr__pfet_01v8_lvt +lappend devices sky130_fd_pr__pfet_01v8_mvt +lappend devices sky130_fd_pr__pfet_01v8_hvt +lappend devices sky130_fd_pr__pfet_g5v0d10v5 +lappend devices sky130_fd_pr__special_pfet_pass +lappend devices sky130_fd_pr__special_nfet_pass +lappend devices sky130_fd_pr__special_nfet_latch +lappend devices sky130_fd_pr__cap_var_lvt +lappend devices sky130_fd_pr__cap_var_hvt +lappend devices sky130_fd_pr__cap_var +lappend devices sky130_fd_pr__nfet_20v0_nvt +lappend devices sky130_fd_pr__nfet_20v0 +lappend devices sky130_fd_pr__pfet_20v0 + +foreach dev $devices { + if {[lsearch $cells1 $dev] >= 0} { + permute "-circuit1 $dev" 1 3 + property "-circuit1 $dev" parallel enable + property "-circuit1 $dev" parallel {l critical} + property "-circuit1 $dev" parallel {w add} + property "-circuit1 $dev" tolerance {w 0.01} {l 0.01} + # Ignore these properties + property "-circuit1 $dev" delete as ad ps pd mult sa sb sd nf nrd nrs + } + if {[lsearch $cells2 $dev] >= 0} { + permute "-circuit2 $dev" 1 3 + property "-circuit2 $dev" parallel enable + property "-circuit2 $dev" parallel {l critical} + property "-circuit2 $dev" parallel {w add} + property "-circuit2 $dev" tolerance {w 0.01} {l 0.01} + # Ignore these properties + property "-circuit2 $dev" delete as ad ps pd mult sa sb sd nf nrd nrs + } +} + +#------------------------------------------- +# diodes +#------------------------------------------- + +set devices {} +lappend devices sky130_fd_pr__diode_pw2nd_05v5 +lappend devices sky130_fd_pr__diode_pw2nd_05v5_lvt +lappend devices sky130_fd_pr__diode_pw2nd_05v5_nvt +lappend devices sky130_fd_pr__diode_pd2nw_05v5 +lappend devices sky130_fd_pr__diode_pd2nw_05v5_lvt +lappend devices sky130_fd_pr__diode_pd2nw_05v5_hvt +lappend devices sky130_fd_pr__diode_pw2nd_11v0 +lappend devices sky130_fd_pr__diode_pd2nw_11v0 + +foreach dev $devices { + if {[lsearch $cells1 $dev] >= 0} { + property "-circuit1 $dev" parallel enable + property "-circuit1 $dev" parallel {area add} + property "-circuit1 $dev" parallel {value add} + property "-circuit1 $dev" tolerance {area 0.02} + # Ignore these properties + property "-circuit1 $dev" delete mult perim + } + if {[lsearch $cells2 $dev] >= 0} { + property "-circuit2 $dev" parallel enable + property "-circuit2 $dev" parallel {area add} + property "-circuit2 $dev" parallel {value add} + property "-circuit2 $dev" tolerance {area 0.02} + # Ignore these properties + property "-circuit2 $dev" delete mult perim + } +} + +#------------------------------------------- +# capacitors +# MiM capacitors +#------------------------------------------- + +set devices {} +lappend devices sky130_fd_pr__cap_mim_m3_1 +lappend devices sky130_fd_pr__cap_mim_m3_2 + +foreach dev $devices { + if {[lsearch $cells1 $dev] >= 0} { + property "-circuit1 $dev" parallel enable + property "-circuit1 $dev" parallel {area add} + property "-circuit1 $dev" parallel {value add} + property "-circuit1 $dev" tolerance {l 0.01} {w 0.01} + # Ignore these properties + property "-circuit1 $dev" delete mult perim mf + } + if {[lsearch $cells2 $dev] >= 0} { + property "-circuit2 $dev" parallel enable + property "-circuit2 $dev" parallel {area add} + property "-circuit2 $dev" parallel {value add} + property "-circuit2 $dev" tolerance {l 0.01} {w 0.01} + # Ignore these properties + property "-circuit2 $dev" delete mult perim mf + } +} + +#------------------------------------------- +# Fixed-layout devices +# bipolar transistors, +# VPP capacitors +#------------------------------------------- + +set devices {} +lappend devices sky130_fd_pr__npn_05v5_W1p00L1p00 +lappend devices sky130_fd_pr__npn_05v5_W1p00L2p00 +lappend devices sky130_fd_pr__pnp_05v5_W0p68L0p68 +lappend devices sky130_fd_pr__pnp_05v5_W3p40L3p40 +lappend devices sky130_fd_pr__npn_05v5 +lappend devices sky130_fd_pr__pnp_05v5 +lappend devices sky130_fd_pr__npn_11v0 + +lappend devices sky130_fd_pr__cap_vpp_11p5x11p7_lim5_shield +lappend devices sky130_fd_pr__cap_vpp_11p5x11p7_m3_lim5_shield +lappend devices sky130_fd_pr__cap_vpp_11p5x11p7_m4_shield +lappend devices sky130_fd_pr__cap_vpp_11p5x11p7_pom4_shield +lappend devices sky130_fd_pr__cap_vpp_4p4x4p6_m3_lim5_shield +lappend devices sky130_fd_pr__cap_vpp_6p8x6p1_lim4_shield +lappend devices sky130_fd_pr__cap_vpp_6p8x6p1_polym4_shield +lappend devices sky130_fd_pr__cap_vpp_8p6x7p9_m3_lim5_shield +lappend devices sky130_fd_pr__cap_vpp_11p5x11p7_m3_li_shield +lappend devices sky130_fd_pr__cap_vpp_11p5x11p7_m3_shield +lappend devices sky130_fd_pr__cap_vpp_1p8x1p8_li_shield +lappend devices sky130_fd_pr__cap_vpp_1p8x1p8_m3_shield +lappend devices sky130_fd_pr__cap_vpp_4p4x4p6_m3_li_shield +lappend devices sky130_fd_pr__cap_vpp_4p4x4p6_m3_shield +lappend devices sky130_fd_pr__cap_vpp_8p6x7p9_m3_li_shield +lappend devices sky130_fd_pr__cap_vpp_8p6x7p9_m3_shield +lappend devices sky130_fd_pr__ind_04_01 +lappend devices sky130_fd_pr__ind_04_02 + +foreach dev $devices { + if {[lsearch $cells1 $dev] >= 0} { + property "-circuit1 $dev" parallel enable + # Ignore these properties + property "-circuit1 $dev" delete mult + } + if {[lsearch $cells2 $dev] >= 0} { + property "-circuit2 $dev" parallel enable + # Ignore these properties + property "-circuit2 $dev" delete mult + } +} + +#--------------------------------------------------------------- +# Schematic cells which are not extractable +#--------------------------------------------------------------- + +set devices {sky130_fd_io__condiode sky130_fd_io__tap_1} + +foreach dev $devices { + if {[lsearch $cells1 $dev] >= 0} { + ignore class "-circuit1 $dev" + } + if {[lsearch $cells2 $dev] >= 0} { + ignore class "-circuit2 $dev" + } +} + +#--------------------------------------------------------------- +# Digital cells (ignore decap, fill, and tap cells) +# Make a separate list for each supported library +#--------------------------------------------------------------- +# e.g., ignore class "-circuit2 sky130_fc_sc_hd__decap_3" +#--------------------------------------------------------------- + +if { [info exist ::env(MAGIC_EXT_USE_GDS)] && $::env(MAGIC_EXT_USE_GDS) } { + foreach cell $cells1 { +# if {[regexp {sky130_fd_sc_[^_]+__decap_[[:digit:]]+} $cell match]} { +# ignore class "-circuit1 $cell" +# } + if {[regexp {sky130_fd_sc_[^_]+__fill_[[:digit:]]+} $cell match]} { + ignore class "-circuit1 $cell" + } + if {[regexp {sky130_fd_sc_[^_]+__tapvpwrvgnd_[[:digit:]]+} $cell match]} { + ignore class "-circuit1 $cell" + } + if {[regexp {sky130_ef_sc_[^_]+__fakediode_[[:digit:]]+} $cell match]} { + ignore class "-circuit1 $cell" + } + } + foreach cell $cells2 { +# if {[regexp {sky130_fd_sc_[^_]+__decap_[[:digit:]]+} $cell match]} { +# ignore class "-circuit2 $cell" +# } + if {[regexp {sky130_fd_sc_[^_]+__fill_[[:digit:]]+} $cell match]} { + ignore class "-circuit2 $cell" + } + if {[regexp {sky130_fd_sc_[^_]+__tapvpwrvgnd_[[:digit:]]+} $cell match]} { + ignore class "-circuit2 $cell" + } + if {[regexp {sky130_ef_sc_[^_]+__fakediode_[[:digit:]]+} $cell match]} { + ignore class "-circuit2 $cell" + } + } +} + +#--------------------------------------------------------------- +# Allow the fill, decap, etc., cells to be parallelized +#--------------------------------------------------------------- + +foreach cell $cells1 { + if {[regexp {sky130_fd_sc_[^_]+__decap_[[:digit:]]+} $cell match]} { + property "-circuit1 $cell" parallel enable + } + if {[regexp {sky130_fd_sc_[^_]+__fill_[[:digit:]]+} $cell match]} { + property "-circuit1 $cell" parallel enable + } + if {[regexp {sky130_fd_sc_[^_]+__tapvpwrvgnd_[[:digit:]]+} $cell match]} { + property "-circuit1 $cell" parallel enable + } + if {[regexp {sky130_fd_sc_[^_]+__diode_[[:digit:]]+} $cell match]} { + property "-circuit1 $cell" parallel enable + } + if {[regexp {sky130_fd_sc_[^_]+__fill_diode_[[:digit:]]+} $cell match]} { + property "-circuit1 $cell" parallel enable + } + if {[regexp {sky130_ef_sc_[^_]+__fakediode_[[:digit:]]+} $cell match]} { + property "-circuit1 $cell" parallel enable + } +} +foreach cell $cells2 { + if {[regexp {sky130_fd_sc_[^_]+__decap_[[:digit:]]+} $cell match]} { + property "-circuit2 $cell" parallel enable + } + if {[regexp {sky130_fd_sc_[^_]+__fill_[[:digit:]]+} $cell match]} { + property "-circuit2 $cell" parallel enable + } + if {[regexp {sky130_fd_sc_[^_]+__tapvpwrvgnd_[[:digit:]]+} $cell match]} { + property "-circuit2 $cell" parallel enable + } + if {[regexp {sky130_fd_sc_[^_]+__diode_[[:digit:]]+} $cell match]} { + property "-circuit2 $cell" parallel enable + } + if {[regexp {sky130_fd_sc_[^_]+__fill_diode_[[:digit:]]+} $cell match]} { + property "-circuit2 $cell" parallel enable + } + if {[regexp {sky130_ef_sc_[^_]+__fakediode_[[:digit:]]+} $cell match]} { + property "-circuit2 $cell" parallel enable + } +} + +#--------------------------------------------------------------- +# Handle cells captured from Electric +# +# Find cells of the form "__" in the netlist +# from Electric where the extracted layout netlist has only +# "". Cross-check by ensuring that the full name +# "__" does not exist in both cells, and that +# the truncated name "" does not exist in both cells. +#--------------------------------------------------------------- +# e.g., hydra_spi_controller__hydra_spi_controller +#--------------------------------------------------------------- + +foreach cell $cells1 { + if {[regexp "(.+)__(.+)" $cell match library cellname]} { + if {([lsearch $cells2 $cell] < 0) && \ + ([lsearch $cells2 $cellname] >= 0) && \ + ([lsearch $cells1 $cellname] < 0)} { + equate classes "-circuit1 $cell" "-circuit2 $cellname" + puts stdout "Matching pins of $cell in circuit 1 and $cellname in circuit 2" + equate pins "-circuit1 $cell" "-circuit2 $cellname" + } + } +} + +foreach cell $cells2 { + if {[regexp "(.+)__(.+)" $cell match library cellname]} { + if {([lsearch $cells1 $cell] < 0) && \ + ([lsearch $cells1 $cellname] >= 0) && \ + ([lsearch $cells2 $cellname] < 0)} { + equate classes "-circuit1 $cellname" "-circuit2 $cell" + puts stdout "Matching pins of $cellname in circuit 1 and $cell in circuit 2" + equate pins "-circuit1 $cellname" "-circuit2 $cell" + } + } +} + +# Match pins on black-box cells if LVS is called with "-blackbox" +if {[model blackbox]} { + foreach cell $cells1 { + if {[model "-circuit1 $cell"] == "blackbox"} { + if {[lsearch $cells2 $cell] >= 0} { + puts stdout "Matching pins of $cell in circuits 1 and 2" + equate pins "-circuit1 $cell" "-circuit2 $cell" + } + } + } +} + +#--------------------------------------------------------------- diff --git a/openfasoc/generators/gdsfactory-gen/tapeout_and_RL/sky130_nist_tapeout.py b/openfasoc/generators/gdsfactory-gen/tapeout_and_RL/sky130_nist_tapeout.py new file mode 100644 index 000000000..0d66b8cf6 --- /dev/null +++ b/openfasoc/generators/gdsfactory-gen/tapeout_and_RL/sky130_nist_tapeout.py @@ -0,0 +1,1162 @@ +import sys +# path to glayout +sys.path.append('../') + +from gdsfactory.read.import_gds import import_gds +from gdsfactory.components import text_freetype, rectangle +from glayout.pdk.util.comp_utils import prec_array, movey, align_comp_to_port, prec_ref_center +from glayout.pdk.util.port_utils import add_ports_perimeter, print_ports +from gdsfactory.component import Component +from glayout.pdk.mappedpdk import MappedPDK +from glayout.opamp import opamp +from glayout.routing.L_route import L_route +from glayout.routing.straight_route import straight_route +from glayout.routing.c_route import c_route +from glayout.via_gen import via_array +from gdsfactory.cell import cell, clear_cache +import numpy as np +from subprocess import Popen +from pathlib import Path +from typing import Union, Optional, Literal, Iterable +from tempfile import TemporaryDirectory +from shutil import copyfile, copytree +from multiprocessing import Pool +import matplotlib.pyplot as plt +from scipy.stats import norm +from scipy.optimize import curve_fit +from scipy.spatial.distance import pdist, squareform +import pandas as pd +import seaborn as sns +from sklearn.decomposition import PCA +from sklearn.cluster import KMeans, AgglomerativeClustering +from sklearn.metrics import silhouette_score +import argparse +from glayout.pdk.sky130_mapped import sky130_mapped_pdk as pdk +from itertools import count, repeat +from glayout.pdk.util.snap_to_grid import component_snap_to_grid +from glayout.pdk.util.opamp_array_create import write_opamp_matrix + + +global _GET_PARAM_SET_LENGTH_ +global _TAKE_OUTPUT_AT_SECOND_STAGE_ +_GET_PARAM_SET_LENGTH_ = False +_TAKE_OUTPUT_AT_SECOND_STAGE_ = False + + +# ====Build Opamp==== + + +def sky130_opamp_add_pads(opamp_in: Component, flatten=False) -> Component: + """adds the MPW-5 pads and nano pads to opamp. + Also adds text labels and pin layers so that extraction is nice + this function does not need to be used with sky130_add_opamp_labels + """ + opamp_wpads = opamp_in.copy() + opamp_wpads = movey(opamp_wpads, destination=0) + # create pad array and add to opamp + pad = import_gds("pads/pad_60um_flat.gds") + pad.name = "NISTpad" + pad = add_ports_perimeter(pad, pdk.get_glayer("met4"),prefix="pad_") + pad_array = prec_array(pad, rows=2, columns=(4+1), spacing=(20,120)) + pad_array_ref = prec_ref_center(pad_array) + opamp_wpads.add(pad_array_ref) + # add via_array to vdd pin + vddarray = via_array(pdk, "met4","met5",size=(opamp_wpads.ports["pin_vdd_N"].width,opamp_wpads.ports["pin_vdd_E"].width)) + via_array_ref = opamp_wpads << vddarray + align_comp_to_port(via_array_ref,opamp_wpads.ports["pin_vdd_N"],alignment=('c','b')) + # route to the pads + opamp_wpads << L_route(pdk, opamp_wpads.ports["pin_minus_W"],pad_array_ref.ports["row1_col1_pad_S"],hwidth=3) + opamp_wpads << L_route(pdk, opamp_wpads.ports["pin_plus_W"],pad_array_ref.ports["row0_col1_pad_N"],hwidth=3) + opamp_wpads << straight_route(pdk, pad_array_ref.ports["row1_col2_pad_S"],opamp_wpads.ports["pin_vdd_S"], width=4,glayer1="met5") + opamp_wpads << straight_route(pdk, opamp_wpads.ports["pin_diffpairibias_S"],pad_array_ref.ports["row0_col2_pad_N"]) + opamp_wpads << L_route(pdk, opamp_wpads.ports["gnd_route_con_E"],pad_array_ref.ports["row0_col3_pad_N"], vglayer="met4",hwidth=3) + opamp_wpads << L_route(pdk, opamp_wpads.ports["pin_commonsourceibias_E"],pad_array_ref.ports["row0_col4_pad_N"],hwidth=3) + opamp_wpads << L_route(pdk, opamp_wpads.ports["pin_outputibias_E"],pad_array_ref.ports["row1_col4_pad_S"], hwidth=3) + opamp_wpads << c_route(pdk, opamp_wpads.ports["pin_output_route_E"],pad_array_ref.ports["row1_col3_pad_E"], extension=1, cglayer="met3", cwidth=4) + # add pin layer and text labels for LVS + text_pin_labels = list() + met5pin = rectangle(size=(5,5),layer=(72,16), centered=True) + for name in ["plus","diffpairibias","gnd","commonsourceibias","minus","vdd","output","outputibias"]: + pin_w_label = met5pin.copy() + pin_w_label.add_label(text=name,layer=(72,5),magnification=4) + text_pin_labels.append(pin_w_label) + for row in range(2): + for col_u in range(4): + col = col_u + 1# left most are for nano pads + port_name = "row"+str(row)+"_col"+str(col)+"_pad_S" + pad_array_port = pad_array_ref.ports[port_name] + pin_ref = opamp_wpads << text_pin_labels[4*row + col_u] + align_comp_to_port(pin_ref,pad_array_port,alignment=('c','t')) + # import nano pad and add to opamp + nanopad = import_gds("pads/sky130_nano_pad.gds") + nanopad.name = "nanopad" + nanopad = add_ports_perimeter(nanopad, pdk.get_glayer("met4"),prefix="nanopad_") + nanopad_array = prec_array(nanopad, rows=2, columns=2, spacing=(10,10)) + nanopad_array_ref = nanopad_array.ref_center() + opamp_wpads.add(nanopad_array_ref) + nanopad_array_ref.movex(opamp_wpads.xmin+nanopad_array.xmax) + # route nano pad connections + opamp_wpads << straight_route(pdk, nanopad_array_ref.ports["row1_col0_nanopad_N"],pad_array_ref.ports["row1_col0_pad_S"],width=3) + opamp_wpads << straight_route(pdk, nanopad_array_ref.ports["row0_col0_nanopad_S"],pad_array_ref.ports["row0_col0_pad_N"],width=3) + opamp_wpads << straight_route(pdk, nanopad_array_ref.ports["row0_col1_nanopad_E"],pad_array_ref.ports["row0_col1_pad_N"],width=3) + opamp_wpads << straight_route(pdk, nanopad_array_ref.ports["row1_col1_nanopad_E"],pad_array_ref.ports["row1_col1_pad_S"],width=3) + # add the extra pad for the CS output + cspadref = opamp_wpads << pad + cspadref.movex(240).movey(90) + opamp_wpads << L_route(pdk, cspadref.ports["pad_S"], opamp_wpads.ports["commonsource_output_E"],hwidth=3, hglayer="met5",vglayer="met5") + #opamp_wpads << nanopad + if flatten: + return opamp_wpads.flatten() + else: + return opamp_wpads + + +def sky130_add_opamp_labels(opamp_in: Component) -> Component: + """adds opamp labels for extraction, without adding pads + this function does not need to be used with sky130_add_opamp_pads + """ + opamp_in.unlock() + # define layers + met2_pin = (69,16) + met2_label = (69,5) + met3_pin = (70,16) + met3_label = (70,5) + met4_pin = (71,16) + met4_label = (71,5) + # list that will contain all port/comp info + move_info = list() + # create labels and append to info list + # gnd + gndlabel = rectangle(layer=met3_pin,size=(1,1),centered=True).copy() + gndlabel.add_label(text="gnd",layer=met3_label) + move_info.append((gndlabel,opamp_in.ports["pin_gnd_N"],None)) + #diffpairibias + ibias1label = rectangle(layer=met2_pin,size=(1,1),centered=True).copy() + ibias1label.add_label(text="diffpairibias",layer=met2_label) + move_info.append((ibias1label,opamp_in.ports["pin_diffpairibias_N"],None)) + #outputibias + ibias3label = rectangle(layer=met2_pin,size=(1,1),centered=True).copy() + ibias3label.add_label(text="outputibias",layer=met2_label) + move_info.append((ibias3label,opamp_in.ports["pin_outputibias_N"],None)) + # commonsourceibias + ibias2label = rectangle(layer=met4_pin,size=(1,1),centered=True).copy() + ibias2label.add_label(text="commonsourceibias",layer=met4_label) + move_info.append((ibias2label,opamp_in.ports["pin_commonsourceibias_N"],None)) + #minus + minuslabel = rectangle(layer=met3_pin,size=(1,1),centered=True).copy() + minuslabel.add_label(text="minus",layer=met3_label) + move_info.append((minuslabel,opamp_in.ports["pin_minus_N"],None)) + #-plus + pluslabel = rectangle(layer=met3_pin,size=(1,1),centered=True).copy() + pluslabel.add_label(text="plus",layer=met3_label) + move_info.append((pluslabel,opamp_in.ports["pin_plus_N"],None)) + #vdd + vddlabel = rectangle(layer=met3_pin,size=(1,1),centered=True).copy() + vddlabel.add_label(text="vdd",layer=met3_label) + move_info.append((vddlabel,opamp_in.ports["pin_vdd_N"],None)) + # output (3rd stage) + outputlabel = rectangle(layer=met2_pin,size=(1,1),centered=True).copy() + outputlabel.add_label(text="output",layer=met2_label) + move_info.append((outputlabel,opamp_in.ports["pin_output_route_N"],None)) + # output (2nd stage) + outputlabel = rectangle(layer=met4_pin,size=(0.2,0.2),centered=True).copy() + outputlabel.add_label(text="CSoutput",layer=met4_label) + move_info.append((outputlabel,opamp_in.ports["commonsource_output_E"],('l','c'))) + # move everything to position + for comp, prt, alignment in move_info: + alignment = ('c','b') if alignment is None else alignment + compref = align_comp_to_port(comp, prt, alignment=alignment) + opamp_in.add(compref) + return opamp_in.flatten() + + +def sky130_add_lvt_layer(opamp_in: Component) -> Component: + opamp_in.unlock() + # define layers + lvt_layer = (125,44) + # define geometry over pmos components and add lvt + SW_S_edge = opamp_in.ports["commonsource_Pamp_L_multiplier_0_plusdoped_S"] + SW_W_edge = opamp_in.ports["commonsource_Pamp_L_multiplier_0_plusdoped_W"] + NE_N_edge = opamp_in.ports["commonsource_Pamp_R_multiplier_2_plusdoped_N"] + NE_E_edge = opamp_in.ports["commonsource_Pamp_R_multiplier_2_plusdoped_E"] + SW_S_center = SW_S_edge.center + SW_W_center = SW_W_edge.center + NE_N_center = NE_N_edge.center + NE_E_center = NE_E_edge.center + SW_corner = [SW_W_center[0], SW_S_center[1]] + NE_corner = [NE_E_center[0], NE_N_center[1]] + middle_top_y = opamp_in.ports["pcomps_ptopAB_L_plusdoped_N"].center[1] + middle_bottom_y = opamp_in.ports["pcomps_pbottomAB_R_plusdoped_S"].center[1] + max_y = max(middle_top_y, NE_corner[1]) + min_y = min(middle_bottom_y, SW_corner[1]) + abs_center = (SW_corner[0] + (NE_corner[0] - SW_corner[0])/2, min_y + (max_y - min_y)/2) + # draw lvt rectangle + LVT_rectangle = rectangle(layer=lvt_layer, size=(abs(NE_corner[0] - SW_corner[0]), abs(max_y - min_y)), centered=True) + LVT_rectangle_ref = opamp_in << LVT_rectangle + # align lvt rectangle to the plusdoped_N region + LVT_rectangle_ref.move(origin=(0, 0), destination=abs_center) + # define geometry over output amplfier and add lvt + outputW = opamp_in.ports["outputstage_amp_multiplier_0_plusdoped_W"] + outputE = opamp_in.ports["outputstage_amp_multiplier_0_plusdoped_E"] + width = abs(outputE.center[0]-outputW.center[0]) + hieght = outputW.width + center = (outputW.center[0] + width/2, outputW.center[1]) + lvtref = opamp_in << rectangle(size=(width,hieght),layer=lvt_layer,centered=True) + lvtref.move(destination=center) + return opamp_in + + + +# ====Run Training==== + + + +def opamp_parameters_serializer( + diffpair_params: tuple[float, float, int] = (6, 1, 4), + diffpair_bias: tuple[float, float, int] = (6, 2, 4), + half_common_source_params: tuple[float, float, int, int] = (7, 1, 10, 3), + half_common_source_bias: tuple[float, float, int, int] = (6, 2, 8, 2), + output_stage_params: tuple[float, float, int] = (5, 1, 16), + output_stage_bias: tuple[float, float, int] = (6, 2, 4), + mim_cap_size=(12, 12), + mim_cap_rows=3, + rmult: int = 2 +) -> np.array: + """converts opamp params into the uniform numpy float format""" + return np.array( + [diffpair_params[0],diffpair_params[1],diffpair_params[2], + diffpair_bias[0],diffpair_bias[1],diffpair_bias[2], + half_common_source_params[0],half_common_source_params[1],half_common_source_params[2],half_common_source_params[3], + half_common_source_bias[0],half_common_source_bias[1],half_common_source_bias[2],half_common_source_bias[3], + output_stage_params[0],output_stage_params[1],output_stage_params[2], + output_stage_bias[0],output_stage_bias[1],output_stage_bias[2], + mim_cap_size[0],mim_cap_size[1], + mim_cap_rows, + rmult], + dtype=np.float64 + ) + +def opamp_parameters_de_serializer(serialized_params: Optional[np.array]=None) -> dict: + """converts uniform numpy float format to opamp kwargs""" + if serialized_params is None: + serialized_params = 24*[-987.654321] + serialized_params[16] = int(-987.654321) + serialized_params[17] = int(-987.654321) + if not len(serialized_params) == 24: + raise ValueError("serialized_params should be a length 24 array") + params_dict = dict() + params_dict["diffpair_params"] = tuple(serialized_params[0:3]) + params_dict["diffpair_bias"] = tuple(serialized_params[3:6]) + params_dict["half_common_source_params"] = tuple(serialized_params[6:10]) + params_dict["half_common_source_bias"] = tuple(serialized_params[10:14]) + params_dict["output_stage_params"] = tuple(serialized_params[14:17]) + params_dict["output_stage_bias"] = tuple(serialized_params[17:20]) + params_dict["mim_cap_size"] = tuple(serialized_params[20:22]) + params_dict["mim_cap_rows"] = int(serialized_params[22]) + params_dict["rmult"] = int(serialized_params[23]) + return params_dict + +def opamp_results_serializer( + ugb: float = -987.654321, + dcGain: float = -987.654321, + phaseMargin: float = -987.654321, + Ibias_diffpair: float = -987.654321, + Ibias_commonsource: float = -987.654321, + Ibias_output: float = -987.654321, + area: float = -987.654321, + power: float = -987.654321, + noise: float = -987.654321, + bw_3db: float = -987.654321, + power_twostage: float = -987.654321 +) -> np.array: + return np.array([ugb, dcGain, phaseMargin, Ibias_diffpair, Ibias_commonsource, Ibias_output, area, power, noise, bw_3db, power_twostage], dtype=np.float64) + +def opamp_results_de_serializer( + results: Optional[np.array]=None +) -> dict: + results_length_const = 11 + if results is None: + results = results_length_const*[-987.654321] + if not len(results) == results_length_const: + raise ValueError("results should be a length "+str(results_length_const)+" array") + results_dict = dict() + results_dict["ugb"] = float(results[0]) + results_dict["dcGain"] = float(results[1]) + results_dict["phaseMargin"] = float(results[2]) + results_dict["Ibias_diffpair"] = float(results[3]) + results_dict["Ibias_commonsource"] = float(results[4]) + results_dict["Ibias_output"] = float(results[5]) + results_dict["area"] = float(results[6]) + results_dict["power"] = float(results[7]) + results_dict["noise"] = float(results[8]) + results_dict["bw_3db"] = float(results[9]) + results_dict["power_twostage"] = float(results[10]) + return results_dict + +def get_small_parameter_list(test_mode = False) -> np.array: + """creates small parameter list intended for brute force""" + # all diffpairs to try + diffpairs = list() + if test_mode: + diffpairs.append((6,1,4)) + diffpairs.append((5,1,4)) + else: + for width in [2,6]: + for length in [0.5, 1, 2]: + for fingers in [2,4]: + diffpairs.append((width,length,fingers)) + # all bias2 (output amp bias) transistors + bias2s = list() + if test_mode: + bias2s.append((6,1,4,3)) + else: + for width in [6]: + for length in [1,2]: + for fingers in [4]: + for mults in [2,3]: + bias2s.append((width,length,fingers,mults)) + # all output pmos transistors + pamp_hparams = list() + if test_mode: + pamp_hparams.append((7,1,8,3)) + else: + for width in [7,4]: + for length in [0.5,1,2]: + for fingers in [8,4,2]: + pamp_hparams.append((width,length,fingers,3)) + # diffpair bias cmirror + diffpair_cmirrors = list() + if test_mode: + pass + else: + for width in [6]: + for length in [2]: + for fingers in [4]: + diffpair_cmirrors.append((width,length,fingers)) + # rows of the cap array to try + cap_arrays = [1,2] + # routing mults to try + rmults = [2] + # ****************************************** + # create and return the small parameters list + short_list_len = len(diffpairs) * len(bias2s) * len(pamp_hparams) * len(cap_arrays) * len(rmults) * len(diffpair_cmirrors) + short_list_len += 2 if test_mode else 0 + short_list = np.empty(shape=(short_list_len,len(opamp_parameters_serializer())),dtype=np.float64) + index = 0 + for diffpair_v in diffpairs: + for bias2_v in bias2s: + for pamp_o_v in pamp_hparams: + for cap_array_v in cap_arrays: + for rmult in rmults: + for diffpair_cmirror_v in diffpair_cmirrors: + tup_to_add = opamp_parameters_serializer( + diffpair_params=diffpair_v, + half_common_source_bias=bias2_v, + mim_cap_rows=cap_array_v, + half_common_source_params=pamp_o_v, + rmult=rmult, + diffpair_bias=diffpair_cmirror_v, + ) + short_list[index] = tup_to_add + index = index + 1 + # if test_mode create a failed attempt (to test error handling) + if test_mode: + short_list[index] = opamp_parameters_serializer(mim_cap_rows=-1) + short_list[index+1] = opamp_parameters_serializer(mim_cap_rows=0) + global _GET_PARAM_SET_LENGTH_ + if _GET_PARAM_SET_LENGTH_: + print("created parameter set of length: "+str(len(short_list))) + import sys + sys.exit() + return short_list + +def get_sim_results(acpath: Union[str,Path], dcpath: Union[str,Path], noisepath: Union[str,Path]): + acabspath = Path(acpath).resolve() + dcabspath = Path(dcpath).resolve() + noiseabspath = Path(noisepath).resolve() + ACColumns = None + DCColumns = None + NoiseColumns = None + try: + with open(acabspath, "r") as ACReport: + RawAC = ACReport.readlines()[0] + ACColumns = [item for item in RawAC.split() if item] + except Exception: + pass + try: + with open(dcabspath, "r") as DCReport: + RawDC = DCReport.readlines()[0] + DCColumns = [item for item in RawDC.split() if item] + except Exception: + pass + try: + with open(noiseabspath, "r") as NoiseReport: + RawNoise = NoiseReport.readlines()[0] + NoiseColumns = [item for item in RawNoise.split() if item] + except Exception: + pass + na = -987.654321 + noACresults = (ACColumns is None) or len(ACColumns)<13 + noDCresults = (DCColumns is None) or len(DCColumns)<4 + nonoiseresults = (NoiseColumns is None) or len(NoiseColumns)<2 + return_dict = { + "ugb": na if noACresults else ACColumns[1], + "Ibias_diffpair": na if noACresults else ACColumns[3], + "Ibias_commonsource": na if noACresults else ACColumns[5], + "Ibias_output": na if noACresults else ACColumns[7], + "phaseMargin": na if noACresults else ACColumns[9], + "dcGain": na if noACresults else ACColumns[11], + "bw_3db": na if noACresults else ACColumns[13], + "power": na if noDCresults else DCColumns[1], + "noise": na if nonoiseresults else NoiseColumns[1], + "power_twostage": na if noDCresults else DCColumns[3], + } + for key, val in return_dict.items(): + val_flt = na + try: + val_flt = float(val) + except ValueError: + val_flt = na + return_dict[key] = val_flt + return return_dict + +def process_netlist_subckt(netlist: Union[str,Path], sim_model: Literal["normal model", "cryo model"], cload: float=0.0, noparasitics: bool=False): + global _TAKE_OUTPUT_AT_SECOND_STAGE_ + netlist = Path(netlist).resolve() + if not netlist.is_file(): + raise ValueError("netlist must be file") + hints = [".subckt","output","plus","minus","vdd","gnd","commonsourceibias","outputibias"] + subckt_lines = list() + with open(netlist, "r") as spice_net: + subckt_lines = spice_net.readlines() + for i,line in enumerate(subckt_lines): + line = line.strip().lower() + if (i+1) np.array: + """runs the brute force testing of parameters by + 1-constructing the opamp layout specfied by parameters + 2-extracting the netlist for the opamp + 3-running simulations on the opamp + returns the results from opamp simulations as nparray + """ + if sky130pdk.name != "sky130": + raise ValueError("this is for sky130 only") + # disable adding NPC layer + add_npc_decorator = sky130pdk.default_decorator + sky130pdk.default_decorator = None + sky130pdk.activate() + # initialize empty results array + results = None + # run layout, extraction, sim + save_gds_dir = Path('./save_gds_by_index').resolve() + save_gds_dir.mkdir(parents=True) + # pass pdk as global var to avoid pickling issues + global pdk + pdk = sky130pdk + with Pool(120) as cores: + if saverawsims: + results = np.array(cores.starmap(__run_single_brtfrc, zip(count(0), parameter_list, repeat(save_gds_dir), repeat(temperature_info), repeat(cload), repeat(noparasitics), count(0))),np.float64) + else: + results = np.array(cores.starmap(__run_single_brtfrc, zip(count(0), parameter_list, repeat(save_gds_dir), repeat(temperature_info), repeat(cload), repeat(noparasitics))),np.float64) + # undo pdk modification + sky130pdk.default_decorator = add_npc_decorator + return results + +# data gathering main function +def get_training_data(test_mode: bool=True, temperature_info: tuple[int,str]=(25,"normal model"), cload: float=0.0, noparasitics: bool=False, parameter_array: Optional[np.array]=None, saverawsims=False) -> None: + if temperature_info[1] != "normal model" and temperature_info[1] != "cryo model": + raise ValueError("model must be one of \"normal model\" or \"cryo model\"") + if parameter_array is None: + params = get_small_parameter_list(test_mode) + else: + params = parameter_array + results = brute_force_full_layout_and_PEXsim(pdk, params, temperature_info, cload=cload, noparasitics=noparasitics,saverawsims=saverawsims) + np.save("training_params.npy",params) + np.save("training_results.npy",results) + + + +#util function for pure simulation. sky130 is imported automatically +def single_build_and_simulation(parameters: np.array, temp: int=25, output_dir: Optional[Union[str,Path]] = None, cload: float=0.0, noparasitics: bool=False) -> dict: + """Builds, extract, and simulates a single opamp + saves opamp gds in current directory with name 12345678987654321.gds + returns -987.654321 for all values IF phase margin < 45 + """ + from glayout.pdk.sky130_mapped import sky130_mapped_pdk + # process temperature info + temperature_info = [temp, None] + if temperature_info[0] > -20: + temperature_info[1] = "normal model" + elif temperature_info[0]!=-269: + raise ValueError("simulation temperature should be exactly -269C for cryo sim. Below -20C there are no good models for simulation") + else: + temperature_info[1] = "cryo model" + temperature_info = tuple(temperature_info) + # run single build + save_gds_dir = Path('./').resolve() + index = 12345678987654321 + # pass pdk as global var to avoid pickling issues + global pdk + pdk = sky130_mapped_pdk + results = __run_single_brtfrc(index, parameters, temperature_info=temperature_info, save_gds_dir=save_gds_dir, output_dir=output_dir, cload=cload, noparasitics=noparasitics) + results = opamp_results_de_serializer(results) + if results["phaseMargin"] < 45: + for key in results: + results[key] = -987.654321 + return results + + + + +# ================ stats ================== + + + + + +def save_distwith_best_fit(data, output_file, title="Distribution With Trend", xlabel="Data", ylabel="Distribution"): + """Create a histogram with a line of best fit for the input data and save it as a PNG file. + args: + data (numpy.array): 1D array containing the simulation metrics. + output_file (str): File path to save the generated PNG. + bins (int or str): Number of bins for the histogram or 'auto' for automatic binning (default is 'auto'). + fit_distribution (str): Distribution to fit to the data. Supported options: 'norm' (normal distribution) or 'exponential'. + """ + # Create the histogram + plt.figure() + n, bins, patches = plt.hist(data, bins="auto", density=True, alpha=0.7) + # Fit a normal distribution to the data + mu, sigma = norm.fit(data) + best_fit_line = norm.pdf(bins, mu, sigma) + distribution_label = 'Normal Distribution' + # Add the line of best fit to the plot + plt.plot(bins, best_fit_line, 'r-', label=distribution_label) + # Add labels and legend + plt.xlabel(xlabel) + plt.ylabel(ylabel) + plt.title(title) + plt.legend() + # Save the plot as a PNG file + plt.savefig(output_file) + plt.clf() + +def save_pairwise_scatter_plot(data, output_file): + """Create a Pairwise Scatter Plot for the input data and save it as a PNG file. + args: + data (numpy.array or pandas.DataFrame): + output_file (str/path): File path to save the generated PNG. + """ + # If the data is a NumPy array, convert it to a pandas DataFrame + if isinstance(data, np.ndarray): + data = pd.DataFrame(data) + # Create the Pairwise Scatter Plot + sns.pairplot(data) + # Save the plot as a PNG file + plt.savefig(output_file) + plt.close() + plt.clf() + +def run_pca_and_save_plot(data, output_file): + """Run PCA on the input data and save the PCA plot as a PNG file. + args: + data (numpy.array or pandas.DataFrame): The 17-dimensional input data for PCA. + output_file (str): File path to save the generated PNG. + """ + # If the data is a pandas DataFrame, convert it to a NumPy array + if isinstance(data, pd.DataFrame): + data = data.to_numpy() + # Perform PCA + pca = PCA(n_components=2) # Reduce to 2 dimensions for visualization + pca_result = pca.fit_transform(data) + # Create the biplot + plt.figure(figsize=(10, 8)) + plt.scatter(pca_result[:, 0], pca_result[:, 1], alpha=0.7) + # Plot feature loadings as arrows + feature_vectors = pca.components_.T + for i, (x, y) in enumerate(feature_vectors): + plt.arrow(0, 0, x, y, color='r', alpha=0.5) + plt.text(x, y, f'Feature {i+1}', color='g', ha='center', va='center') + # Add labels and title + plt.xlabel('Principal Component 1') + plt.ylabel('Principal Component 2') + plt.title('PCA Biplot') + # Save the plot as a PNG file + plt.savefig(output_file) + plt.close() + plt.clf() + +def find_optimal_clusters(data, max_clusters=10): + if isinstance(data, pd.DataFrame): + data = data.to_numpy() + results = [] + for num_clusters in range(1, max_clusters + 1): + kmeans = KMeans(n_clusters=num_clusters) + kmeans.fit(data) + inertia = kmeans.inertia_ + results.append((num_clusters, inertia)) + return results + +def elbow_point(x, y): + deltas = np.diff(y) + elbow_index = np.argmax(deltas < np.mean(deltas)) + return x[elbow_index] + +def create_pca_biplot_with_clusters(data, results, output_file, max_clusters=10, results_index: int=0): + if isinstance(data, pd.DataFrame): + data = data.to_numpy() + if isinstance(results, pd.Series): + results = results.to_numpy() + pca = PCA(n_components=2) + pca_result = pca.fit_transform(data) + cluster_results = find_optimal_clusters(data, max_clusters) + num_clusters_values, inertias = zip(*cluster_results) + optimal_num_clusters = elbow_point(num_clusters_values, inertias) + kmeans = KMeans(n_clusters=int(optimal_num_clusters)) + cluster_assignments = kmeans.fit_predict(data) + plt.figure(figsize=(10, 8)) + for i in range(int(optimal_num_clusters)): + cluster_indices = np.where(cluster_assignments == i)[0] + plt.scatter(pca_result[cluster_indices, 0], pca_result[cluster_indices, 1], alpha=0.7, label=f'Cluster {i+1}') + # Color the data points based on their result values + plt.scatter(pca_result[:, 0], pca_result[:, 1], c=results[:,results_index], cmap='viridis', edgecolor='k', s=80) + feature_vectors = pca.components_.T + for i, (x, y) in enumerate(feature_vectors): + plt.arrow(0, 0, x, y, color='r', alpha=0.5) + plt.text(x, y, f'Feature {i+1}', color='g', ha='center', va='center') + plt.xlabel('Principal Component 1') + plt.ylabel('Principal Component 2') + plt.title('PCA Biplot with Clusters') + plt.legend() + plt.colorbar(label='Results') + plt.savefig(output_file) + plt.close() + plt.clf() + +def create_heatmap_with_clusters(parameters, results, output_file, max_clusters=10,results_index: int=0): + if isinstance(parameters, pd.DataFrame): + parameters = parameters.to_numpy() + if isinstance(results, pd.Series): + results = results.to_numpy() + # Perform PCA + pca = PCA(n_components=2) + pca_result = pca.fit_transform(parameters) + # Cluster the parameters based on the results using hierarchical clustering + results_dist = pdist(results[:,results_index].reshape(-1, 1)) # Pairwise distance between result values + results_linkage = squareform(results_dist) # Convert to a condensed distance matrix + clustering = AgglomerativeClustering(n_clusters=max_clusters, affinity='precomputed', linkage='complete') + cluster_assignments = clustering.fit_predict(results_linkage) + # Create a dictionary to map clusters to their corresponding parameters + cluster_param_dict = {} + for cluster_id, param_values in zip(cluster_assignments, parameters): + if cluster_id not in cluster_param_dict: + cluster_param_dict[cluster_id] = [] + cluster_param_dict[cluster_id].append(param_values) + # Calculate the mean parameter values for each cluster + cluster_means = [np.mean(cluster_param_dict[cluster_id], axis=0) for cluster_id in range(max_clusters)] + # Create the heatmap + plt.figure(figsize=(10, 8)) + sns.heatmap(cluster_means, cmap='YlGnBu', annot=True, fmt='.2f', xticklabels=False, + yticklabels=False, cbar_kws={'label': 'Mean Parameter Value'}) + plt.xlabel('Parameter Clusters') + plt.ylabel('Parameter Clusters') + plt.title('Heatmap Clusters') + plt.savefig(output_file) + plt.close() + plt.clf() + +def find_indices_with_same_other_params(data, parameter_index, other_params_values): + mask = np.ones(len(data), dtype=bool) + num_params = data.shape[1] + for param_idx in range(num_params): + if param_idx == parameter_index: + continue + mask = mask & (data[:, param_idx] == other_params_values[param_idx]) + return np.where(mask)[0] + +def single_param_scatter(data: np.array, results: np.array, col_to_isolate: int, output_file: str, isolate: bool=True,results_index: int=0, trend:bool=True): + output_file = Path(output_file).resolve() + example_others = data[0, :] + if isolate: + indices = find_indices_with_same_other_params(data, col_to_isolate, example_others) + x = data[indices, col_to_isolate] + y = results[:,results_index][indices] + else: + x = data[:, col_to_isolate] + y = results[:,results_index] + plt.scatter(x, y, marker='o', s=50, label="Data Points") + # Fit a quadratic regression model to the data + coeffs = np.polyfit(x, y, deg=2) + # Generate points for the quadratic trend line + if trend: + quadratic_function = lambda x, a, b, c: a * x**2 + b * x + c + trend_line_x = np.linspace(min(x), max(x), 1000) + trend_line_y = quadratic_function(trend_line_x, *coeffs) + # Plot the quadratic trend line + plt.plot(trend_line_x, trend_line_y, color='red')#, label="Quadratic Trend Line") + # label and return + plt.xlabel(output_file.stem) + plt.ylabel("Normalized Performance Score") + plt.title("Performance vs Parameter="+output_file.stem) + plt.legend() + plt.grid(True) + plt.savefig(output_file) + plt.clf() + +def simple2pt_param_scatter(x: np.array, y: np.array, output_file: str, x_label: str,y_label:str, trend:bool=True): + output_file = Path(output_file).resolve() + plt.scatter(x, y, marker='o', s=50, label="Data Points") + # Fit a quadratic regression model to the data + coeffs = np.polyfit(x, y, deg=2) + # Generate points for the quadratic trend line + if trend: + quadratic_function = lambda x, a, b, c: a * x**2 + b * x + c + trend_line_x = np.linspace(min(x), max(x), 1000) + trend_line_y = quadratic_function(trend_line_x, *coeffs) + # Plot the quadratic trend line + plt.plot(trend_line_x, trend_line_y, color='red')#, label="Quadratic Trend Line") + # label and return + plt.xlabel(x_label) + plt.ylabel(y_label) + plt.title(output_file.stem) + plt.legend() + plt.grid(True) + plt.savefig(output_file) + plt.clf() + +def find_optimal_num_clusters(data, max_clusters=10): + wcss = [] + for num_clusters in range(1, max_clusters+1): + kmeans = KMeans(n_clusters=num_clusters) + kmeans.fit(data) + wcss.append(kmeans.inertia_) # Inertia is the WCSS value + + # Find the optimal number of clusters using the elbow method + optimal_num_clusters = np.argmin(np.diff(wcss)) + 1 + + return optimal_num_clusters + +def simple2pt_param_scatter_wautocluster(x, y, output_file, x_label='X Axis', y_label='Y Axis', max_clusters=10): + output_file = Path(output_file).resolve() + # Create a scatter plot + plt.figure(figsize=(8, 6)) + plt.scatter(x, y, c='blue', label='Data Points', edgecolors='black') + # Combine data into a 2D array for clustering + data = np.column_stack((x, y)) + # Find the optimal number of clusters using the elbow method + optimal_num_clusters = find_optimal_num_clusters(data, max_clusters) + # Perform K-means clustering with the determined number of clusters + kmeans = KMeans(n_clusters=optimal_num_clusters) + kmeans.fit(data) + cluster_centers = kmeans.cluster_centers_ + cluster_labels = kmeans.labels_ + # Color code the clusters + unique_labels = np.unique(cluster_labels) + colors = plt.cm.tab10.colors + for i, label in enumerate(unique_labels): + cluster_data = data[cluster_labels == label] + cluster_center = cluster_centers[label] + plt.scatter(cluster_data[:, 0], cluster_data[:, 1], c=colors[i], label=f'Cluster {label}', edgecolors='black') + plt.scatter(cluster_center[0], cluster_center[1], marker='x', s=100, c=colors[i], edgecolors='black') + plt.xlabel(x_label) + plt.ylabel(y_label) + plt.title(output_file.stem) + plt.legend() + plt.grid(True) + # Save the plot as PNG if output_file is provided + plt.savefig(output_file, dpi=300, bbox_inches='tight') + plt.clf() + + +def simple2pt_param_scatter_wcluster(x, y, output_file, x_label='X Axis', y_label='Y Axis', num_clusters=3): + output_file = Path(output_file).resolve() + # Create a scatter plot + plt.figure(figsize=(8, 6)) + plt.scatter(x, y, c='blue', label='Data Points', edgecolors='black') + # Combine data into a 2D array for clustering + data = np.column_stack((x, y)) + # Perform K-means clustering with the specified number of clusters + kmeans = KMeans(n_clusters=num_clusters) + cluster_labels = kmeans.fit_predict(data) + # Color code the clusters and label each point + unique_labels = np.unique(cluster_labels) + colors = plt.cm.tab10.colors + for i, label in enumerate(unique_labels): + cluster_data = data[cluster_labels == label] + plt.scatter(cluster_data[:, 0], cluster_data[:, 1], c=colors[i], label=f'Cluster {label}', edgecolors='black') + for point in cluster_data: + plt.text(point[0], point[1], f'{label}', fontsize=10, ha='center', va='center', color='black') + plt.xlabel(x_label) + plt.ylabel(y_label) + plt.title(output_file.stem) + #plt.legend() + plt.grid(True) + # Save the plot as PNG if output_file is provided + plt.savefig(output_file, dpi=300, bbox_inches='tight') + plt.clf() + + +def extract_stats( + params: Union[np.array,str,Path], + results: Union[np.array,str,Path], +) -> None: + # reading files, error checks + strtopath = lambda strin : Path(strin).resolve() if isinstance(strin,str) else strin + pathtoarr = lambda datain : np.load(datain.resolve()) if isinstance(datain,Path) else datain + params_dirty = pathtoarr(strtopath(params)) + results_dirty = pathtoarr(strtopath(results)) + # clean condition eliminates all failed runs AND negative phase margins + clean_condition = np.where(np.all(results_dirty > 0,axis=1)==True) + params = params_dirty[clean_condition] + results = results_dirty[clean_condition] + if len(params)!=len(results): + raise ValueError("expect both results and params to be same length") + # construct dictionary key=colnames: vals=1D np arrays + col_struct = opamp_parameters_de_serializer() + colnames_vals = dict() + for key, val in col_struct.items(): + if type(val)==tuple: + if len(val)==3: + colnames = ["width","length","fingers"] + elif len(val)==4: + colnames = ["width","length","fingers","multipliers"] + elif len(val)==2: + colnames = ["width","length"] + for colname in colnames: + colnames_vals[key+"_"+colname] = "place_holder" + elif type(val)==int: + colnames_vals[key] = "place_holder" + for i, colname in enumerate(colnames_vals): + colnames_vals[colname] = params[:, i] + + # run statistics on distribution of training parameters individually + params_stats_hists = Path("./stats/param_stats/hists1D") + params_stats_hists.mkdir(parents=True) + for colname, val in colnames_vals.items(): + save_distwith_best_fit(val,str(params_stats_hists)+"/"+colname+".png",'Parameter Distribution',colname,'Normalized trials') + # run stats on distribution of training parameters using pair scatter plots + params_stats_scatter = Path("./stats/param_stats/scatter") + params_stats_scatter.mkdir(parents=True) + save_pairwise_scatter_plot(params,str(params_stats_scatter)+"/pairscatter_params.png") + # run PCA on training parameters + run_pca_and_save_plot(params,str(params_stats_scatter)+"/PCA_params.png") + + # run statistics on results + result_stats_dir = Path("./stats/result_stats/hist1d") + result_stats_dir.mkdir(parents=True) + for i,name in enumerate(opamp_results_de_serializer()): + save_distwith_best_fit(results[:,i],str(result_stats_dir)+"/result_"+name+"_dist.png",name+" Distribution",name) + # plot results against each other + result_stats_verses = Path("./stats/result_stats/compare") + result_stats_verses.mkdir(parents=True) + result_combs=list(opamp_results_de_serializer().keys()) + result_unqiue_combs=np.array(np.meshgrid(result_combs, result_combs)).T.reshape(-1, 2) + for name1, name2 in result_unqiue_combs: + if name1==name2: + continue + index1 = result_combs.index(name1) + index2 = result_combs.index(name2) + output_name = str(result_stats_verses)+"/"+name1+"_vs_"+name2+".png" + simple2pt_param_scatter_wcluster(results[:,index1],results[:,index2],output_name,name1,name2) + + # run stats on results and data combined + comb_stats_dir = Path("./stats/combined") + comb_stats_dir.mkdir(parents=True) + create_pca_biplot_with_clusters(params,results,str(comb_stats_dir)+"/heatmapresults_params.png") + create_heatmap_with_clusters(params,results,str(comb_stats_dir)+"/heatmap_results_clustered.png") + for i, name in enumerate(opamp_results_de_serializer()): + param_stats_isolate = Path("./stats/combined/isolate_params") / name + param_stats_isolate.mkdir(parents=True) + param_stats_NOisolate = Path("./stats/combined/NONisolated_params") / name + param_stats_NOisolate.mkdir(parents=True) + for j, colname in enumerate(colnames_vals): + single_param_scatter(params,results,j,str(param_stats_isolate)+"/"+colname+".png",results_index=i) + single_param_scatter(params,results,j,str(param_stats_NOisolate)+"/"+colname+".png",isolate=False,results_index=i) + + + + + +# ================ create opamp matrix ================== + + + +def create_opamp_matrix(save_dir_name: str, params: np.array, results: Optional[np.array] = None, indices: Optional[list]=None): + """create opamps with pads from the np array of opamp parameters + args: + save_dir_name = name of directory to save gds array and text description into + params = 2d list-like container (list, np.array, tuple, etc.) where each row is in the same form as opamp_parameters_serializer + results = (Optional) 2d list-like container (list, np.array, tuple, etc.) where each row is in the same form as opamp_results_serializer + ****NOTE: if results is not specfied, the stats.txt will not list the sim results for each opamp + indices = (Optional) an iterable of integers where each integer represent an index into the params and results lists + """ + # arg setup + current_setting = pdk.cell_decorator_settings.cache + pdk.cell_decorator_settings.cache = False + comps = list() + if indices is None: + indices = range(len(params)) + # dir setup + save_dir = Path(save_dir_name).resolve() + save_dir.mkdir(parents=True,exist_ok=True) + # run opamps + for index in indices: + # create opamp + comp = sky130_opamp_add_pads(opamp(pdk, **opamp_parameters_de_serializer(params[index])), flatten=False) + comp = component_snap_to_grid(comp) + comp.name = "opamp_" + str(index) + # append to list + comps.append(comp) + clear_cache() + with open(str(save_dir)+"/stats.txt","a") as resfile: + strtowrite = "\n-------------------------\nopamp_"+str(index) + strtowrite += "\nparams = " + str(opamp_parameters_de_serializer(params[index])) + if results is not None: + strtowrite += "\n\nresults = " + str(opamp_results_de_serializer(results[index])) + strtowrite += "\n\n\n" + resfile.write(strtowrite) + write_opamp_matrix(comps, write_name = str(save_dir) + "/opamp_matrix.gds", xspace=600) + pdk.cell_decorator_settings.cache = current_setting + + + + + +if __name__ == "__main__": + import time + start_watch = time.time() + + parser = argparse.ArgumentParser(description="sky130 nist tapeout sample, RL generation, and statistics utility.") + subparsers = parser.add_subparsers(title="mode", required=True, dest="mode") + + # Subparser for extract_stats mode + extract_stats_parser = subparsers.add_parser("extract_stats", help="Run the extract_stats function.") + extract_stats_parser.add_argument("-p", "--params", default="training_params.npy", help="File path for params (default: training_params.npy)") + extract_stats_parser.add_argument("-r", "--results", default="training_results.npy", help="File path for results (default: training_results.npy)") + + # Subparser for get_training_data mode + get_training_data_parser = subparsers.add_parser("get_training_data", help="Run the get_training_data function.") + get_training_data_parser.add_argument("-t", "--test-mode", action="store_true", help="Set test_mode to True (default: False)") + get_training_data_parser.add_argument("--temp", type=int, default=int(25), help="Simulation temperature") + get_training_data_parser.add_argument("--cload", type=float, default=float(0), help="run simulation with load capacitance units=pico Farads") + get_training_data_parser.add_argument("--noparasitics",action="store_true",help="specify that parasitics should be removed when simulating") + get_training_data_parser.add_argument("--nparray",default=None,help="overrides the test parameters and takes the ones you provide (file path to .npy file).\n\tMUST HAVE LEN > 1") + get_training_data_parser.add_argument("--saverawsims",action="store_true",help="specify that the raw simulation directories should be saved (default saved under save_gds_by_index/...)") + get_training_data_parser.add_argument("--get_tset_len",action="store_true",help="print the length of the default parameter set and quit") + get_training_data_parser.add_argument("--output_second_stage",action="store_true",help="measure relevant sim metrics at the output of the second stage rather than output of third stage") + + # Subparser for gen_opamp mode + gen_opamp_parser = subparsers.add_parser("gen_opamp", help="Run the gen_opamp function. optional parameters for transistors are width,length,fingers,mults") + gen_opamp_parser.add_argument("--diffpair_params", nargs=3, type=float, default=[6, 1, 4], help="diffpair_params (default: 6 1 4)") + gen_opamp_parser.add_argument("--diffpair_bias", nargs=3, type=float, default=[6, 2, 4], help="diffpair_bias (default: 6 2 4)") + gen_opamp_parser.add_argument("--half_common_source_params", nargs=4, type=float, default=[7, 1, 10, 3], help="half_common_source_params (default: 7 1 10 3)") + gen_opamp_parser.add_argument("--half_common_source_bias", nargs=4, type=float, default=[6, 2, 8, 2], help="half_common_source_bias (default: 6 2 8 3)") + gen_opamp_parser.add_argument("--output_stage_params", nargs=3, type=float, default=[5, 1, 16], help="pamp_hparams (default: 5 1 16)") + gen_opamp_parser.add_argument("--output_stage_bias", nargs=3, type=float, default=[6,2,4], help="pamp_hparams (default: 6 2 4)") + gen_opamp_parser.add_argument("--mim_cap_size", nargs=2, type=int, default=[12, 12], help="mim_cap_size (default: 12 12)") + gen_opamp_parser.add_argument("--mim_cap_rows", type=int, default=3, help="mim_cap_rows (default: 3)") + gen_opamp_parser.add_argument("--rmult", type=int, default=2, help="rmult (default: 2)") + gen_opamp_parser.add_argument("--add_pads",action="store_true" , help="add pads (gen_opamp mode only)") + gen_opamp_parser.add_argument("--output_gds", help="Filename for outputing opamp (gen_opamp mode only)") + + # subparser for gen_opamps mode + gen_opamps_parser = subparsers.add_parser("gen_opamps", help="generates the opamps returned in the small parameters list but only saves GDS and does not add pads. always outputs to ./outputrawopamps") + gen_opamps_parser.add_argument("--pdk", help="specify sky130 or gf180 pdk") + + # subparse for testing mode (create opamp and run sims) + test = subparsers.add_parser("test", help="Test mode") + test.add_argument("--output_dir", type=Path, default="./", help="Directory for output GDS file") + test.add_argument("--temp", type=int, default=int(25), help="Simulation temperature") + test.add_argument("--cload", type=float, default=float(0), help="run simulation with load capacitance units=pico Farads") + test.add_argument("--noparasitics",action="store_true",help="specify that parasitics should be removed when simulating") + test.add_argument("--output_second_stage",action="store_true",help="measure relevant sim metrics at the output of the second stage rather than output of third stage") + + # Subparser for create_opamp_matrix mode + create_opamp_matrix_parser = subparsers.add_parser("create_opamp_matrix", help="create a matrix of opamps") + create_opamp_matrix_parser.add_argument("-p", "--params", default="params.npy", help="File path for params (default: params.npy)") + create_opamp_matrix_parser.add_argument("-r", "--results", help="Optional File path for results") + create_opamp_matrix_parser.add_argument("--indices", type=int, nargs="+", help="list of int indices to pick from the opamp param.npy and add to the matrix (default: the entire params list)") + create_opamp_matrix_parser.add_argument("--output_dir", type=Path, default="./opampmatrix", help="Directory for output files (default: ./opampmatrix)") + + args = parser.parse_args() + + # Simulation Temperature information + if vars(args).get("temp") is not None: + temperature_info = [args.temp, None] + if temperature_info[0] > -20: + temperature_info[1] = "normal model" + elif temperature_info[0]!=-269: + raise ValueError("simulation temperature should be exactly -269C for cryo sim. Below -20C there are no good models for simulation") + else: + temperature_info[1] = "cryo model" + temperature_info = tuple(temperature_info) + + if args.mode=="extract_stats": + # Call the extract_stats function with the specified file paths or defaults + extract_stats(params=args.params, results=args.results) + + elif args.mode=="get_training_data": + if args.get_tset_len: + _GET_PARAM_SET_LENGTH_ = True + if args.output_second_stage: + _TAKE_OUTPUT_AT_SECOND_STAGE_ = True + # Call the get_training_data function with test_mode flag + parameter_array = None + if args.nparray is not None: + parameter_array = Path(args.nparray).resolve() + assert(parameter_array.is_file()) + parameter_array = np.load(parameter_array) + get_training_data(test_mode=args.test_mode, temperature_info=temperature_info, cload=args.cload, noparasitics=args.noparasitics, parameter_array=parameter_array, saverawsims=args.saverawsims) + + elif args.mode=="gen_opamp": + # Call the opamp function with the parsed arguments + opamp_comp = opamp(pdk=pdk, + diffpair_params=tuple(args.diffpair_params), + diffpair_bias=tuple(args.diffpair_bias), + half_common_source_bias=tuple(args.half_common_source_bias), + half_common_source_params=tuple(args.half_common_source_params), + output_stage_params = tuple(args.output_stage_params), + output_stage_bias = tuple(args.output_stage_bias), + mim_cap_size=tuple(args.mim_cap_size), + mim_cap_rows=args.mim_cap_rows, + rmult=args.rmult, + ) + opamp_comp = sky130_add_lvt_layer(opamp_comp) + if args.add_pads: + opamp_comp_labels = sky130_add_opamp_labels(opamp_comp) + opamp_comp_final = sky130_opamp_add_pads(opamp_comp_labels) + else: + opamp_comp_final = opamp_comp + opamp_comp_final.show() + if args.output_gds: + opamp_comp_final.write_gds(args.output_gds) + + elif args.mode == "test": + if args.output_second_stage: + _TAKE_OUTPUT_AT_SECOND_STAGE_ = True + params = { + "diffpair_params": (6, 1, 4), + "diffpair_bias": (6, 2, 4), + "half_common_source_bias": (6, 2, 8, 3), + "half_common_source_params": (7, 1, 10, 3), + "output_stage_params": (5, 1, 16), + "output_stage_bias": (6, 2, 4), + "mim_cap_size": (12, 12), + "mim_cap_rows": 3, + "rmult": 2 + } + results = single_build_and_simulation(opamp_parameters_serializer(**params), temperature_info[0], args.output_dir, cload=args.cload, noparasitics=args.noparasitics) + print(results) + + elif args.mode =="create_opamp_matrix": + params = Path(args.params).resolve() + params = np.load(str(params)) + results = Path(args.results).resolve() if args.results else None + results = np.load(str(results)) if results else None + if args.indices is not None: + indices = args.indices if isinstance(args.indices, Iterable) else [args.indices] + else: + indices = None + create_opamp_matrix(args.output_dir,params,results,indices) + + + elif args.mode == "gen_opamps": + global usepdk + if args.pdk[0].lower()=="g": + from glayout.pdk.gf180_mapped import gf180_mapped_pdk + usepdk = gf180_mapped_pdk + else: + usepdk = pdk + output_path = Path("./outputrawopamps").resolve() + output_path.mkdir() + def create_func(argnparray, indx: int): + global usepdk + comp = opamp(usepdk,**opamp_parameters_de_serializer(argnparray)) + comp.write_gds("./outputrawopamps/amp"+str(indx)+".gds") + + argnparray = get_small_parameter_list() + with Pool(120) as cores: + cores.starmap(create_func, zip(argnparray,count(0))) + + end_watch = time.time() + print("\ntotal runtime was "+str((end_watch-start_watch)/3600) + " hours\n") diff --git a/openfasoc/generators/ldo-gen/flow/scripts/openfasoc/custom_place.tcl b/openfasoc/generators/ldo-gen/flow/scripts/openfasoc/custom_place.tcl index 2d27bf916..966e77c4f 100644 --- a/openfasoc/generators/ldo-gen/flow/scripts/openfasoc/custom_place.tcl +++ b/openfasoc/generators/ldo-gen/flow/scripts/openfasoc/custom_place.tcl @@ -19,7 +19,7 @@ proc place_pt_unit {instances_list place_limit} { set instname [lindex $line 1] set pt [ $block findInst $instname] set orient [$pt getOrient] - + if {$orient == "R0" && $x_R1<$place_limit} { place_cell -inst_name [lindex $line 0] -orient R0 -origin [list $x_R1 $y_R1] -status PLACED set x_R1 [expr $x_R1 + 2.40] @@ -27,8 +27,8 @@ proc place_pt_unit {instances_list place_limit} { place_cell -inst_name [lindex $line 0] -orient R0 -origin [list $x_R2 $y_R2] -status PLACED set x_R2 [expr $x_R2 + 2.40] } else { - place_cell -inst_name [lindex $line 0] -orient R0 -origin [list $x_R3 $y_R3] -status PLACED - set x_R3 [expr $x_R3 + 2.40] + place_cell -inst_name [lindex $line 0] -orient R0 -origin [list $x_R3 $y_R3] -status PLACED + set x_R3 [expr $x_R3 + 2.40] } } close $ch diff --git a/openfasoc/generators/ldo-gen/tools/generate_verilog.py b/openfasoc/generators/ldo-gen/tools/generate_verilog.py index fa7489eb7..193cb884e 100644 --- a/openfasoc/generators/ldo-gen/tools/generate_verilog.py +++ b/openfasoc/generators/ldo-gen/tools/generate_verilog.py @@ -34,7 +34,13 @@ def update_ldo_place_insts(blocksDir, arrSize): with open(blocksDir + "/ldo_place.txt", "w") as ldo_place_insts: # write arrSize pt cells for i in range(arrSize): - ldo_place_insts.write("{pt_array_unit\\\["+ str(i) +"\\\]} {pt_array_unit\[" + str(i) + "\]}\n") + ldo_place_insts.write( + "{pt_array_unit\\\[" + + str(i) + + "\\\]} {pt_array_unit\[" + + str(i) + + "\]}\n" + ) def update_custom_nets(blocksDir, arrSize): diff --git a/openfasoc/generators/ldo-gen/tools/ldo-gen.py b/openfasoc/generators/ldo-gen/tools/ldo-gen.py index d75aaa8fb..a6f916438 100644 --- a/openfasoc/generators/ldo-gen/tools/ldo-gen.py +++ b/openfasoc/generators/ldo-gen/tools/ldo-gen.py @@ -43,7 +43,11 @@ "--arr_size_in", help="Debug option to manually set power arr size." ) parser.add_argument("--clean", action="store_true", help="Clean the workspace.") -parser.add_argument("--simtype",choices=["postPEX","prePEX"], help="Simulations type prePEX or postPEX") +parser.add_argument( + "--simtype", + choices=["postPEX", "prePEX"], + help="Simulations type prePEX or postPEX", +) parser.add_argument("--pex", help="enable postPEX along with prePEX") args = parser.parse_args() @@ -100,7 +104,7 @@ # Update the ldo_domain_insts.txt as per power transistor array size update_ldo_domain_insts(directories["blocksDir"], arrSize) -#Update the ldo_place.txt as per power transistor array size +# Update the ldo_place.txt as per power transistor array size update_ldo_place_insts(directories["blocksDir"], arrSize) # Update connections to VREG update_custom_nets(directories["blocksDir"], arrSize) @@ -185,7 +189,7 @@ rawPEXPath = spice_dir + user_specs["designName"] + "_pex.spice" rawSynthPath = spice_dir + user_specs["designName"] + ".spice" [processedPEXnetlist, head] = process_PEX_netlist( - rawPEXPath, jsonConfig["simTool"],user_specs["designName"] + rawPEXPath, jsonConfig["simTool"], user_specs["designName"] ) processedSynthNetlist = process_prePEX_netlist(rawSynthPath) powerArrayNetlist = process_power_array_netlist(rawSynthPath) @@ -202,64 +206,64 @@ # prepare simulation scripts, passing prePEX_sim_dir and pex=false to function *_prepare_scripts() runs preprex sims # there should be one output file name specified for each cap value. outputs sent to sim_dir_structure directories if args.simtype == "postPEX" or args.pex == "True": - if jsonConfig["simTool"] == "ngspice": - [sim, output_file_names] = ngspice_prepare_scripts( - head, - cap_list, - directories["simDir"] + "/templates/", - postPEX_sim_dir, - user_specs, - arrSize, - pdk_path, - freq_list, - "tt", - pex=True - ) - elif jsonConfig["simTool"] == "Xyce": - [sim, output_file_names] = xyce_prepare_scripts( - head, - cap_list, - directories["simDir"] + "/templates/", - postPEX_sim_dir, - user_specs, - arrSize, - pdk_path, - freq_list, - "tt", - pex=True - ) - else: + if jsonConfig["simTool"] == "ngspice": + [sim, output_file_names] = ngspice_prepare_scripts( + head, + cap_list, + directories["simDir"] + "/templates/", + postPEX_sim_dir, + user_specs, + arrSize, + pdk_path, + freq_list, + "tt", + pex=True, + ) + elif jsonConfig["simTool"] == "Xyce": + [sim, output_file_names] = xyce_prepare_scripts( + head, + cap_list, + directories["simDir"] + "/templates/", + postPEX_sim_dir, + user_specs, + arrSize, + pdk_path, + freq_list, + "tt", + pex=True, + ) + else: print("simtool not supported") exit(1) if args.simtype == "prePEX": - if jsonConfig["simTool"] == "ngspice": - [sim, output_file_names] = ngspice_prepare_scripts( - head, - cap_list, - directories["simDir"] + "/templates/", - prePEX_sim_dir, - user_specs, - arrSize, - pdk_path, - freq_list, - "tt", - pex=False - ) - elif jsonConfig["simTool"] == "Xyce": - [sim, output_file_names] = xyce_prepare_scripts( - head, - cap_list, - directories["simDir"] + "/templates/", - prePEX_sim_dir, - user_specs, - arrSize, - pdk_path, - freq_list, - "tt", - pex=False - ) - else: + if jsonConfig["simTool"] == "ngspice": + [sim, output_file_names] = ngspice_prepare_scripts( + head, + cap_list, + directories["simDir"] + "/templates/", + prePEX_sim_dir, + user_specs, + arrSize, + pdk_path, + freq_list, + "tt", + pex=False, + ) + elif jsonConfig["simTool"] == "Xyce": + [sim, output_file_names] = xyce_prepare_scripts( + head, + cap_list, + directories["simDir"] + "/templates/", + prePEX_sim_dir, + user_specs, + arrSize, + pdk_path, + freq_list, + "tt", + pex=False, + ) + else: print("simtool not supported") exit(1) @@ -276,7 +280,7 @@ print("#----------------------------------------------------------------------") # run sims processes = [] - #assert len(output_file_names) == len(cap_list)*len(freq_list) + # assert len(output_file_names) == len(cap_list)*len(freq_list) if args.mode != "post": if args.simtype == "postPEX" or args.pex == "True": run_dir = directories["genDir"] + "tools/" @@ -341,4 +345,4 @@ max_load = user_specs["imax"] load = max_load*1000 fig_load_change_results(prePEX_sim_dir + "/" + str(load) + "mA_output_load_change.raw",load).savefig(args.outputDir + "/load_change.png") - """ \ No newline at end of file + """ diff --git a/openfasoc/generators/ldo-gen/tools/processing.py b/openfasoc/generators/ldo-gen/tools/processing.py index 79980b2ca..2236dba77 100644 --- a/openfasoc/generators/ldo-gen/tools/processing.py +++ b/openfasoc/generators/ldo-gen/tools/processing.py @@ -17,12 +17,12 @@ from simulations import * parser = argparse.ArgumentParser(description="processing simulations") -parser.add_argument("--file_path","-f", help="sim path") -parser.add_argument("--vref","-v", help="vrefspec") -parser.add_argument("--iload","-i", help="iloadspec") -parser.add_argument("--odir","-od", help="output dir") -parser.add_argument("--figs","-fg", help="figures") -parser.add_argument("--simType","-sim", help="simulations Type") +parser.add_argument("--file_path", "-f", help="sim path") +parser.add_argument("--vref", "-v", help="vrefspec") +parser.add_argument("--iload", "-i", help="iloadspec") +parser.add_argument("--odir", "-od", help="output dir") +parser.add_argument("--figs", "-fg", help="figures") +parser.add_argument("--simType", "-sim", help="simulations Type") args = parser.parse_args() @@ -33,7 +33,7 @@ odir = args.odir simtype = args.simType -ext = ('.raw',) +ext = (".raw",) for files in os.scandir(sim_dir): if files.path.endswith(ext) and "cap" in files.name: output_file_names.append(files.name) @@ -41,16 +41,22 @@ def fig_VREG_results(raw_files, vrefspec): """Create VREG output plots for all caps at particular freq simulations""" - figureVREG, axesVREG = plt.subplots(len(raw_files),figsize=(30, 15)) - figureVDIF, axesVDIF = plt.subplots(len(raw_files),figsize=(30, 15)) - figureRIPL, axesRIPL = plt.subplots(len(raw_files),figsize=(30, 15)) - #len(axesVREG) # checks that axes can be indexed - figureVREG.text(0.5, 0.04, "Time [us]", ha="center",fontsize ='large') - figureVREG.text(0.04, 0.5, "Vreg and Vref [V]", va="center", rotation="vertical",fontsize =15) - figureVDIF.text(0.5, 0.04, "Time [us]", ha="center",fontsize ='large') - figureVDIF.text(0.04, 0.5, "Vref-Vreg [V]", va="center", rotation="vertical",fontsize =15) - figureRIPL.text(0.5, 0.04, "Time [us]", ha="center",fontsize ='large') - figureRIPL.text(0.04, 0.5, "V_ripple [V]", va="center", rotation="vertical",fontsize =15) + figureVREG, axesVREG = plt.subplots(len(raw_files), figsize=(30, 15)) + figureVDIF, axesVDIF = plt.subplots(len(raw_files), figsize=(30, 15)) + figureRIPL, axesRIPL = plt.subplots(len(raw_files), figsize=(30, 15)) + # len(axesVREG) # checks that axes can be indexed + figureVREG.text(0.5, 0.04, "Time [us]", ha="center", fontsize="large") + figureVREG.text( + 0.04, 0.5, "Vreg and Vref [V]", va="center", rotation="vertical", fontsize=15 + ) + figureVDIF.text(0.5, 0.04, "Time [us]", ha="center", fontsize="large") + figureVDIF.text( + 0.04, 0.5, "Vref-Vreg [V]", va="center", rotation="vertical", fontsize=15 + ) + figureRIPL.text(0.5, 0.04, "Time [us]", ha="center", fontsize="large") + figureRIPL.text( + 0.04, 0.5, "V_ripple [V]", va="center", rotation="vertical", fontsize=15 + ) for i, raw_file in enumerate(raw_files): cap_id = str(raw_file).split("/")[-1].split("_")[2] + " " freq_id = str(raw_file).split("/")[-1].split("_")[1] + " " @@ -62,12 +68,12 @@ def fig_VREG_results(raw_files, vrefspec): axesVREG[i].ticklabel_format(style="sci", axis="x", scilimits=(-6, -6)) axesVREG[i].plot(time, VREG) axesVREG[i].plot(time, VREF) - axesVDIF[i].set_title("V_difference vs Time " + cap_id + freq_id,fontsize=15) + axesVDIF[i].set_title("V_difference vs Time " + cap_id + freq_id, fontsize=15) axesVDIF[i].ticklabel_format(style="sci", axis="x", scilimits=(-6, -6)) axesVDIF[i].plot(time, VREF - VREG) VREG_sample_dev = VREG[100 + np.where(VREG[100:] >= vrefspec)[0][0] :] time_sample_dev = time[100 + np.where(VREG[100:] >= vrefspec)[0][0] :] - axesRIPL[i].set_title("V_ripple vs Time " + cap_id + freq_id,fontsize=15) + axesRIPL[i].set_title("V_ripple vs Time " + cap_id + freq_id, fontsize=15) axesRIPL[i].ticklabel_format(style="sci", axis="x", scilimits=(-6, -6)) axesRIPL[i].plot(time_sample_dev, VREG_sample_dev) return [figureVREG, figureVDIF, figureRIPL] @@ -75,10 +81,10 @@ def fig_VREG_results(raw_files, vrefspec): def fig_comparator_results(raw_files): """Create cmp_out output plots for all caps at particular freq simulations""" - figure, axes = plt.subplots(len(raw_files),figsize=(30, 15)) + figure, axes = plt.subplots(len(raw_files), figsize=(30, 15)) len(axes) # checks that axes can be indexed - figure.text(0.5, 0.04, "Time [us]", ha="center",fontsize ='large') - figure.text(0.04, 0.5, "Cmp_out [V]", va="center", rotation="vertical",fontsize =15) + figure.text(0.5, 0.04, "Time [us]", ha="center", fontsize="large") + figure.text(0.04, 0.5, "Cmp_out [V]", va="center", rotation="vertical", fontsize=15) for i, raw_file in enumerate(raw_files): data = ltspice.Ltspice(raw_file) data.parse() @@ -86,7 +92,7 @@ def fig_comparator_results(raw_files): freq_id = str(raw_file).split("/")[-1].split("_")[1] + " " time = data.get_time() cmp_out = data.get_data("v(cmp_out)") - axes[i].set_title("Comp_out vs Time " + cap_id + freq_id,fontsize=15) + axes[i].set_title("Comp_out vs Time " + cap_id + freq_id, fontsize=15) axes[i].ticklabel_format(style="sci", axis="x", scilimits=(-6, -6)) axes[i].plot(time, cmp_out) return figure @@ -94,10 +100,12 @@ def fig_comparator_results(raw_files): def fig_controller_results(raw_files): """Create controller output plots for all caps at particular freq simulations""" - figure, axes = plt.subplots(len(raw_files),figsize=(30, 15)) + figure, axes = plt.subplots(len(raw_files), figsize=(30, 15)) len(axes) # checks that axes can be indexed - figure.text(0.5, 0.04, "Time [us]", ha="center",fontsize ='large') - figure.text(0.04, 0.5, "Active Switches", va="center", rotation="vertical",fontsize =15) + figure.text(0.5, 0.04, "Time [us]", ha="center", fontsize="large") + figure.text( + 0.04, 0.5, "Active Switches", va="center", rotation="vertical", fontsize=15 + ) for i, raw_file in enumerate(raw_files): data = ltspice.Ltspice(raw_file) data.parse() @@ -112,7 +120,7 @@ def fig_controller_results(raw_files): active_switches = (np.rint(active_switches / 3.3)).astype(int)[100:] num_smooth_pts = np.linspace(time.min(), time.max(), 250) active_switches = make_interp_spline(time, active_switches)(num_smooth_pts) - axes[i].set_title("Active Switches vs Time " + cap_id + freq_id,fontsize=15) + axes[i].set_title("Active Switches vs Time " + cap_id + freq_id, fontsize=15) axes[i].ticklabel_format(style="sci", axis="x", scilimits=(-6, -6)) axes[i].plot(num_smooth_pts, active_switches) return figure @@ -147,7 +155,8 @@ def fig_dc_results(raw_file): axes.legend(loc="lower left") return figure -def fig_load_change_results(raw_file,load): + +def fig_load_change_results(raw_file, load): figure, axes = plt.subplots(1, sharex=True, sharey=True) figure.text(0.5, 0.04, "Time [us]", ha="center") figure.text( @@ -161,19 +170,21 @@ def fig_load_change_results(raw_file,load): data.parse() VREG = data.get_data("v(vreg)") Time = data.get_time() - axes.set_title("Load change sim from 1mA to "+ str(load)+ "mA") + axes.set_title("Load change sim from 1mA to " + str(load) + "mA") axes.ticklabel_format(style="sci", axis="x", scilimits=(-6, -6)) axes.plot(Time, VREG) return figure -def raw_to_csv(raw_files, vrefspec,odir): + + +def raw_to_csv(raw_files, vrefspec, odir): time_settle = [] vripple = [] freq = [] cap = [] load = [] - csv1 = odir + "/"+simtype+ "/csv_data" - os.system("mkdir -p "+csv1) - for i,raw_file in enumerate(raw_files): + csv1 = odir + "/" + simtype + "/csv_data" + os.system("mkdir -p " + csv1) + for i, raw_file in enumerate(raw_files): data = ltspice.Ltspice(raw_file) data.parse() VREG = data.get_data("v(vreg)") @@ -190,16 +201,27 @@ def raw_to_csv(raw_files, vrefspec,odir): VREG_sample_dev = VREG[100 + np.where(VREG[100:] >= vrefspec)[0][0] :] VREG_min = min(VREG_sample_dev) VREG_max = max(VREG_sample_dev) - vripple.append(VREG_max-VREG_min) + vripple.append(VREG_max - VREG_min) time_sample_dev = time[100 + np.where(VREG[100:] >= vrefspec)[0][0] :] time_settle.append((time_sample_dev[0])) - df = pd.DataFrame({"Time" : time , "VREG" : VREG,"VREF" :VREF, "cmp_out" : cmp_out}) - df.to_csv(csv1 + "/" + test_conditions +"_.csv",index=False) - df2 = pd.DataFrame({"Iload":load,"Frequency":freq,"Cap_Value":cap, "VREG_Ripple" : vripple,"Settling Time" : time_settle}) - df2.to_csv(csv1 + "/" + "parameters.csv" , index=False) + df = pd.DataFrame( + {"Time": time, "VREG": VREG, "VREF": VREF, "cmp_out": cmp_out} + ) + df.to_csv(csv1 + "/" + test_conditions + "_.csv", index=False) + df2 = pd.DataFrame( + { + "Iload": load, + "Frequency": freq, + "Cap_Value": cap, + "VREG_Ripple": vripple, + "Settling Time": time_settle, + } + ) + df2.to_csv(csv1 + "/" + "parameters.csv", index=False) + raw_files = [(sim_dir + ofile) for ofile in output_file_names] -raw_to_csv(raw_files,float(vrefspec),odir) +raw_to_csv(raw_files, float(vrefspec), odir) if args.figs == "True": figures = list() @@ -211,7 +233,7 @@ def raw_to_csv(raw_files, vrefspec,odir): figure_names.append("active_switches") figures.append(fig_controller_results(raw_files)) # save results to png files - current_freq_results = odir + "/" +simtype+ "/output_plots" + current_freq_results = odir + "/" + simtype + "/output_plots" try: os.mkdir(current_freq_results) except OSError as error: @@ -221,7 +243,11 @@ def raw_to_csv(raw_files, vrefspec,odir): assert len(figures) == len(figure_names) for i, figure in enumerate(figures): figure.savefig(current_freq_results + "/" + figure_names[i] + ".png") - fig_dc_results(sim_dir + "/isweep.raw").savefig(odir + "/" +simtype+"/dc.png") + fig_dc_results(sim_dir + "/isweep.raw").savefig( + odir + "/" + simtype + "/dc.png" + ) max_load = float(iloadspec) - load = max_load*1000 - fig_load_change_results(sim_dir + "/" + str(load) + "mA_output_load_change.raw",load).savefig(odir +"/" +simtype+ "/load_change.png") \ No newline at end of file + load = max_load * 1000 + fig_load_change_results( + sim_dir + "/" + str(load) + "mA_output_load_change.raw", load + ).savefig(odir + "/" + simtype + "/load_change.png")