Adding run_lvs script for GF180BCDLite
diff --git a/BCDLite/klayout/lvs/run_lvs.py b/BCDLite/klayout/lvs/run_lvs.py
new file mode 100644
index 0000000..81dc9de
--- /dev/null
+++ b/BCDLite/klayout/lvs/run_lvs.py
@@ -0,0 +1,426 @@
+################################################################################################
+# Copyright 2023 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.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+################################################################################################
+
+"""Run GlobalFoundries 180nm BCDLite LVS.
+
+Usage:
+ run_lvs.py (--help| -h)
+ run_lvs.py (--layout=<layout_path>) (--netlist=<netlist_path>) (--variant=<combined_options>) [--thr=<thr>] [--run_dir=<run_dir_path>] [--topcell=<topcell_name>] [--run_mode=<run_mode>] [--verbose] [--lvs_sub=<sub_name>] [--no_net_names] [--spice_comments] [--scale] [--schematic_simplify] [--net_only] [--top_lvl_pins] [--combine] [--purge] [--purge_nets]
+
+Options:
+ --help -h Print this help message.
+ --layout=<layout_path> The input GDS file path.
+ --netlist=<netlist_path> The input netlist file path.
+ --variant=<combined_options> Select combined options of metal_top, mim_option, and metal_level. Allowed values (A, B, C).
+ variant=A: Select metal_top=30K mim_option=A metal_level=3LM poly_res=1K, and mim_cap=2
+ variant=B: Select metal_top=11K mim_option=B metal_level=4LM poly_res=1K, and mim_cap=2
+ variant=C: Select metal_top=9K mim_option=B metal_level=5LM poly_res=1K, and mim_cap=2
+ --thr=<thr> The number of threads used in run.
+ --run_dir=<run_dir_path> Run directory to save all the results [default: pwd]
+ --topcell=<topcell_name> Topcell name to use.
+ --run_mode=<run_mode> Select klayout mode Allowed modes (flat , deep, tiling). [default: deep]
+ --lvs_sub=<sub_name> Substrate name used in your design.
+ --verbose Detailed rule execution log for debugging.
+ --no_net_names Discard net names in extracted netlist.
+ --spice_comments Enable netlist comments in extracted netlist.
+ --scale Enable scale of 1e6 in extracted netlist.
+ --schematic_simplify Enable schematic simplification in input netlist.
+ --net_only Enable netlist object creation only in extracted netlist.
+ --top_lvl_pins Enable top level pins only in extracted netlist.
+ --combine Enable netlist combine only in extracted netlist.
+ --purge Enable netlist purge all only in extracted netlist.
+ --purge_nets Enable netlist purge nets only in extracted netlist.
+"""
+
+from docopt import docopt
+import os
+import logging
+import klayout.db
+from datetime import datetime
+from subprocess import check_call
+
+
+def check_klayout_version():
+ """
+ check_klayout_version checks klayout version and makes sure it would work with the LVS.
+ """
+ # ======= Checking Klayout version =======
+ klayout_v_ = os.popen("klayout -b -v").read()
+ klayout_v_ = klayout_v_.split("\n")[0]
+ klayout_v_list = []
+
+ if klayout_v_ == "":
+ logging.error("Klayout is not found. Please make sure klayout is installed.")
+ exit(1)
+ else:
+ klayout_v_list = [int(v) for v in klayout_v_.split(" ")[-1].split(".")]
+
+ if len(klayout_v_list) < 1 or len(klayout_v_list) > 3:
+ logging.error("Was not able to get klayout version properly.")
+ exit(1)
+ elif len(klayout_v_list) >= 2 or len(klayout_v_list) <= 3:
+ if klayout_v_list[1] < 28:
+ logging.error("Prerequisites at a minimum: KLayout 0.28.0")
+ logging.error(
+ "Using this klayout version has not been assesed in this development. Limits are unknown"
+ )
+ exit(1)
+
+ logging.info(f"Your Klayout version is: {klayout_v_}")
+
+
+def check_layout_type(layout_path):
+ """
+ check_layout_type checks if the layout provided is GDS or OAS. Otherwise, kill the process. We only support GDS or OAS now.
+
+ Parameters
+ ----------
+ layout_path : string
+ string that represent the path of the layout.
+
+ Returns
+ -------
+ string
+ string that represent full absolute layout path.
+ """
+
+ if not os.path.isfile(layout_path):
+ logging.error(
+ f"## GDS file path {layout_path} provided doesn't exist or not a file."
+ )
+ exit(1)
+
+ if ".gds" not in layout_path and ".oas" not in layout_path:
+ logging.error(
+ f"## Layout {layout_path} is not in GDSII or OASIS format. Please use gds format."
+ )
+ exit(1)
+
+ return os.path.abspath(layout_path)
+
+
+def get_top_cell_names(gds_path):
+ """
+ get_top_cell_names get the top cell names from the GDS file.
+
+ Parameters
+ ----------
+ gds_path : string
+ Path to the target GDS file.
+
+ Returns
+ -------
+ List of string
+ Names of the top cell in the layout.
+ """
+ layout = klayout.db.Layout()
+ layout.read(gds_path)
+ top_cells = [t.name for t in layout.top_cells()]
+
+ return top_cells
+
+
+def get_run_top_cell_name(arguments, layout_path):
+ """
+ get_run_top_cell_name Get the top cell name to use for running. If it's provided by the user, we use the user input.
+ If not, we get it from the GDS file.
+
+ Parameters
+ ----------
+ arguments : dict
+ Dictionary that holds the user inputs for the script generated by docopt.
+ layout_path : string
+ Path to the target layout.
+
+ Returns
+ -------
+ string
+ Name of the topcell to use in run.
+
+ """
+
+ if arguments["--topcell"]:
+ topcell = arguments["--topcell"]
+ else:
+ layout_topcells = get_top_cell_names(layout_path)
+ if len(layout_topcells) > 1:
+ logging.error(
+ "## Layout has multiple topcells. Please use --topcell to determine which topcell you want to run on."
+ )
+ exit(1)
+ else:
+ topcell = layout_topcells[0]
+
+ return topcell
+
+
+def generate_klayout_switches(arguments, layout_path, netlist_path):
+ """
+ parse_switches Function that parse all the args from input to prepare switches for LVS run.
+
+ Parameters
+ ----------
+ arguments : dict
+ Dictionary that holds the arguments used by user in the run command. This is generated by docopt library.
+ layout_path : string
+ Path to the layout file that we will run LVS on.
+ netlist_path : string
+ Path to the netlist file that we will run LVS on.
+
+ Returns
+ -------
+ dict
+ Dictionary that represent all run switches passed to klayout.
+ """
+ switches = dict()
+
+ # No. of threads
+ thrCount = 2 if arguments["--thr"] is None else int(arguments["--thr"])
+ switches["thr"] = str(int(thrCount))
+
+ if arguments["--run_mode"] in ["flat", "deep", "tiling"]:
+ switches["run_mode"] = arguments["--run_mode"]
+ else:
+ logging.error("Allowed klayout modes are (flat , deep , tiling) only")
+ exit()
+
+ if arguments["--variant"] == "A":
+ switches["metal_top"] = "30K"
+ switches["mim_option"] = "A"
+ switches["metal_level"] = "3LM"
+ switches["poly_res"] = "1K"
+ switches["mim_cap"] = "2"
+ elif arguments["--variant"] == "B":
+ switches["metal_top"] = "11K"
+ switches["mim_option"] = "B"
+ switches["metal_level"] = "4LM"
+ switches["poly_res"] = "1K"
+ switches["mim_cap"] = "2"
+ elif arguments["--variant"] == "C":
+ switches["metal_top"] = "9K"
+ switches["mim_option"] = "B"
+ switches["metal_level"] = "5LM"
+ switches["poly_res"] = "1K"
+ switches["mim_cap"] = "2"
+ else:
+ logging.error("variant switch allowed values are (A , B, C) only")
+ exit(1)
+
+ if arguments["--lvs_sub"]:
+ switches["lvs_sub"] = arguments["--lvs_sub"]
+ else:
+ switches["lvs_sub"] = "gf180BCDLite_gnd"
+
+ if arguments["--verbose"]:
+ switches["verbose"] = "true"
+ else:
+ switches["verbose"] = "false"
+
+ if arguments["--no_net_names"]:
+ switches["spice_net_names"] = "false"
+ else:
+ switches["spice_net_names"] = "true"
+
+ if arguments["--spice_comments"]:
+ switches["spice_comments"] = "true"
+ else:
+ switches["spice_comments"] = "false"
+
+ if arguments["--scale"]:
+ switches["scale"] = "true"
+ else:
+ switches["scale"] = "false"
+
+ if arguments["--schematic_simplify"]:
+ switches["schematic_simplify"] = "true"
+ else:
+ switches["schematic_simplify"] = "false"
+
+ if arguments["--net_only"]:
+ switches["net_only"] = "true"
+ else:
+ switches["net_only"] = "false"
+
+ if arguments["--top_lvl_pins"]:
+ switches["top_lvl_pins"] = "true"
+ else:
+ switches["top_lvl_pins"] = "false"
+
+ if arguments["--combine"]:
+ switches["combine"] = "true"
+ else:
+ switches["combine"] = "false"
+
+ if arguments["--purge"]:
+ switches["purge"] = "true"
+ else:
+ switches["purge"] = "false"
+
+ if arguments["--purge_nets"]:
+ switches["purge_nets"] = "true"
+ else:
+ switches["purge_nets"] = "false"
+
+ switches["topcell"] = get_run_top_cell_name(arguments, layout_path)
+ switches["input"] = os.path.abspath(layout_path)
+ switches["schematic"] = os.path.abspath(netlist_path)
+
+ return switches
+
+
+def build_switches_string(sws: dict):
+ """
+ build_switches_string Build swtiches string from dictionary.
+
+ Parameters
+ ----------
+ sws : dict
+ Dictionary that holds the Antenna swithces.
+ """
+ return " ".join(f"-rd {k}={v}" for k, v in sws.items())
+
+
+def check_lvs_results(results_db_files: list):
+ """
+ check_lvs_results Checks the results db generated from run and report at the end if the LVS run failed or passed.
+
+ Parameters
+ ----------
+ results_db_files : list
+ A list of strings that represent paths to results databases of all the LVS runs.
+ """
+
+ if len(results_db_files) < 1:
+ logging.error("Klayout did not generate any db results. Please check run logs")
+ exit(1)
+
+
+def run_check(lvs_file: str, path: str, run_dir: str, sws: dict):
+ """
+ run_check run LVS check.
+
+ Parameters
+ ----------
+ lvs_file : str
+ String that has the file full path to run.
+ path : str
+ String that holds the full path of the layout.
+ run_dir : str
+ String that holds the full path of the run location.
+ sws : dict
+ Dictionary that holds all switches that needs to be passed to the antenna checks.
+
+ Returns
+ -------
+ string
+ string that represent the path to the results output database for this run.
+
+ """
+
+ logging.info(f'Running Global Foundries 180nm BCDLite {lvs_file} checks on design {path} on cell {sws["topcell"]}')
+
+ layout_base_name = os.path.basename(path).split(".")[0]
+ new_sws = sws.copy()
+ report_path = os.path.join(run_dir, f"{layout_base_name}.lvsdb")
+ ext_net_path = os.path.join(run_dir, f"{layout_base_name}.cir")
+ new_sws["report"] = report_path
+ new_sws["target_netlist"] = ext_net_path
+
+ sws_str = build_switches_string(new_sws)
+
+ run_str = f"klayout -b -r {lvs_file} {sws_str}"
+ check_call(run_str, shell=True)
+
+ return report_path
+
+
+def main(lvs_run_dir: str, arguments: dict):
+ """
+ main function to run the LVS.
+
+ Parameters
+ ----------
+ lvs_run_dir : str
+ String with absolute path of the full run dir.
+ arguments : dict
+ Dictionary that holds the arguments used by user in the run command. This is generated by docopt library.
+ """
+
+ ## Check Klayout version
+ check_klayout_version()
+
+ ## Check layout file existance
+ layout_path = arguments["--layout"]
+ if not os.path.exists(arguments["--layout"]):
+ logging.error(
+ f"The input GDS file path {layout_path} doesn't exist, please recheck."
+ )
+ exit(1)
+
+ ## Check layout type
+ layout_path = check_layout_type(layout_path)
+
+ # Check netlist file existance
+ netlist_path = arguments["--netlist"]
+ if not os.path.exists(arguments["--netlist"]):
+ logging.error(
+ f"The input netlist file path {netlist_path} doesn't exist, please recheck."
+ )
+ exit(1)
+
+ lvs_rule_deck = os.path.join(os.path.dirname(os.path.abspath(__file__)), "gf180BCDLite.lvs")
+
+ ## Get run switches
+ switches = generate_klayout_switches(arguments, layout_path, netlist_path)
+
+ ## Run LVS check
+ res_db_files = run_check(lvs_rule_deck, layout_path, lvs_run_dir, switches)
+
+ ## Check run
+ check_lvs_results(res_db_files)
+
+
+if __name__ == "__main__":
+
+ # arguments
+ arguments = docopt(__doc__, version="RUN LVS: 1.0")
+
+ # logs format
+ now_str = datetime.utcnow().strftime("lvs_run_%Y_%m_%d_%H_%M_%S")
+
+ if (
+ arguments["--run_dir"] == "pwd"
+ or arguments["--run_dir"] == ""
+ or arguments["--run_dir"] is None
+ ):
+ lvs_run_dir = os.path.join(os.path.abspath(os.getcwd()), now_str)
+ else:
+ lvs_run_dir = os.path.abspath(arguments["--run_dir"])
+
+ os.makedirs(lvs_run_dir, exist_ok=True)
+
+ logging.basicConfig(
+ level=logging.DEBUG,
+ handlers=[
+ logging.FileHandler(os.path.join(lvs_run_dir, "{}.log".format(now_str))),
+ logging.StreamHandler(),
+ ],
+ format="%(asctime)s | %(levelname)-7s | %(message)s",
+ datefmt="%d-%b-%Y %H:%M:%S",
+ )
+
+ # Calling main function
+ main(lvs_run_dir, arguments)